From 73cfa3785e8fc04c8ac75f2e2d9afa903dd29581 Mon Sep 17 00:00:00 2001 From: Don Ho Date: Thu, 10 Oct 2013 23:05:50 +0000 Subject: [PATCH] [NEW_FEATURE] (Author: Andreas Jonsson) Add auto-completion for absolute path feature. git-svn-id: svn://svn.tuxfamily.org/svnroot/notepadplus/repository/trunk@1129 f5eea248-9336-0410-98b8-ebc06183d4e3 --- PowerEditor/src/Notepad_plus.cpp | 7 + PowerEditor/src/Notepad_plus.h | 1 + PowerEditor/src/Notepad_plus.rc | 1 + PowerEditor/src/NppCommands.cpp | 4 + PowerEditor/src/Parameters.cpp | 1 + .../src/ScitillaComponent/AutoCompletion.cpp | 158 ++++++++++++++++++ .../src/ScitillaComponent/AutoCompletion.h | 4 +- PowerEditor/src/menuCmdID.h | 1 + 8 files changed, 176 insertions(+), 1 deletion(-) diff --git a/PowerEditor/src/Notepad_plus.cpp b/PowerEditor/src/Notepad_plus.cpp index b6cce307d..3bc3fc46e 100644 --- a/PowerEditor/src/Notepad_plus.cpp +++ b/PowerEditor/src/Notepad_plus.cpp @@ -3385,6 +3385,13 @@ void Notepad_plus::showAutoComp() autoC->showAutoComplete(); } +void Notepad_plus::showPathCompletion() +{ + bool isFromPrimary = _pEditView == &_mainEditView; + AutoCompletion * autoC = isFromPrimary?&_autoCompleteMain:&_autoCompleteSub; + autoC->showPathCompletion(); +} + void Notepad_plus::autoCompFromCurrentFile(bool autoInsert) { bool isFromPrimary = _pEditView == &_mainEditView; diff --git a/PowerEditor/src/Notepad_plus.h b/PowerEditor/src/Notepad_plus.h index ad8583e2e..dbc30b5f2 100644 --- a/PowerEditor/src/Notepad_plus.h +++ b/PowerEditor/src/Notepad_plus.h @@ -583,6 +583,7 @@ private: void showAutoComp(); void autoCompFromCurrentFile(bool autoInsert = true); void showFunctionComp(); + void showPathCompletion(); //void changeStyleCtrlsLang(HWND hDlg, int *idArray, const char **translatedText); bool replaceInOpenedFiles(); diff --git a/PowerEditor/src/Notepad_plus.rc b/PowerEditor/src/Notepad_plus.rc index dc70dcb91..af573207e 100644 --- a/PowerEditor/src/Notepad_plus.rc +++ b/PowerEditor/src/Notepad_plus.rc @@ -279,6 +279,7 @@ BEGIN MENUITEM "Function Completion", IDM_EDIT_AUTOCOMPLETE MENUITEM "Word Completion", IDM_EDIT_AUTOCOMPLETE_CURRENTFILE MENUITEM "Function Parameters Hint", IDM_EDIT_FUNCCALLTIP + MENUITEM "Path Completion", IDM_EDIT_AUTOCOMPLETE_PATH END POPUP "EOL Conversion" BEGIN diff --git a/PowerEditor/src/NppCommands.cpp b/PowerEditor/src/NppCommands.cpp index 9911f8a6c..751134532 100644 --- a/PowerEditor/src/NppCommands.cpp +++ b/PowerEditor/src/NppCommands.cpp @@ -2258,6 +2258,10 @@ void Notepad_plus::command(int id) autoCompFromCurrentFile(); break; + case IDM_EDIT_AUTOCOMPLETE_PATH : + showPathCompletion(); + break; + case IDM_EDIT_FUNCCALLTIP : showFunctionComp(); break; diff --git a/PowerEditor/src/Parameters.cpp b/PowerEditor/src/Parameters.cpp index a9f0e58c0..a26f635b4 100644 --- a/PowerEditor/src/Parameters.cpp +++ b/PowerEditor/src/Parameters.cpp @@ -118,6 +118,7 @@ WinMenuKeyDefinition winKeyDefs[] = { {VK_K, IDM_EDIT_BLOCK_UNCOMMENT, true, false, true, NULL}, {VK_Q, IDM_EDIT_STREAM_COMMENT, true, false, true, NULL}, {VK_SPACE, IDM_EDIT_AUTOCOMPLETE, true, false, false, NULL}, + {VK_SPACE, IDM_EDIT_AUTOCOMPLETE_PATH, true, true, false, NULL}, {VK_RETURN, IDM_EDIT_AUTOCOMPLETE_CURRENTFILE, true, false, false, NULL}, {VK_SPACE, IDM_EDIT_FUNCCALLTIP, true, false, true, NULL}, {VK_R, IDM_EDIT_RTL, true, true, false, NULL}, diff --git a/PowerEditor/src/ScitillaComponent/AutoCompletion.cpp b/PowerEditor/src/ScitillaComponent/AutoCompletion.cpp index c26ff74d9..e081d45ef 100644 --- a/PowerEditor/src/ScitillaComponent/AutoCompletion.cpp +++ b/PowerEditor/src/ScitillaComponent/AutoCompletion.cpp @@ -30,6 +30,7 @@ #include "AutoCompletion.h" #include "Notepad_plus_msgs.h" +#include static bool isInList(generic_string word, const vector & wordArray) { @@ -76,6 +77,163 @@ bool AutoCompletion::showAutoComplete() { return true; } +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 == '"' || 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 + { + locale loc; + 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, 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()); + _activeCompletion = CompletionPath; + return; +} + bool AutoCompletion::showWordComplete(bool autoInsert) { int curPos = int(_pEditView->execute(SCI_GETCURRENTPOS)); diff --git a/PowerEditor/src/ScitillaComponent/AutoCompletion.h b/PowerEditor/src/ScitillaComponent/AutoCompletion.h index 996d4946f..7994eb65c 100644 --- a/PowerEditor/src/ScitillaComponent/AutoCompletion.h +++ b/PowerEditor/src/ScitillaComponent/AutoCompletion.h @@ -41,7 +41,7 @@ class ScintillaEditView; class AutoCompletion { public: - enum ActiveCompletion {CompletionNone = 0, CompletionAuto, CompletionWord, CompletionFunc}; + enum ActiveCompletion {CompletionNone = 0, CompletionAuto, CompletionWord, CompletionFunc, CompletionPath}; AutoCompletion(ScintillaEditView * pEditView) : _funcCompletionActive(false), _pEditView(pEditView), _funcCalltip(pEditView), _curLang(L_TEXT), _pXmlFile(NULL), _activeCompletion(CompletionNone), @@ -62,6 +62,8 @@ public: bool showWordComplete(bool autoInsert); //autoInsert true if completion should fill in the word on a single match //Parameter display from the list bool showFunctionComplete(); + // Autocomplete from path. + void showPathCompletion(); void insertMatchedChars(int character, const MatchedPairConf & matchedPairConf); void update(int character); diff --git a/PowerEditor/src/menuCmdID.h b/PowerEditor/src/menuCmdID.h index 351510b8c..5a2308250 100644 --- a/PowerEditor/src/menuCmdID.h +++ b/PowerEditor/src/menuCmdID.h @@ -129,6 +129,7 @@ #define IDM_EDIT_AUTOCOMPLETE (50000 + 0) #define IDM_EDIT_AUTOCOMPLETE_CURRENTFILE (50000 + 1) #define IDM_EDIT_FUNCCALLTIP (50000 + 2) + #define IDM_EDIT_AUTOCOMPLETE_PATH (50000 + 6) //Belong to MENU FILE #define IDM_OPEN_ALL_RECENT_FILE (IDM_EDIT + 40)