mirror of
https://github.com/notepad-plus-plus/notepad-plus-plus.git
synced 2025-09-13 13:08:24 +02:00
Enhance auto-insert behaviour for parenthesis, bracket and Braces inside of parenthesis, bracket or Braces.
851 lines
24 KiB
C++
851 lines
24 KiB
C++
// This file is part of Notepad++ project
|
|
// Copyright (C)2003 Don HO <don.h@free.fr>
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// Note that the GPL places important restrictions on "derived works", yet
|
|
// it does not provide a detailed definition of that term. To avoid
|
|
// misunderstandings, we consider an application to constitute a
|
|
// "derivative work" for the purpose of this license if it does any of the
|
|
// following:
|
|
// 1. Integrates source code from Notepad++.
|
|
// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable
|
|
// installer, such as those produced by InstallShield.
|
|
// 3. Links to a library or executes a program that does any of the above.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
#include <algorithm>
|
|
#include <locale>
|
|
#include <shlwapi.h>
|
|
#include "AutoCompletion.h"
|
|
#include "Notepad_plus_msgs.h"
|
|
|
|
using namespace std;
|
|
|
|
static bool isInList(generic_string word, const vector<generic_string> & wordArray)
|
|
{
|
|
for (size_t i = 0, len = wordArray.size(); i < len; ++i)
|
|
if (wordArray[i] == word)
|
|
return true;
|
|
return false;
|
|
};
|
|
|
|
|
|
bool AutoCompletion::showApiComplete()
|
|
{
|
|
if (!_funcCompletionActive)
|
|
return false;
|
|
|
|
// calculate entered word's length
|
|
int curPos = int(_pEditView->execute(SCI_GETCURRENTPOS));
|
|
int startPos = int(_pEditView->execute(SCI_WORDSTARTPOSITION, curPos, true));
|
|
|
|
if (curPos == startPos)
|
|
return false;
|
|
|
|
size_t len = (curPos > startPos)?(curPos - startPos):(startPos - curPos);
|
|
if (len >= _keyWordMaxLen)
|
|
return false;
|
|
|
|
_pEditView->execute(SCI_AUTOCSETSEPARATOR, WPARAM(' '));
|
|
_pEditView->execute(SCI_AUTOCSETIGNORECASE, _ignoreCase);
|
|
_pEditView->showAutoComletion(curPos - startPos, _keyWords.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AutoCompletion::showApiAndWordComplete()
|
|
{
|
|
int curPos = int(_pEditView->execute(SCI_GETCURRENTPOS));
|
|
int startPos = int(_pEditView->execute(SCI_WORDSTARTPOSITION, curPos, true));
|
|
|
|
if (curPos == startPos)
|
|
return false;
|
|
|
|
const size_t bufSize = 256;
|
|
TCHAR beginChars[bufSize];
|
|
|
|
size_t len = (curPos > startPos)?(curPos - startPos):(startPos - curPos);
|
|
if (len >= bufSize)
|
|
return false;
|
|
|
|
// Get word array
|
|
vector<generic_string> wordArray;
|
|
_pEditView->getGenericText(beginChars, bufSize, startPos, curPos);
|
|
|
|
getWordArray(wordArray, beginChars);
|
|
|
|
|
|
for (size_t i = 0, len = _keyWordArray.size(); i < len; ++i)
|
|
{
|
|
if (_keyWordArray[i].find(beginChars) == 0)
|
|
{
|
|
if (!isInList(_keyWordArray[i], wordArray))
|
|
wordArray.push_back(_keyWordArray[i]);
|
|
}
|
|
}
|
|
|
|
sort(wordArray.begin(), wordArray.end());
|
|
|
|
// Get word list
|
|
generic_string words;
|
|
|
|
for (size_t i = 0, len = wordArray.size(); i < len; ++i)
|
|
{
|
|
words += wordArray[i];
|
|
if (i != wordArray.size()-1)
|
|
words += TEXT(" ");
|
|
}
|
|
|
|
_pEditView->execute(SCI_AUTOCSETSEPARATOR, WPARAM(' '));
|
|
_pEditView->execute(SCI_AUTOCSETIGNORECASE, _ignoreCase);
|
|
_pEditView->showAutoComletion(curPos - startPos, words.c_str());
|
|
return true;
|
|
}
|
|
|
|
|
|
void AutoCompletion::getWordArray(vector<generic_string> & wordArray, TCHAR *beginChars)
|
|
{
|
|
const size_t bufSize = 256;
|
|
|
|
generic_string expr(TEXT("\\<"));
|
|
expr += beginChars;
|
|
expr += TEXT("[^ \\t\\n\\r.,;:\"()=<>'+!\\[\\]]*");
|
|
|
|
int docLength = int(_pEditView->execute(SCI_GETLENGTH));
|
|
|
|
int flags = SCFIND_WORDSTART | SCFIND_MATCHCASE | SCFIND_REGEXP | SCFIND_POSIX;
|
|
|
|
_pEditView->execute(SCI_SETSEARCHFLAGS, flags);
|
|
int posFind = _pEditView->searchInTarget(expr.c_str(), expr.length(), 0, docLength);
|
|
|
|
while (posFind != -1 && posFind != -2)
|
|
{
|
|
int wordStart = int(_pEditView->execute(SCI_GETTARGETSTART));
|
|
int wordEnd = int(_pEditView->execute(SCI_GETTARGETEND));
|
|
|
|
size_t foundTextLen = wordEnd - wordStart;
|
|
if (foundTextLen < bufSize)
|
|
{
|
|
TCHAR w[bufSize];
|
|
_pEditView->getGenericText(w, bufSize, wordStart, wordEnd);
|
|
|
|
if (lstrcmp(w, beginChars) != 0)
|
|
if (!isInList(w, wordArray))
|
|
wordArray.push_back(w);
|
|
}
|
|
posFind = _pEditView->searchInTarget(expr.c_str(), expr.length(), wordEnd, docLength);
|
|
}
|
|
}
|
|
|
|
static generic_string addTrailingSlash(generic_string path)
|
|
{
|
|
if(path.length() >=1 && path[path.length() - 1] == '\\')
|
|
return path;
|
|
else
|
|
return path + L"\\";
|
|
}
|
|
|
|
static generic_string removeTrailingSlash(generic_string path)
|
|
{
|
|
if(path.length() >= 1 && path[path.length() - 1] == '\\')
|
|
return path.substr(0, path.length() - 1);
|
|
else
|
|
return path;
|
|
}
|
|
|
|
static bool isDirectory(generic_string path)
|
|
{
|
|
DWORD type = ::GetFileAttributes(path.c_str());
|
|
return type != INVALID_FILE_ATTRIBUTES && (type & FILE_ATTRIBUTE_DIRECTORY);
|
|
}
|
|
|
|
static bool isFile(generic_string path)
|
|
{
|
|
DWORD type = ::GetFileAttributes(path.c_str());
|
|
return type != INVALID_FILE_ATTRIBUTES && ! (type & FILE_ATTRIBUTE_DIRECTORY);
|
|
}
|
|
|
|
static bool isAllowedBeforeDriveLetter(TCHAR c)
|
|
{
|
|
locale loc;
|
|
return c == '\'' || c == '"' || c == '(' || std::isspace(c, loc);
|
|
}
|
|
|
|
static bool getRawPath(generic_string input, generic_string &rawPath_out)
|
|
{
|
|
// Try to find a path in the given input.
|
|
// Algorithm: look for a colon. The colon must be preceded by an alphabetic character.
|
|
// The alphabetic character must, in turn, be preceded by nothing, or by whitespace, or by
|
|
// a quotation mark.
|
|
locale loc;
|
|
size_t lastOccurrence = input.rfind(L":");
|
|
if(lastOccurrence == std::string::npos) // No match.
|
|
return false;
|
|
else if(lastOccurrence == 0)
|
|
return false;
|
|
else if(!std::isalpha(input[lastOccurrence - 1], loc))
|
|
return false;
|
|
else if(lastOccurrence >= 2 && !isAllowedBeforeDriveLetter(input[lastOccurrence - 2]))
|
|
return false;
|
|
|
|
rawPath_out = input.substr(lastOccurrence - 1);
|
|
return true;
|
|
}
|
|
|
|
static bool getPathsForPathCompletion(generic_string input, generic_string &rawPath_out, generic_string &pathToMatch_out)
|
|
{
|
|
generic_string rawPath;
|
|
if(! getRawPath(input, rawPath))
|
|
{
|
|
return false;
|
|
}
|
|
else if(isFile(rawPath) || isFile(removeTrailingSlash(rawPath)))
|
|
{
|
|
return false;
|
|
}
|
|
else if(isDirectory(rawPath))
|
|
{
|
|
rawPath_out = rawPath;
|
|
pathToMatch_out = rawPath;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
size_t last_occurrence = rawPath.rfind(L"\\");
|
|
if(last_occurrence == std::string::npos) // No match.
|
|
return false;
|
|
else
|
|
{
|
|
rawPath_out = rawPath;
|
|
pathToMatch_out = rawPath.substr(0, last_occurrence);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AutoCompletion::showPathCompletion()
|
|
{
|
|
// Get current line (at most MAX_PATH characters "backwards" from current caret).
|
|
generic_string currentLine;
|
|
{
|
|
const long bufSize = MAX_PATH;
|
|
TCHAR buf[bufSize + 1];
|
|
const int currentPos = _pEditView->execute(SCI_GETCURRENTPOS);
|
|
const int startPos = max(0, currentPos - bufSize);
|
|
_pEditView->getGenericText(buf, bufSize + 1, startPos, currentPos);
|
|
currentLine = buf;
|
|
}
|
|
|
|
/* Try to figure out which path the user wants us to complete.
|
|
We need to know the "raw path", which is what the user actually wrote.
|
|
But we also need to know which directory to look in (pathToMatch), which might
|
|
not be the same as what the user wrote. This happens when the user types an
|
|
incomplete name.
|
|
For instance: the user wants to autocomplete "C:\Wind", and assuming that no such directory
|
|
exists, this means we should list all files and directories in C:.
|
|
*/
|
|
generic_string rawPath, pathToMatch;
|
|
if(! getPathsForPathCompletion(currentLine, rawPath, pathToMatch))
|
|
return;
|
|
|
|
// Get all files and directories in the path.
|
|
generic_string autoCompleteEntries;
|
|
{
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA data;
|
|
generic_string pathToMatchPlusSlash = addTrailingSlash(pathToMatch);
|
|
generic_string searchString = pathToMatchPlusSlash + TEXT("*.*");
|
|
hFind = ::FindFirstFile(searchString.c_str(), &data);
|
|
if(hFind != INVALID_HANDLE_VALUE)
|
|
{
|
|
// Maximum number of entries to show. Without this it appears to the user like N++ hangs when autocompleting
|
|
// some really large directories (c:\windows\winxsys on my system for instance).
|
|
const unsigned int maxEntries = 2000;
|
|
unsigned int counter = 0;
|
|
do
|
|
{
|
|
if(++counter > maxEntries)
|
|
break;
|
|
|
|
if(generic_string(data.cFileName) == TEXT(".") || generic_string(data.cFileName) == TEXT(".."))
|
|
continue;
|
|
|
|
if(! autoCompleteEntries.empty())
|
|
autoCompleteEntries += TEXT("\n");
|
|
|
|
autoCompleteEntries += pathToMatchPlusSlash;
|
|
autoCompleteEntries += data.cFileName;
|
|
if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // If directory, add trailing slash.
|
|
autoCompleteEntries += TEXT("\\");
|
|
|
|
} while(::FindNextFile(hFind, &data));
|
|
::FindClose(hFind);
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
|
|
// Show autocompletion box.
|
|
_pEditView->execute(SCI_AUTOCSETSEPARATOR, WPARAM('\n'));
|
|
_pEditView->execute(SCI_AUTOCSETIGNORECASE, true);
|
|
_pEditView->showAutoComletion(rawPath.length(), autoCompleteEntries.c_str());
|
|
return;
|
|
}
|
|
|
|
bool AutoCompletion::showWordComplete(bool autoInsert)
|
|
{
|
|
int curPos = int(_pEditView->execute(SCI_GETCURRENTPOS));
|
|
int startPos = int(_pEditView->execute(SCI_WORDSTARTPOSITION, curPos, true));
|
|
|
|
if (curPos == startPos)
|
|
return false;
|
|
|
|
const size_t bufSize = 256;
|
|
TCHAR beginChars[bufSize];
|
|
|
|
size_t len = (curPos > startPos)?(curPos - startPos):(startPos - curPos);
|
|
if (len >= bufSize)
|
|
return false;
|
|
|
|
// Get word array
|
|
vector<generic_string> wordArray;
|
|
_pEditView->getGenericText(beginChars, bufSize, startPos, curPos);
|
|
|
|
getWordArray(wordArray, beginChars);
|
|
|
|
if (wordArray.size() == 0) return false;
|
|
|
|
if (wordArray.size() == 1 && autoInsert)
|
|
{
|
|
_pEditView->replaceTargetRegExMode(wordArray[0].c_str(), startPos, curPos);
|
|
_pEditView->execute(SCI_GOTOPOS, startPos + wordArray[0].length());
|
|
return true;
|
|
}
|
|
|
|
sort(wordArray.begin(), wordArray.end());
|
|
|
|
// Get word list
|
|
generic_string words(TEXT(""));
|
|
|
|
for (size_t i = 0, len = wordArray.size(); i < len; ++i)
|
|
{
|
|
words += wordArray[i];
|
|
if (i != wordArray.size()-1)
|
|
words += TEXT(" ");
|
|
}
|
|
|
|
_pEditView->execute(SCI_AUTOCSETSEPARATOR, WPARAM(' '));
|
|
_pEditView->execute(SCI_AUTOCSETIGNORECASE, _ignoreCase);
|
|
_pEditView->showAutoComletion(curPos - startPos, words.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool AutoCompletion::showFunctionComplete()
|
|
{
|
|
if (!_funcCompletionActive)
|
|
return false;
|
|
|
|
if (_funcCalltip.updateCalltip(0, true))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void AutoCompletion::getCloseTag(char *closeTag, size_t closeTagSize, size_t caretPos)
|
|
{
|
|
int flags = SCFIND_REGEXP | SCFIND_POSIX;
|
|
_pEditView->execute(SCI_SETSEARCHFLAGS, flags);
|
|
TCHAR tag2find[] = TEXT("<[^\\s>]*");
|
|
int targetStart = _pEditView->searchInTarget(tag2find, lstrlen(tag2find), caretPos, 0);
|
|
|
|
if (targetStart == -1 || targetStart == -2)
|
|
return;
|
|
|
|
int targetEnd = int(_pEditView->execute(SCI_GETTARGETEND));
|
|
int foundTextLen = targetEnd - targetStart;
|
|
if (foundTextLen < 2) // "<>" will be ignored
|
|
return;
|
|
|
|
if (size_t(foundTextLen) > closeTagSize - 2) // buffer size is not large enough. -2 for '/' & '\0'
|
|
return;
|
|
|
|
char tagHead[5];
|
|
_pEditView->getText(tagHead, targetStart, targetStart+4);
|
|
|
|
if (tagHead[1] == '/') // "</toto>" will be ignored
|
|
return;
|
|
|
|
if (strncmp(tagHead, "<!--", 4) == 0) // Comments will be ignored
|
|
return;
|
|
|
|
char tagTail[2];
|
|
_pEditView->getText(tagTail, caretPos-2, caretPos-1);
|
|
|
|
if (tagTail[0] == '/') // "<toto/>" and "<toto arg="0" />" will be ignored
|
|
return;
|
|
|
|
closeTag[0] = '<';
|
|
closeTag[1] = '/';
|
|
_pEditView->getText(closeTag + 2, targetStart + 1, targetEnd);
|
|
closeTag[foundTextLen+1] = '>';
|
|
closeTag[foundTextLen+2] = '\0';
|
|
}
|
|
|
|
void InsertedMatchedChars::removeInvalidElements(MatchedCharInserted mci)
|
|
{
|
|
if (mci._c == '\n' || mci._c == '\r') // create a new line, so all matched char are invalidated
|
|
{
|
|
_insertedMatchedChars.clear();
|
|
}
|
|
else
|
|
{
|
|
for (int i = _insertedMatchedChars.size() - 1; i >= 0; --i)
|
|
{
|
|
if (_insertedMatchedChars[i]._pos < mci._pos)
|
|
{
|
|
int posToDetectLine = _pEditView->execute(SCI_LINEFROMPOSITION, mci._pos);
|
|
int startPosLine = _pEditView->execute(SCI_LINEFROMPOSITION, _insertedMatchedChars[i]._pos);
|
|
|
|
if (posToDetectLine != startPosLine) //not in the same line
|
|
{
|
|
_insertedMatchedChars.erase(_insertedMatchedChars.begin() + i);
|
|
}
|
|
}
|
|
else // current position is before matchedStartSybol Pos
|
|
{
|
|
_insertedMatchedChars.erase(_insertedMatchedChars.begin() + i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void InsertedMatchedChars::add(MatchedCharInserted mci)
|
|
{
|
|
removeInvalidElements(mci);
|
|
_insertedMatchedChars.push_back(mci);
|
|
}
|
|
|
|
// if current pos > matchedStartSybol Pos and current pos is on the same line of matchedStartSybolPos, it'll be checked then removed
|
|
// otherwise it is just removed
|
|
// return the pos of matchedEndSybol or -1 if matchedEndSybol not found
|
|
int InsertedMatchedChars::search(char startChar, char endChar, int posToDetect)
|
|
{
|
|
if (isEmpty())
|
|
return -1;
|
|
int posToDetectLine = _pEditView->execute(SCI_LINEFROMPOSITION, posToDetect);
|
|
|
|
for (int i = _insertedMatchedChars.size() - 1; i >= 0; --i)
|
|
{
|
|
if (_insertedMatchedChars[i]._c == startChar)
|
|
{
|
|
if (_insertedMatchedChars[i]._pos < posToDetect)
|
|
{
|
|
int startPosLine = _pEditView->execute(SCI_LINEFROMPOSITION, _insertedMatchedChars[i]._pos);
|
|
if (posToDetectLine == startPosLine)
|
|
{
|
|
int endPos = _pEditView->execute(SCI_GETLINEENDPOSITION, startPosLine);
|
|
|
|
for (int j = posToDetect; j <= endPos; ++j)
|
|
{
|
|
char aChar = (char)_pEditView->execute(SCI_GETCHARAT, j);
|
|
|
|
if (aChar != ' ') // non space is not allowed
|
|
{
|
|
|
|
if (aChar == endChar) // found it!!!
|
|
{
|
|
_insertedMatchedChars.erase(_insertedMatchedChars.begin() + i);
|
|
return j;
|
|
}
|
|
else // whichever character, stop searching
|
|
{
|
|
_insertedMatchedChars.erase(_insertedMatchedChars.begin() + i);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // not in the same line
|
|
{
|
|
_insertedMatchedChars.erase(_insertedMatchedChars.begin() + i);
|
|
}
|
|
}
|
|
else // current position is before matchedStartSybol Pos
|
|
{
|
|
_insertedMatchedChars.erase(_insertedMatchedChars.begin() + i);
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void AutoCompletion::insertMatchedChars(int character, const MatchedPairConf & matchedPairConf)
|
|
{
|
|
const vector< pair<char, char> > & matchedPairs = matchedPairConf._matchedPairs;
|
|
int caretPos = _pEditView->execute(SCI_GETCURRENTPOS);
|
|
char *matchedChars = NULL;
|
|
|
|
char charPrev = (char)_pEditView->execute(SCI_GETCHARAT, caretPos - 2);
|
|
char charNext = (char)_pEditView->execute(SCI_GETCHARAT, caretPos);
|
|
|
|
bool isCharPrevBlank = (charPrev == ' ' || charPrev == '\t' || charPrev == '\n' || charPrev == '\r' || charPrev == '\0');
|
|
int docLen = _pEditView->getCurrentDocLen();
|
|
bool isCharNextBlank = (charNext == ' ' || charNext == '\t' || charNext == '\n' || charNext == '\r' || caretPos == docLen);
|
|
bool isInSandwich = (charPrev == '(' && charNext == ')') || (charPrev == '[' && charNext == ']') || (charPrev == '{' && charNext == '}');
|
|
|
|
// User defined matched pairs should be checked firstly
|
|
for (size_t i = 0, len = matchedPairs.size(); i < len; ++i)
|
|
{
|
|
if (int(matchedPairs[i].first) == character)
|
|
{
|
|
if (isCharNextBlank)
|
|
{
|
|
char userMatchedChar[2] = { '\0', '\0' };
|
|
userMatchedChar[0] = matchedPairs[i].second;
|
|
_pEditView->execute(SCI_INSERTTEXT, caretPos, (LPARAM)userMatchedChar);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if there's no user defined matched pair found, continue to check notepad++'s one
|
|
|
|
const size_t closeTagLen = 256;
|
|
char closeTag[closeTagLen];
|
|
closeTag[0] = '\0';
|
|
switch (character)
|
|
{
|
|
case int('('):
|
|
if (matchedPairConf._doParentheses)
|
|
{
|
|
if (isCharNextBlank || isInSandwich)
|
|
|
|
{
|
|
matchedChars = ")";
|
|
_insertedMatchedChars.add(MatchedCharInserted(char(character), caretPos - 1));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case int('['):
|
|
if (matchedPairConf._doBrackets)
|
|
{
|
|
if (isCharNextBlank || isInSandwich)
|
|
{
|
|
matchedChars = "]";
|
|
_insertedMatchedChars.add(MatchedCharInserted(char(character), caretPos - 1));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case int('{'):
|
|
if (matchedPairConf._doCurlyBrackets)
|
|
{
|
|
if (isCharNextBlank || isInSandwich)
|
|
{
|
|
matchedChars = "}";
|
|
_insertedMatchedChars.add(MatchedCharInserted(char(character), caretPos - 1));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case int('"'):
|
|
if (matchedPairConf._doDoubleQuotes)
|
|
{
|
|
if (!_insertedMatchedChars.isEmpty())
|
|
{
|
|
int pos = _insertedMatchedChars.search('"', char(character), caretPos);
|
|
if (pos != -1)
|
|
{
|
|
_pEditView->execute(SCI_DELETERANGE, pos, 1);
|
|
_pEditView->execute(SCI_GOTOPOS, pos);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((isCharPrevBlank && isCharNextBlank) || isInSandwich ||
|
|
(charPrev == '(' && isCharNextBlank) || (isCharPrevBlank && charNext == ')') ||
|
|
(charPrev == '[' && isCharNextBlank) || (isCharPrevBlank && charNext == ']') ||
|
|
(charPrev == '{' && isCharNextBlank) || (isCharPrevBlank && charNext == '}'))
|
|
{
|
|
matchedChars = "\"";
|
|
_insertedMatchedChars.add(MatchedCharInserted(char(character), caretPos - 1));
|
|
}
|
|
}
|
|
break;
|
|
case int('\''):
|
|
if (matchedPairConf._doQuotes)
|
|
{
|
|
if (!_insertedMatchedChars.isEmpty())
|
|
{
|
|
int pos = _insertedMatchedChars.search('\'', char(character), caretPos);
|
|
if (pos != -1)
|
|
{
|
|
_pEditView->execute(SCI_DELETERANGE, pos, 1);
|
|
_pEditView->execute(SCI_GOTOPOS, pos);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((isCharPrevBlank && isCharNextBlank) || isInSandwich ||
|
|
(charPrev == '(' && isCharNextBlank) || (isCharPrevBlank && charNext == ')') ||
|
|
(charPrev == '[' && isCharNextBlank) || (isCharPrevBlank && charNext == ']') ||
|
|
(charPrev == '{' && isCharNextBlank) || (isCharPrevBlank && charNext == '}'))
|
|
{
|
|
matchedChars = "'";
|
|
_insertedMatchedChars.add(MatchedCharInserted(char(character), caretPos - 1));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case int('>'):
|
|
{
|
|
if (matchedPairConf._doHtmlXmlTag && (_curLang == L_HTML || _curLang == L_XML))
|
|
{
|
|
getCloseTag(closeTag, closeTagLen, caretPos);
|
|
if (closeTag[0] != '\0')
|
|
matchedChars = closeTag;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case int(')') :
|
|
case int(']') :
|
|
case int('}') :
|
|
if (!_insertedMatchedChars.isEmpty())
|
|
{
|
|
char startChar;
|
|
if (character == int(')'))
|
|
{
|
|
if (!matchedPairConf._doParentheses)
|
|
return;
|
|
startChar = '(';
|
|
}
|
|
else if (character == int(']'))
|
|
{
|
|
if (!matchedPairConf._doBrackets)
|
|
return;
|
|
startChar = '[';
|
|
}
|
|
else // if (character == int('}'))
|
|
{
|
|
if (!matchedPairConf._doCurlyBrackets)
|
|
return;
|
|
startChar = '{';
|
|
}
|
|
|
|
int pos = _insertedMatchedChars.search(startChar, char(character), caretPos);
|
|
if (pos != -1)
|
|
{
|
|
_pEditView->execute(SCI_DELETERANGE, pos, 1);
|
|
_pEditView->execute(SCI_GOTOPOS, pos);
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (!_insertedMatchedChars.isEmpty())
|
|
_insertedMatchedChars.removeInvalidElements(MatchedCharInserted(char(character), caretPos - 1));
|
|
}
|
|
|
|
if (matchedChars)
|
|
_pEditView->execute(SCI_INSERTTEXT, caretPos, (LPARAM)matchedChars);
|
|
}
|
|
|
|
|
|
void AutoCompletion::update(int character)
|
|
{
|
|
const NppGUI & nppGUI = NppParameters::getInstance()->getNppGUI();
|
|
if (!_funcCompletionActive && nppGUI._autocStatus == nppGUI.autoc_func)
|
|
return;
|
|
|
|
if (nppGUI._funcParams || _funcCalltip.isVisible()) {
|
|
if (_funcCalltip.updateCalltip(character)) { //calltip visible because triggered by autocomplete, set mode
|
|
return; //only return in case of success, else autocomplete
|
|
}
|
|
}
|
|
|
|
if (!character)
|
|
return;
|
|
|
|
//If autocomplete already active, let Scintilla handle it
|
|
if (_pEditView->execute(SCI_AUTOCACTIVE) != 0)
|
|
return;
|
|
|
|
const int wordSize = 64;
|
|
TCHAR s[wordSize];
|
|
_pEditView->getWordToCurrentPos(s, wordSize);
|
|
|
|
if (lstrlen(s) >= int(nppGUI._autocFromLen))
|
|
{
|
|
if (nppGUI._autocStatus == nppGUI.autoc_word)
|
|
showWordComplete(false);
|
|
else if (nppGUI._autocStatus == nppGUI.autoc_func)
|
|
showApiComplete();
|
|
else if (nppGUI._autocStatus == nppGUI.autoc_both)
|
|
showApiAndWordComplete();
|
|
|
|
}
|
|
}
|
|
|
|
void AutoCompletion::callTipClick(int direction) {
|
|
if (!_funcCompletionActive)
|
|
return;
|
|
|
|
if (direction == 1) {
|
|
_funcCalltip.showPrevOverload();
|
|
} else if (direction == 2) {
|
|
_funcCalltip.showNextOverload();
|
|
}
|
|
}
|
|
|
|
bool AutoCompletion::setLanguage(LangType language) {
|
|
if (_curLang == language)
|
|
return true;
|
|
_curLang = language;
|
|
|
|
TCHAR path[MAX_PATH];
|
|
::GetModuleFileName(NULL, path, MAX_PATH);
|
|
PathRemoveFileSpec(path);
|
|
lstrcat(path, TEXT("\\plugins\\APIs\\"));
|
|
lstrcat(path, getApiFileName());
|
|
lstrcat(path, TEXT(".xml"));
|
|
|
|
if (_pXmlFile)
|
|
delete _pXmlFile;
|
|
|
|
_pXmlFile = new TiXmlDocument(path);
|
|
_funcCompletionActive = _pXmlFile->LoadFile();
|
|
|
|
TiXmlNode * pAutoNode = NULL;
|
|
if (_funcCompletionActive) {
|
|
_funcCompletionActive = false; //safety
|
|
TiXmlNode * pNode = _pXmlFile->FirstChild(TEXT("NotepadPlus"));
|
|
if (!pNode)
|
|
return false;
|
|
pAutoNode = pNode = pNode->FirstChildElement(TEXT("AutoComplete"));
|
|
if (!pNode)
|
|
return false;
|
|
pNode = pNode->FirstChildElement(TEXT("KeyWord"));
|
|
if (!pNode)
|
|
return false;
|
|
_pXmlKeyword = reinterpret_cast<TiXmlElement *>(pNode);
|
|
if (!_pXmlKeyword)
|
|
return false;
|
|
_funcCompletionActive = true;
|
|
}
|
|
|
|
if(_funcCompletionActive) //try setting up environment
|
|
{
|
|
//setup defaults
|
|
_ignoreCase = true;
|
|
_funcCalltip._start = '(';
|
|
_funcCalltip._stop = ')';
|
|
_funcCalltip._param = ',';
|
|
_funcCalltip._terminal = ';';
|
|
_funcCalltip._ignoreCase = true;
|
|
_funcCalltip._additionalWordChar.clear();
|
|
|
|
TiXmlElement * pElem = pAutoNode->FirstChildElement(TEXT("Environment"));
|
|
if (pElem)
|
|
{
|
|
const TCHAR * val = 0;
|
|
val = pElem->Attribute(TEXT("ignoreCase"));
|
|
if (val && !lstrcmp(val, TEXT("no"))) {
|
|
_ignoreCase = false;
|
|
_funcCalltip._ignoreCase = false;
|
|
}
|
|
val = pElem->Attribute(TEXT("startFunc"));
|
|
if (val && val[0])
|
|
_funcCalltip._start = val[0];
|
|
val = pElem->Attribute(TEXT("stopFunc"));
|
|
if (val && val[0])
|
|
_funcCalltip._stop = val[0];
|
|
val = pElem->Attribute(TEXT("paramSeparator"));
|
|
if (val && val[0])
|
|
_funcCalltip._param = val[0];
|
|
val = pElem->Attribute(TEXT("terminal"));
|
|
if (val && val[0])
|
|
_funcCalltip._terminal = val[0];
|
|
val = pElem->Attribute(TEXT("additionalWordChar"));
|
|
if (val && val[0])
|
|
_funcCalltip._additionalWordChar = val;
|
|
}
|
|
}
|
|
|
|
if (_funcCompletionActive) {
|
|
_funcCalltip.setLanguageXML(_pXmlKeyword);
|
|
} else {
|
|
_funcCalltip.setLanguageXML(NULL);
|
|
}
|
|
|
|
_keyWords.clear();
|
|
_keyWordArray.clear();
|
|
|
|
if (_funcCompletionActive)
|
|
{
|
|
//Cache the keywords
|
|
//Iterate through all keywords
|
|
TiXmlElement *funcNode = _pXmlKeyword;
|
|
|
|
for (; funcNode; funcNode = funcNode->NextSiblingElement(TEXT("KeyWord")) )
|
|
{
|
|
const TCHAR *name = funcNode->Attribute(TEXT("name"));
|
|
if (name)
|
|
{
|
|
size_t len = lstrlen(name);
|
|
if (len)
|
|
{
|
|
_keyWordArray.push_back(name);
|
|
if (len > _keyWordMaxLen)
|
|
_keyWordMaxLen = len;
|
|
}
|
|
}
|
|
}
|
|
|
|
sort(_keyWordArray.begin(), _keyWordArray.end());
|
|
|
|
for (size_t i = 0, len = _keyWordArray.size(); i < len; ++i)
|
|
{
|
|
_keyWords.append(_keyWordArray[i]);
|
|
_keyWords.append(TEXT(" "));
|
|
}
|
|
}
|
|
return _funcCompletionActive;
|
|
}
|
|
|
|
const TCHAR * AutoCompletion::getApiFileName()
|
|
{
|
|
if (_curLang == L_USER)
|
|
{
|
|
Buffer * currentBuf = _pEditView->getCurrentBuffer();
|
|
if (currentBuf->isUserDefineLangExt())
|
|
{
|
|
return currentBuf->getUserDefineLangName();
|
|
}
|
|
}
|
|
|
|
if (_curLang >= L_EXTERNAL && _curLang < NppParameters::getInstance()->L_END)
|
|
return NppParameters::getInstance()->getELCFromIndex(_curLang - L_EXTERNAL)._name;
|
|
|
|
if (_curLang > L_EXTERNAL)
|
|
_curLang = L_TEXT;
|
|
|
|
return ScintillaEditView::langNames[_curLang].lexerName;
|
|
|
|
}
|