diff options
author | elfmz <fenix1905@tut.by> | 2021-06-18 00:32:09 +0300 |
---|---|---|
committer | elfmz <fenix1905@tut.by> | 2021-06-18 00:32:09 +0300 |
commit | 8eed813f040cbbd88d7603afbb2af81b44a7191b (patch) | |
tree | d3eedd49f1c03641601886c06433526969498f95 /editorcomp | |
parent | 276661ae975a0258274569e529d069a6c9ff533c (diff) |
Editor Autocomplete plugin improvements: suggest only on character-typing events (dont react on cursor moves), add suggestions toggling ability by Shift+TAB, optimizations (touch #962)
Diffstat (limited to 'editorcomp')
-rw-r--r-- | editorcomp/configs/plug/editorcomp_en.lng | 1 | ||||
-rw-r--r-- | editorcomp/configs/plug/editorcomp_ru.lng | 1 | ||||
-rw-r--r-- | editorcomp/src/Editor.cpp | 232 | ||||
-rw-r--r-- | editorcomp/src/Editor.h | 17 | ||||
-rw-r--r-- | editorcomp/src/editorcomp.cpp | 68 |
5 files changed, 126 insertions, 193 deletions
diff --git a/editorcomp/configs/plug/editorcomp_en.lng b/editorcomp/configs/plug/editorcomp_en.lng index 2ade0137..f049b88c 100644 --- a/editorcomp/configs/plug/editorcomp_en.lng +++ b/editorcomp/configs/plug/editorcomp_en.lng @@ -1,6 +1,7 @@ .Language=English,English "Editor Autocomplete" +"Use Shift+Tab to toggle a suggestion" "Use Tab to confirm a suggestion" "Auto&enable for following file masks:" "Ok" diff --git a/editorcomp/configs/plug/editorcomp_ru.lng b/editorcomp/configs/plug/editorcomp_ru.lng index 43cd3c42..9bf94dd6 100644 --- a/editorcomp/configs/plug/editorcomp_ru.lng +++ b/editorcomp/configs/plug/editorcomp_ru.lng @@ -1,6 +1,7 @@ .Language=Russian,Russian (Русский) "Автодополнение редактора" +"Используйте Shift+Tab для переключения подсказки" "Используйте Tab для применения подсказки" "Автоматически &включать для файлов:" "Сохранить" diff --git a/editorcomp/src/Editor.cpp b/editorcomp/src/Editor.cpp index df1172fc..39b489c1 100644 --- a/editorcomp/src/Editor.cpp +++ b/editorcomp/src/Editor.cpp @@ -1,3 +1,4 @@ +#include <assert.h> #include <plugin.hpp> #include <utils.h> #include "Editor.h" @@ -82,123 +83,60 @@ void Editor::debug(const std::string &msg) { } } +static inline bool IsPrefixed(const std::wstring &str, const std::wstring &prefix) +{ + return str.size() > prefix.size() && wmemcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0; +} + void Editor::updateWords() { const EditorInfo ei = getInfo(); - const EditorGetString ¤tLine = getString(ei.CurLine); - std::wstring currentEditorInfoLine = currentLine.StringText; - - // Nothing changed? - if (ei.TotalLines == previousEditorInfo.TotalLines - && ei.CurLine == previousEditorInfo.CurLine - && currentLine.StringText == previousEditorInfoLine) { - previousEditorInfo = ei; - previousEditorInfoLine = currentEditorInfoLine; - return; - } - // Nothing changed (just cursor moved)? - if (ei.TotalLines == previousEditorInfo.TotalLines - && std::abs(ei.CurLine - previousEditorInfo.CurLine) == 1 && !previousEditorInfoLine.empty()) { - const EditorGetString &previousLine = getString(previousEditorInfo.CurLine); - if (previousLine.StringText == previousEditorInfoLine) { - previousEditorInfo = ei; - previousEditorInfoLine = currentEditorInfoLine; -#if defined(DEBUG_EDITORCOMP) - debug("Nothing changed (just cursor moved)"); -#endif - return; - } - } - - // Minor change in the current line? - if (ei.TotalLines == previousEditorInfo.TotalLines && ei.CurLine == previousEditorInfoLineIndex - && ei.CurLine == previousEditorInfo.CurLine && std::abs(ei.CurPos - previousEditorInfo.CurPos) <= 1) { - int length = std::min(int(previousEditorInfoLine.length()), currentLine.StringLength); - int commonPrefixLength = 0; - for (int i = 0; i < length; i++) - if (previousEditorInfoLine[i] != currentLine.StringText[i]) { - commonPrefixLength = i; - break; - } - bool minorChange = length == int(previousEditorInfoLine.length()); - if (!minorChange) { - int commonSuffixLength = 0; - for (int i = 0; i < length; i++) - if (previousEditorInfoLine[previousEditorInfoLine.length() - i - 1] != - currentLine.StringText[currentLine.StringLength - i - 1]) { - commonSuffixLength = i; - break; - } - if (std::abs(commonPrefixLength + commonSuffixLength - int(previousEditorInfoLine.length())) <= 1) - minorChange = true; - } - if (minorChange) { - std::wstring cur; - words_current_line.clear(); - for (int j = 0; j < currentLine.StringLength; j++) - if (isSeparator(currentLine.StringText[j])) { - if (!cur.empty()) { - if (cur.length() >= 3 && cur.length() <= MAX_WORD_LENGTH_TO_UPDATE_WORDS) - words_current_line.insert(cur); - cur = L""; - } - } else - cur += currentLine.StringText[j]; - if (!cur.empty() && cur.length() >= 3 && cur.length() <= MAX_WORD_LENGTH_TO_UPDATE_WORDS) - words_current_line.insert(cur); - previousEditorInfo = ei; - previousEditorInfoLine = currentEditorInfoLine; -#if defined(DEBUG_EDITORCOMP) - debug("Minor change in the current line"); -#endif - return; - } - } + // Change only in the current line? + const bool only_current_line = (ei.CurLine == previousEditorInfo.CurLine + && ei.TotalLines == previousEditorInfo.TotalLines); // Rebuild words. - words_wo_current_line.clear(); - words_current_line.clear(); - std::set<std::wstring> currentEditorInfoLineWords; + words.prefixed.clear(); + words.current_line.clear(); + if (!only_current_line) { + words.other_lines.clear(); + } + std::wstring cur; for (int i = 0; i < ei.TotalLines; i++) { - if (std::abs(i - ei.CurLine) <= MAX_LINE_DELTA_TO_UPDATE_WORDS) { + if (i == ei.CurLine || (!only_current_line && std::abs(i - ei.CurLine) <= MAX_LINE_DELTA_TO_UPDATE_WORDS)) { const EditorGetString &string = getString(i); - if (i == ei.CurLine) { - currentEditorInfoLine = string.StringText; - } - const wchar_t *line = string.StringText; - int length = string.StringLength; + const int length = string.StringLength; if (length <= MAX_LINE_LENGTH_TO_UPDATE_WORDS) { - std::wstring cur; - for (int j = 0; j < length; j++) - if (isSeparator(line[j])) { - if (!cur.empty()) { - if (cur.length() >= 3 && cur.length() <= MAX_WORD_LENGTH_TO_UPDATE_WORDS) { - if (i == ei.CurLine) - words_current_line.insert(cur); - else - words_wo_current_line.insert(cur); - if (i == ei.CurLine) - currentEditorInfoLineWords.insert(cur); - } - cur = L""; + const wchar_t *line = string.StringText; + for (int j = 0, k = 0; j <= length; j++) { + if (j == length || isSeparator(line[j])) { + if ((size_t)(j - k) > MIN_WORD_LENGTH_TO_SUGGEST && j - k <= MAX_WORD_LENGTH_TO_UPDATE_WORDS) { + cur.assign(&line[k], j - k); + if (i == ei.CurLine) + words.current_line.insert(cur); + else + words.other_lines.insert(cur); } - } else - cur += line[j]; - if (!cur.empty() && cur.length() >= 3 && cur.length() <= MAX_WORD_LENGTH_TO_UPDATE_WORDS) { - if (i == ei.CurLine) - words_current_line.insert(cur); - else - words_wo_current_line.insert(cur); - if (i == ei.CurLine) - currentEditorInfoLineWords.insert(cur); + k = j + 1; + } } } } } + + words.prefixed.reserve(words.current_line.size() + words.other_lines.size()); + + for (const auto &w : words.current_line) if (IsPrefixed(w, words.prefix)) { + words.prefixed.emplace_back(&w); + } + for (const auto &w : words.other_lines) if (IsPrefixed(w, words.prefix)) { + if (words.current_line.find(w) == words.current_line.end()) { + words.prefixed.emplace_back(&w); + } + } + previousEditorInfo = ei; - previousEditorInfoLine = currentEditorInfoLine; - previousEditorInfoLineIndex = ei.CurLine; #if defined(DEBUG_EDITORCOMP) debug("Rebuild words"); @@ -223,30 +161,6 @@ void Editor::undoHighlight(int row, int col, int size) { info.EditorControl(ECTL_ADDCOLOR, &ec); } -// Longest common prefix -static size_t lcp(const std::wstring &a, const std::wstring &b) { - size_t length = std::min(a.length(), b.length()); - for (size_t i = 0; i < length; i++) { - if (a[i] != b[i]) { - return i; - } - } - return length; -} - -static std::wstring -upper_bound(const std::set<std::wstring> &a, const std::set<std::wstring> &b, const std::wstring &s) { - auto i = a.upper_bound(s); - auto j = b.upper_bound(s); - if (i == a.end() && j == b.end()) - return L""; - if (i == a.end()) - return *j; - if (j == b.end()) - return *i; - return std::min(*i, *j); -} - void Editor::putSuggestion() { EditorInfo ei = getInfo(); EditorGetString egs = getString(ei.CurLine); @@ -258,42 +172,28 @@ void Editor::putSuggestion() { from--; from++; - if (ei.CurPos - from >= 2) { + if (ei.CurPos - from >= MIN_WORD_LENGTH_TO_SUGGEST) { std::wstring prefix(egs.StringText + from, egs.StringText + ei.CurPos); - - auto i = upper_bound(words_current_line, words_wo_current_line, prefix); - if (!i.empty() && lcp(prefix, i) == prefix.length()) { - auto fi = i; - size_t cp = i.length(); - - while (true) { - auto pi = i; - i = upper_bound(words_current_line, words_wo_current_line, pi); - if (i.empty()) { - break; - } - - size_t ccp = lcp(pi, i); - if (ccp < prefix.length()) { - break; - } - cp = std::min(cp, ccp); - } - - if (cp > prefix.length()) { - this->suggestion = fi.substr(prefix.length(), cp - prefix.length()); - this->suggestionRow = ei.CurLine; - this->suggestionCol = ei.CurPos; - info.EditorControl(ECTL_INSERTTEXT, (void *) suggestion.c_str()); + if (toggles == 0 || toggles >= words.prefixed.size() || prefix != words.prefix) { + toggles = 0; + words.prefix = prefix; + updateWords(); + } + if (toggles < words.prefixed.size()) { + const auto &fi = *words.prefixed[toggles]; + assert(fi.length() > prefix.length()); + this->suggestion = fi.substr(prefix.length(), fi.size() - prefix.length()); + this->suggestionRow = ei.CurLine; + this->suggestionCol = ei.CurPos; + info.EditorControl(ECTL_INSERTTEXT, (void *) suggestion.c_str()); #if defined(DEBUG_EDITORCOMP) - debug("Added suggestion of length " + std::to_string(suggestion.length()) + - " at the position (" - + std::to_string(ei.CurLine) + ", " + std::to_string(ei.CurPos) + ")"); + debug("Added suggestion of length " + std::to_string(suggestion.length()) + + " at the position (" + + std::to_string(ei.CurLine) + ", " + std::to_string(ei.CurPos) + ")"); #endif - state = DO_COLOR; - info.EditorControl(ECTL_REDRAW, nullptr); - } + state = DO_COLOR; + info.EditorControl(ECTL_REDRAW, nullptr); } } } @@ -321,6 +221,15 @@ State Editor::getState() { return state; } +void Editor::toggleSuggestion() +{ + auto saved_toggles = toggles; + declineSuggestion(); + state = DO_PUT; + toggles = saved_toggles + 1; + info.EditorControl(ECTL_REDRAW, nullptr); +} + void Editor::confirmSuggestion() { if (state == DO_ACTION) { undoHighlight(suggestionRow, suggestionCol, static_cast<int>(suggestion.length())); @@ -337,7 +246,8 @@ void Editor::confirmSuggestion() { #if defined(DEBUG_EDITORCOMP) debug("Confirmed suggestion of length " + std::to_string(suggestion.length()) + ""); #endif - suggestion = L""; + toggles = 0; + suggestion.clear(); suggestionRow = 0; suggestionCol = 0; @@ -390,7 +300,8 @@ void Editor::declineSuggestion() { #if defined(DEBUG_EDITORCOMP) debug("Declined suggestion of length " + std::to_string(suggestion.length()) + ""); #endif - suggestion = L""; + toggles = 0; + suggestion.clear(); suggestionRow = 0; suggestionCol = 0; @@ -404,6 +315,7 @@ void Editor::on() { #if defined(DEBUG_EDITORCOMP) debug("Editor::on"); #endif + toggles = 0; state = DO_PUT; info.EditorControl(ECTL_REDRAW, nullptr); } diff --git a/editorcomp/src/Editor.h b/editorcomp/src/Editor.h index 38939a7c..c54d289c 100644 --- a/editorcomp/src/Editor.h +++ b/editorcomp/src/Editor.h @@ -5,6 +5,7 @@ #include <utility> #include <string> #include <set> +#include <vector> enum State { OFF, DO_PUT, DO_COLOR, DO_ACTION @@ -12,25 +13,33 @@ enum State { class Editor { private: + // TODO: make this contants configurable const static int MAX_LINE_DELTA_TO_UPDATE_WORDS = 100; const static int MAX_LINE_LENGTH_TO_UPDATE_WORDS = 512; const static int MAX_WORD_LENGTH_TO_UPDATE_WORDS = 256; + const static int MIN_WORD_LENGTH_TO_SUGGEST = 2; int id; PluginStartupInfo& info; FarStandardFunctions& fsf; - std::set<std::wstring> words_wo_current_line; - std::set<std::wstring> words_current_line; + struct { + std::wstring prefix; + std::set<std::wstring> current_line, other_lines; + std::vector<const std::wstring *> prefixed; // contains pointers into sets above + } words; + std::wstring suggestion; + size_t toggles = 0; int suggestionRow = 0; int suggestionCol = 0; State state = DO_PUT; bool isEnabled = false; EditorInfo previousEditorInfo = {0}; - std::wstring previousEditorInfoLine; int previousEditorInfoLineIndex = -1; + + void updateWords(); void putSuggestion(); public: @@ -48,13 +57,13 @@ public: void debug(const std::string& msg); - void updateWords(); void doHighlight(int row, int col, int size); void undoHighlight(int row, int col, int size); void processSuggestion(); bool isSeparator(wchar_t c); void on(); + void toggleSuggestion(); void confirmSuggestion(); void declineSuggestion(); diff --git a/editorcomp/src/editorcomp.cpp b/editorcomp/src/editorcomp.cpp index e36ae393..f65d1f91 100644 --- a/editorcomp/src/editorcomp.cpp +++ b/editorcomp/src/editorcomp.cpp @@ -39,28 +39,29 @@ SHAREDSYMBOL int WINAPI ConfigureW(int ItemNumber) { PluginStartupInfo &info = editors->getInfo(); int w = 50; - int h = 10; + int h = 11; struct FarDialogItem fdi[] = { {DI_DOUBLEBOX, 1, 1, w - 2, h - 2, 0, {}, 0, 0, getMsg(info, 0), 0}, {DI_TEXT, 3, 2, 0, h - 1, FALSE, {}, 0, 0, getMsg(info, 1), 0}, - {DI_CHECKBOX, 3, 4, 0, 0, TRUE, {}, 0, 0, getMsg(info, 2), 0}, - {DI_EDIT, 3, 5, w - 4, 0, 0, {}, 0, 0, nullptr, 0}, - {DI_SINGLEBOX, 2, 6, 0, 0, FALSE, {}, 0, 0, L"", 0}, - {DI_BUTTON, 11, 7, 0, 0, FALSE, {}, 0, TRUE, getMsg(info, 3), 0}, - {DI_BUTTON, 26, 7, 0, 0, FALSE, {}, 0, 0, getMsg(info, 4), 0} + {DI_TEXT, 3, 3, 0, h - 1, FALSE, {}, 0, 0, getMsg(info, 2), 0}, + {DI_CHECKBOX, 3, 5, 0, 0, TRUE, {}, 0, 0, getMsg(info, 3), 0}, + {DI_EDIT, 3, 6, w - 4, 0, 0, {}, 0, 0, nullptr, 0}, + {DI_SINGLEBOX, 2, 7, 0, 0, FALSE, {}, 0, 0, L"", 0}, + {DI_BUTTON, 11, 8, 0, 0, FALSE, {}, 0, TRUE, getMsg(info, 4), 0}, + {DI_BUTTON, 26, 8, 0, 0, FALSE, {}, 0, 0, getMsg(info, 5), 0} }; unsigned int size = sizeof(fdi) / sizeof(fdi[0]); - fdi[2].Param.Selected = editors->getAutoEnabling(); - fdi[3].PtrData = (const TCHAR*)editors->getFileMasks().c_str(); + fdi[3].Param.Selected = editors->getAutoEnabling(); + fdi[4].PtrData = (const TCHAR*)editors->getFileMasks().c_str(); HANDLE hDlg = info.DialogInit(info.ModuleNumber, -1, -1, w, h, L"config", fdi, size, 0, 0, nullptr, 0); int runResult = info.DialogRun(hDlg); if (runResult == int(size) - 2) { - editors->setAutoEnabling(info.SendDlgMessage(hDlg, DM_GETCHECK, 2, 0) == BSTATE_CHECKED); - editors->setFileMasks(((const TCHAR *)info.SendDlgMessage(hDlg,DM_GETCONSTTEXTPTR, 3, 0))); + editors->setAutoEnabling(info.SendDlgMessage(hDlg, DM_GETCHECK, 3, 0) == BSTATE_CHECKED); + editors->setFileMasks(((const TCHAR *)info.SendDlgMessage(hDlg,DM_GETCONSTTEXTPTR, 4, 0))); } info.DialogFree(hDlg); @@ -75,26 +76,27 @@ SHAREDSYMBOL HANDLE WINAPI OpenPluginW(int OpenFrom, INT_PTR Item) { PluginStartupInfo &info = editors->getInfo(); int w = 50; - int h = 8; + int h = 10; struct FarDialogItem fdi[] = { {DI_DOUBLEBOX, 1, 1, w - 2, h - 2, 0, {}, 0, 0, getMsg(info, 0), 0}, {DI_TEXT, 3, 2, 0, h - 1, FALSE, {}, 0, 0, getMsg(info, 1), 0}, - {DI_CHECKBOX, 3, 4, 0, 0, TRUE, {}, 0, 0, getMsg(info, 5), 0}, - {DI_SINGLEBOX, 2, 5, 0, 0, FALSE, {}, 0, 0, L"", 0}, - {DI_BUTTON, 11, 6, 0, 0, FALSE, {}, 0, TRUE, getMsg(info, 3), 0}, - {DI_BUTTON, 26, 6, 0, 0, FALSE, {}, 0, 0, getMsg(info, 4), 0} + {DI_TEXT, 3, 3, 0, h - 1, FALSE, {}, 0, 0, getMsg(info, 2), 0}, + {DI_CHECKBOX, 3, 5, 0, 0, TRUE, {}, 0, 0, getMsg(info, 6), 0}, + {DI_SINGLEBOX, 2, 6, 0, 0, FALSE, {}, 0, 0, L"", 0}, + {DI_BUTTON, 11, 7, 0, 0, FALSE, {}, 0, TRUE, getMsg(info, 4), 0}, + {DI_BUTTON, 26, 7, 0, 0, FALSE, {}, 0, 0, getMsg(info, 5), 0} }; bool active = editor->getEnabled(); unsigned int size = sizeof(fdi) / sizeof(fdi[0]); - fdi[2].Param.Selected = active; + fdi[3].Param.Selected = active; HANDLE hDlg = info.DialogInit(info.ModuleNumber, -1, -1, w, h, L"config", fdi, size, 0, 0, nullptr, 0); int runResult = info.DialogRun(hDlg); if (runResult == int(size) - 2) { - bool checked = (info.SendDlgMessage(hDlg, DM_GETCHECK, 2, 0) == BSTATE_CHECKED); + bool checked = (info.SendDlgMessage(hDlg, DM_GETCHECK, 3, 0) == BSTATE_CHECKED); if (checked != active) { editor->setEnabled(checked); } @@ -116,7 +118,6 @@ SHAREDSYMBOL int WINAPI ProcessEditorEventW(int Event, void *Param) { // } else { if (Event == EE_REDRAW) { - editor->updateWords(); editor->processSuggestion(); } else if (Event == EE_SAVE) { //? @@ -140,23 +141,28 @@ SHAREDSYMBOL int WINAPI ProcessEditorInputW(const INPUT_RECORD *ir) { return 0; // Is regular key event? - if (ir->EventType == KEY_EVENT && (ir->Event.KeyEvent.dwControlKeyState & BAD_MODIFIERS) == 0 - && ir->Event.KeyEvent.wVirtualScanCode == 0 && ir->Event.KeyEvent.bKeyDown) { + if (ir->EventType == KEY_EVENT && ir->Event.KeyEvent.wVirtualScanCode == 0 && ir->Event.KeyEvent.bKeyDown) { // Tab ? - if (editor->getState() == DO_ACTION && ir->Event.KeyEvent.wVirtualKeyCode == VK_TAB) { - editor->confirmSuggestion(); - return 1; + if (ir->Event.KeyEvent.wVirtualKeyCode == VK_TAB && editor->getState() == DO_ACTION) { + switch (ir->Event.KeyEvent.dwControlKeyState & BAD_MODIFIERS) { + case 0: + editor->confirmSuggestion(); + return 1; + + case SHIFT_PRESSED: + editor->toggleSuggestion(); + return 1; + } } // Escape ? - if (editor->getState() == DO_ACTION && - (ir->Event.KeyEvent.wVirtualKeyCode == 27 || ir->Event.KeyEvent.wVirtualKeyCode == 46)) { + if (editor->getState() == DO_ACTION && (ir->Event.KeyEvent.dwControlKeyState & BAD_MODIFIERS) == 0 + && (ir->Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE || ir->Event.KeyEvent.wVirtualKeyCode == VK_DELETE)) { bool has_suggestion = editor->getSuggestionLength() > 0; editor->declineSuggestion(); return has_suggestion; } - } if (ir->EventType == KEY_EVENT && ir->Event.KeyEvent.bKeyDown @@ -169,12 +175,16 @@ SHAREDSYMBOL int WINAPI ProcessEditorInputW(const INPUT_RECORD *ir) { if (ir->EventType == MOUSE_EVENT && ir->Event.MouseEvent.dwButtonState) editor->declineSuggestion(); - // Is regular key event? + // Is character-typing key event? if (ir->EventType == KEY_EVENT && (ir->Event.KeyEvent.dwControlKeyState & BAD_MODIFIERS) == 0 && ir->Event.KeyEvent.wVirtualScanCode == 0 && !ir->Event.KeyEvent.bKeyDown - && ir->Event.KeyEvent.wVirtualKeyCode != 9 && ir->Event.KeyEvent.wVirtualKeyCode != 8 - && ir->Event.KeyEvent.wVirtualKeyCode != 27 && ir->Event.KeyEvent.wVirtualKeyCode != 46) + && ir->Event.KeyEvent.wVirtualKeyCode != VK_TAB && ir->Event.KeyEvent.wVirtualKeyCode != VK_BACK + && ir->Event.KeyEvent.wVirtualKeyCode != VK_ESCAPE && ir->Event.KeyEvent.wVirtualKeyCode != VK_DELETE + && ir->Event.KeyEvent.wVirtualKeyCode != VK_UP && ir->Event.KeyEvent.wVirtualKeyCode != VK_DOWN + && ir->Event.KeyEvent.wVirtualKeyCode != VK_LEFT && ir->Event.KeyEvent.wVirtualKeyCode != VK_RIGHT + && ir->Event.KeyEvent.uChar.UnicodeChar != 0) { editors->getEditor()->on(); + } return 0; } |