about summary refs log tree commit diff
path: root/saneterm/completion.py
blob: 0eaaa4857168a762a5caa7610556400da8296f41 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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):
        # Preferably, we would expand ~ to $HOME on tab since the
        # application may not expand ~ itself. However, the current
        # completion setup just allows appending characters to the end.
        input = os.path.expanduser(input)

        if input.find("/") != -1:
            base = os.path.dirname(input)
            prefix = os.path.basename(input)

            if not os.path.isabs(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)

        # Ensure that shortest matches are suggested first
        matches.sort(key=len)

        return matches