Disregard the version I posted in my previous post. It is bugged and does not work.
This version contains a proper working implementation of a feature in which the current word is boosted to the top if it previously occurred and is otherwise not shown.
from Npp import *
# BEGIN SETTINGS
AUTOCOMPLETION_MIN_LEN = 2 # min length of word to trigger autocompletion
CHARS_TO_MATCH = r'[\w_-]' # characters that can be in "words" (by default most letters, digits, underscores, and dashes)
USE_LANGUAGE_IGNORECASE = True # use the document's lexer language setting for ignoring case
DEFAULT_IGNORECASE = False # should case be ignored if not using language ignorecase?
ENABLED_EXTENSIONS = {
'', # files with no extension yet
'csv',
'txt',
'md',
'xml',
'json',
'tsv',
'log',
'dump',
'yaml',
'yml',
} # only use for files with these extensions
MAX_FILE_SIZE = 200_000 # do not try autocompleting for files with more bytes than this
CURRENT_WORD_ONLY_IF_IN_TEXT = True
# END_SETTINGS
def on_match(m, ctr, ignorecase):
'''increase the count of the current word by 1
if ignorecase, store only the uppercase version of each word'''
word = m.group(0)
if ignorecase:
word = word.upper()
ctr.setdefault(word, 0)
ctr[word] += 1
def getWordRangeUnderCaret():
'''get the start and end of the word under the caret'''
pos = editor.getCurrentPos()
word_start_pos = editor.wordStartPosition(pos, True)
word_end_pos = editor.wordEndPosition(pos, True)
return word_start_pos, word_end_pos
def getExtension(fname):
for ii in range(len(fname) - 1, -1, -1):
if fname[ii] == '.':
break
if ii == 0:
return ''
return fname[ii + 1:]
def onCharInsert(notif):
'''Find all words in the document prefixed by the word under the caret
and show those words for autocompletion sorted by their frequency.
Ignore words with length less than AUTOCOMPLETION_MIN_LEN.
May ignore the case of words based on the lexer language
(e.g., will ignore case in SQL but not in Python)'''
if editor.getLength() > MAX_FILE_SIZE:
return
ext = getExtension(notepad.getCurrentFilename())
if ext not in ENABLED_EXTENSIONS:
return
word_start_pos, word_end_pos = getWordRangeUnderCaret()
word_length = word_end_pos - word_start_pos
word = editor.getRangePointer(word_start_pos, word_length).strip()
if word_length < AUTOCOMPLETION_MIN_LEN:
return
ctr = {}
# anything preceded by a non-word-char and starting with the current word
match_pat = '(?<!{0}){1}({0}*)'.format(CHARS_TO_MATCH, word)
ignorecase = DEFAULT_IGNORECASE
if USE_LANGUAGE_IGNORECASE:
ignorecase = editor.autoCGetIgnoreCase()
else:
editor.autoCSetIgnoreCase(ignorecase)
if ignorecase:
# match case-insenstively if that's the language default
match_pat = '(?i)' + match_pat
editor.research(match_pat, lambda m: on_match(m, ctr, ignorecase))
if CURRENT_WORD_ONLY_IF_IN_TEXT:
if ignorecase:
upword = word.upper()
if upword in ctr:
if ctr[upword] > 1: # word earlier in text, move to front
ctr[upword] = 10_000_000_000
else:
del ctr[upword] # word not in text, remove
elif word in ctr:
if ctr[word] > 1:
ctr[word] = 10_000_000_000
else:
del ctr[word]
autocomp = sorted(ctr, key = lambda x: ctr[x], reverse=True)
autocomp_str = ' '.join(autocomp)
editor.autoCShow(word_length, autocomp_str)
if __name__ == '__main__':
try:
CALLBACK_ADDED
except NameError:
CALLBACK_ADDED = 1
editor.callback(onCharInsert, [SCINTILLANOTIFICATION.CHARADDED])