diff options
author | Sören Tempel <soeren+git@soeren-tempel.net> | 2021-05-31 04:57:44 +0200 |
---|---|---|
committer | Sören Tempel <soeren+git@soeren-tempel.net> | 2021-05-31 04:57:44 +0200 |
commit | 3ad5afdfb265c29ac32766ee2023a77c9a4ad978 (patch) | |
tree | 3e46057041d0f24826a904236a541abf405611cc /saneterm/completion.py | |
parent | 170d4f003f2b530a6e892b33caaf71baa258d754 (diff) |
Preliminary tab completion support
Diffstat (limited to 'saneterm/completion.py')
-rw-r--r-- | saneterm/completion.py | 93 |
1 files changed, 93 insertions, 0 deletions
diff --git a/saneterm/completion.py b/saneterm/completion.py new file mode 100644 index 0000000..888c567 --- /dev/null +++ b/saneterm/completion.py @@ -0,0 +1,93 @@ +import os + +class TabComp(): + "Implements a state machine for tab completions on a Gtk.TextBuffer" + + def __init__(self, buffer, compfn): + self.__buffer = buffer + self.__compfn = compfn + + self._tabcomp_mark = None + self._tabcomp_index = 0 + + def reset(self): + """Invalidates any cached completion results. Should be called + when the user presses any key other than the key configured + for tab completions.""" + + self._tabcomp_mark = None + + def next(self, start): + "Completes the word starting at the given start iterator." + + buffer = self.__buffer + + # Distinguish two cases: + # 1. This is the first time the user pressed tab. + # If so: Determine input between word start and end. + # 2. User is switching through generated matches. + # If so: Determine input between word start and _tabcomp_mark. + if self._tabcomp_mark is None: + # Reset has been called → invalidate completion cache + self._tabcomp_matches = None + self._tabcomp_index = 0 + + end = buffer.get_iter_at_offset(buffer.props.cursor_position) + self._tabcomp_mark = buffer.create_mark(None, end, True) + else: + end = buffer.get_iter_at_mark(self._tabcomp_mark) + + # Extract text, regenerate completion results for + # given text if cache has been invalidated above. + text = buffer.get_text(start, end, True) + if self._tabcomp_matches is None: + self._tabcomp_matches = self.__compfn(text) + self._tabcomp_matches.append("") # original text + c = self._tabcomp_matches[self._tabcomp_index] + + # Insert the matched completion text and delete + # text potentially remaining from older completion. + buffer.insert(end, c) + cursor = buffer.get_iter_at_offset(buffer.props.cursor_position) + buffer.delete(end, cursor) + + # Advance current index in matches and wrap-around. + self._tabcomp_index = (self._tabcomp_index + 1) % len(self._tabcomp_matches) + +class FileName(): + "Provides file name completions relative to a given directory." + + def __init__(self, cwd): + self.__cwd = cwd + + def get_matches(self, input): + if input.find("/") != -1: + base = os.path.dirname(input) + prefix = os.path.basename(input) + + if not os.path.abspath(input): + base = os.path.join(self.__cwd, base) + else: + base = self.__cwd + prefix = input + + return self.__get_matches(base, prefix) + + def __get_matches(self, base, prefix): + if not os.path.isdir(base): + return [] + + matches = [] + with os.scandir(base) as it: + for entry in it: + name = entry.name + if prefix != "" and not name.startswith(prefix): + continue + if entry.is_dir(): + name += "/" + + # Strip prefix from name + name = name[len(prefix):] + matches.append(name) + + return matches |