// This file is part of Notepad++ project // Copyright (C)2003 Don HO // // 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 #include #include #include "AutoCompletion.h" #include "Notepad_plus_msgs.h" using namespace std; static bool isInList(generic_string word, const vector & 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 wordArray; _pEditView->getGenericText(beginChars, bufSize, startPos, curPos); getWordArray(wordArray, beginChars); bool canStop = false; for (size_t i = 0, kwlen = _keyWordArray.size(); i < kwlen; ++i) { if (_keyWordArray[i].compare(0, len, beginChars) == 0) { if (!isInList(_keyWordArray[i], wordArray)) wordArray.push_back(_keyWordArray[i]); canStop = true; } else if (canStop) { // Early out since no more strings will match break; } } 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 != len - 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 & 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(), int(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 (!isInList(w, wordArray)) wordArray.push_back(w); } posFind = _pEditView->searchInTarget(expr.c_str(), static_cast(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 size_t bufSize = MAX_PATH; TCHAR buf[bufSize + 1]; const size_t currentPos = static_cast(_pEditView->execute(SCI_GETCURRENTPOS)); const auto 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 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, bool isHTML) { char prev = (char)_pEditView->execute(SCI_GETCHARAT, caretPos - 2); char prevprev = (char)_pEditView->execute(SCI_GETCHARAT, caretPos - 3); // Closing a tag (i.e. "-->") will be ignored if (prevprev == '-' && prev == '-') return; // "" and "" will be ignored if (prev == '/') return; 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[tagMaxLen]; _pEditView->getText(tagHead, targetStart, targetEnd); if (tagHead[1] == '/') // "" will be ignored return; if (strncmp(tagHead, "