Skip to content

Commit 3f2e705

Browse files
igordsmJeremy Wootten
andauthored
Rewrite word completion to play nice with other completion providers. (#1125)
* Rewrite word completion to play nice with other completion providers. * lint * lint 2 * Fix problem with extra space in words. * remove print * Avoid incluing delimiters in the prefix tree. * Use is_empty * Fix is_empty * Remove hyphen and underscore from delimiters Co-authored-by: Jeremy Wootten <jeremy@elementaryos.org>
1 parent 70bccb9 commit 3f2e705

File tree

5 files changed

+188
-128
lines changed

5 files changed

+188
-128
lines changed

plugins/word-completion/completion-provider.vala

Lines changed: 30 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider,
2828
private Gtk.TextView? view;
2929
private Gtk.TextBuffer? buffer;
3030
private Euclide.Completion.Parser parser;
31-
private bool proposals_found = false;
3231
private Gtk.TextMark completion_end_mark;
3332
private Gtk.TextMark completion_start_mark;
3433

@@ -53,40 +52,36 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider,
5352
}
5453

5554
public bool match (Gtk.SourceCompletionContext context) {
56-
return true;
55+
Gtk.TextIter start, end;
56+
buffer.get_iter_at_offset (out end, buffer.cursor_position);
57+
start = end.copy ();
58+
start.backward_word_start ();
59+
string text = buffer.get_text (start, end, true);
60+
61+
return parser.match (text);
5762
}
5863

5964
public void populate (Gtk.SourceCompletionContext context) {
6065
/*Store current insertion point for use in activate_proposal */
6166
GLib.List<Gtk.SourceCompletionItem>? file_props;
6267
bool no_minimum = (context.get_activation () == Gtk.SourceCompletionActivation.USER_REQUESTED);
63-
proposals_found = get_proposals (out file_props, no_minimum);
64-
65-
if (proposals_found)
66-
context.add_proposals (this, file_props, true);
67-
68-
/* Signal to plugin whether proposals are available
69-
* If none, the completion will be active but not visible */
70-
can_propose (proposals_found);
68+
get_proposals (out file_props, no_minimum);
69+
context.add_proposals (this, file_props, true);
7170
}
7271

7372
public bool activate_proposal (Gtk.SourceCompletionProposal proposal, Gtk.TextIter iter) {
74-
if (proposals_found) {
75-
/* Count backward from completion_mark instead of iter
76-
* (avoids wrong insertion if the user is typing fast) */
77-
Gtk.TextIter start;
78-
Gtk.TextIter end;
79-
Gtk.TextMark mark;
73+
Gtk.TextIter start;
74+
Gtk.TextIter end;
75+
Gtk.TextMark mark;
8076

81-
mark = buffer.get_mark (COMPLETION_END_MARK_NAME);
82-
buffer.get_iter_at_mark (out end, mark);
77+
mark = buffer.get_mark (COMPLETION_END_MARK_NAME);
78+
buffer.get_iter_at_mark (out end, mark);
8379

84-
mark = buffer.get_mark (COMPLETION_START_MARK_NAME);
85-
buffer.get_iter_at_mark (out start, mark);
80+
mark = buffer.get_mark (COMPLETION_START_MARK_NAME);
81+
buffer.get_iter_at_mark (out start, mark);
8682

87-
buffer.@delete (ref start, ref end);
88-
buffer.insert (ref start, proposal.get_text (), proposal.get_text ().length);
89-
}
83+
buffer.@delete (ref start, ref end);
84+
buffer.insert (ref start, proposal.get_text (), proposal.get_text ().length);
9085
return true;
9186
}
9287

@@ -95,11 +90,6 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider,
9590
Gtk.SourceCompletionActivation.USER_REQUESTED;
9691
}
9792

98-
public unowned Gtk.Widget? get_info_widget (Gtk.SourceCompletionProposal proposal) {
99-
/* As no additional info is provided no widget is needed */
100-
return null;
101-
}
102-
10393
public int get_interactive_delay () {
10494
return 0;
10595
}
@@ -116,14 +106,8 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider,
116106
return true;
117107
}
118108

119-
public void update_info (Gtk.SourceCompletionProposal proposal, Gtk.SourceCompletionInfo info) {
120-
/* No additional info provided on proposals */
121-
return;
122-
}
123-
124109
private bool get_proposals (out GLib.List<Gtk.SourceCompletionItem>? props, bool no_minimum) {
125110
string to_find = "";
126-
Gtk.TextIter iter;
127111
Gtk.TextBuffer temp_buffer = buffer;
128112
props = null;
129113

@@ -133,38 +117,28 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider,
133117
to_find = temp_buffer.get_text (start, end, true);
134118

135119
if (to_find.length == 0) {
136-
/* Find start of current word */
137-
temp_buffer.get_iter_at_offset (out iter, buffer.cursor_position);
138-
/* Mark current insertion point as end point for use in activate proposal */
139-
buffer.move_mark_by_name (COMPLETION_END_MARK_NAME, iter);
140-
/* TODO - Use iter.backward_word_start? */
141-
iter.backward_find_char ((c) => {
142-
bool valid = parser.is_delimiter (c);
143-
if (!valid)
144-
to_find += c.to_string ();
145-
146-
return valid;
147-
}, null);
148-
iter.forward_cursor_position ();
149-
/* Mark start of delimited text as start point for use in activate proposal */
150-
buffer.move_mark_by_name (COMPLETION_START_MARK_NAME, iter);
151-
to_find = to_find.reverse ();
152-
} else {
153-
/* mark start and end of the selection */
154-
buffer.move_mark_by_name (COMPLETION_END_MARK_NAME, end);
155-
buffer.move_mark_by_name (COMPLETION_START_MARK_NAME, start);
120+
temp_buffer.get_iter_at_offset (out end, buffer.cursor_position);
121+
122+
start = end;
123+
start.backward_word_start ();
124+
125+
to_find = buffer.get_text (start, end, false);
156126
}
157127

128+
buffer.move_mark_by_name (COMPLETION_END_MARK_NAME, end);
129+
buffer.move_mark_by_name (COMPLETION_START_MARK_NAME, start);
130+
131+
158132
/* There is no minimum length of word to find if the user requested a completion */
159133
if (no_minimum || to_find.length >= Euclide.Completion.Parser.MINIMUM_WORD_LENGTH) {
160134
/* Get proposals, if any */
161-
Gee.TreeSet<string> prop_word_list;
135+
List<string> prop_word_list;
162136
if (parser.get_for_word (to_find, out prop_word_list)) {
163137
foreach (var word in prop_word_list) {
164138
var item = new Gtk.SourceCompletionItem ();
165139
item.label = word;
166140
item.text = word;
167-
props.prepend (item);
141+
props.append (item);
168142
}
169143

170144
return true;

plugins/word-completion/engine.vala

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,74 +22,58 @@ public class Euclide.Completion.Parser : GLib.Object {
2222
public const int MINIMUM_WORD_LENGTH = 1;
2323
public const int MAX_TOKENS = 1000000;
2424

25-
public const string DELIMITERS = " .,;:?{}[]()0123456789+-=&|-<>*\\/\n\t\'\"";
26-
public bool is_delimiter (unichar c) {
25+
private Scratch.Plugins.PrefixTree prefix_tree;
26+
27+
public const string DELIMITERS = " .,;:?{}[]()0123456789+=&|<>*\\/\r\n\t\'\"`";
28+
public static bool is_delimiter (unichar c) {
2729
return DELIMITERS.index_of_char (c) >= 0;
2830
}
2931

30-
public Gee.HashMap<Gtk.TextView,Gee.ArrayList<string>> text_view_words;
32+
public Gee.HashMap<Gtk.TextView,Scratch.Plugins.PrefixTree> text_view_words;
3133
public bool parsing_cancelled = false;
3234

33-
private Gee.ArrayList<string> words;
34-
private string last_word = "";
35-
3635
public Parser () {
37-
text_view_words = new Gee.HashMap<Gtk.TextView,Gee.ArrayList<string>> ();
36+
text_view_words = new Gee.HashMap<Gtk.TextView,Scratch.Plugins.PrefixTree> ();
37+
prefix_tree = new Scratch.Plugins.PrefixTree ();
3838
}
3939

40-
public void add_last_word () {
41-
add_word (last_word);
40+
public bool match (string to_find) {
41+
return prefix_tree.find_prefix (to_find);
4242
}
4343

44-
public bool get_for_word (string to_find, out Gee.TreeSet<string> list) {
45-
uint length = to_find.length;
46-
list = new Gee.TreeSet<string> ();
47-
last_word = to_find;
48-
if (words != null) {
49-
lock (words) {
50-
foreach (var word in words) {
51-
if (word.length > length && word.slice (0, length) == to_find) {
52-
list.add (word);
53-
}
54-
}
55-
}
56-
}
57-
58-
return !list.is_empty;
44+
public bool get_for_word (string to_find, out List<string> list) {
45+
list = prefix_tree.get_all_matches (to_find);
46+
return list.first () != null;
5947
}
6048

6149
public void rebuild_word_list (Gtk.TextView view) {
62-
lock (words) {
63-
words.clear ();
64-
}
50+
prefix_tree.clear ();
6551
parse_text_view (view);
6652
}
6753

6854
public void parse_text_view (Gtk.TextView view) {
6955
/* If this view has already been parsed, restore the word list */
70-
lock (words) {
56+
lock (prefix_tree) {
7157
if (text_view_words.has_key (view)) {
72-
words = text_view_words.@get (view);
58+
prefix_tree = text_view_words.@get (view);
7359
} else {
74-
/* Else create a new word list and parse the buffer text */
75-
words = new Gee.ArrayList<string> ();
60+
/* Else create a new word list and parse the buffer text */
61+
prefix_tree = new Scratch.Plugins.PrefixTree ();
7662
}
7763
}
7864

7965
if (view.buffer.text.length > 0) {
8066
parse_string (view.buffer.text);
81-
text_view_words.@set (view, words);
67+
text_view_words.@set (view, prefix_tree);
8268
}
8369
}
8470

85-
private void add_word (string word) {
71+
public void add_word (string word) {
8672
if (word.length < MINIMUM_WORD_LENGTH)
8773
return;
8874

89-
if (!(word in words)) {
90-
lock (words) {
91-
words.add (word);
92-
}
75+
lock (prefix_tree) {
76+
prefix_tree.insert (word);
9377
}
9478
}
9579

plugins/word-completion/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module_name = 'word-completion'
22

33
module_files = [
4+
'prefix-tree.vala',
45
'completion-provider.vala',
56
'engine.vala',
67
'plugin.vala'

plugins/word-completion/plugin.vala

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {
2828

2929
private MainWindow main_window;
3030
private Scratch.Services.Interface plugins;
31+
private bool completion_in_progress = false;
3132

3233
private const uint [] ACTIVATE_KEYS = {
3334
Gdk.Key.Return,
@@ -41,7 +42,6 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {
4142
private const uint USER_REQUESTED_KEY = Gdk.Key.backslash;
4243

4344
private uint timeout_id = 0;
44-
private bool completion_visible = false;
4545

4646
public void activate () {
4747
plugins = (Scratch.Services.Interface) object;
@@ -77,20 +77,24 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {
7777
current_document = doc;
7878
current_view = doc.source_view;
7979
current_view.key_press_event.connect (on_key_press);
80-
current_view.completion.show.connect (on_completion_shown);
81-
current_view.completion.hide.connect (on_completion_hidden);
80+
current_view.completion.show.connect (() => {
81+
completion_in_progress = true;
82+
});
83+
current_view.completion.hide.connect (() => {
84+
completion_in_progress = false;
85+
});
86+
8287

8388
if (text_view_list.find (current_view) == null)
8489
text_view_list.append (current_view);
8590

8691
var comp_provider = new Scratch.Plugins.CompletionProvider (this);
8792
comp_provider.priority = 1;
8893
comp_provider.name = provider_name_from_document (doc);
89-
comp_provider.can_propose.connect (on_propose);
9094

9195
try {
9296
current_view.completion.add_provider (comp_provider);
93-
current_view.completion.show_headers = false;
97+
current_view.completion.show_headers = true;
9498
current_view.completion.show_icons = true;
9599
/* Wait a bit to allow text to load then run parser*/
96100
timeout_id = Timeout.add (1000, on_timeout_update);
@@ -134,49 +138,33 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {
134138
parser.rebuild_word_list (current_view);
135139
current_view.show_completion ();
136140
return true;
137-
} else
138-
return false;
141+
}
139142
}
140143

141-
bool activating = kv in ACTIVATE_KEYS;
144+
if (!completion_in_progress && parser.is_delimiter (uc) &&
145+
(uc.isprint () || uc.isspace ())) {
142146

143-
if (completion_visible && activating) {
144-
current_view.completion.activate_proposal ();
145-
parser.add_last_word ();
146-
return true;
147-
}
147+
var buffer = current_view.buffer;
148+
var mark = buffer.get_insert ();
149+
Gtk.TextIter cursor_iter;
150+
buffer.get_iter_at_mark (out cursor_iter, mark);
151+
152+
var word_start = cursor_iter;
153+
word_start.backward_word_start ();
148154

149-
if (activating || (uc.isprint () && parser.is_delimiter (uc) )) {
150-
parser.add_last_word ();
151-
current_view.completion.hide ();
155+
string word = buffer.get_text (word_start, cursor_iter, false);
156+
parser.add_word (word);
152157
}
153158

154159
return false;
155160
}
156161

157-
private void on_completion_shown () {
158-
completion_visible = true;
159-
}
160-
161-
private void on_completion_hidden () {
162-
completion_visible = false;
163-
}
164-
165-
private void on_propose (bool can_propose) {
166-
if (!can_propose)
167-
current_view.completion.hide ();
168-
169-
completion_visible = can_propose;
170-
}
171-
172162
private string provider_name_from_document (Scratch.Services.Document doc) {
173163
return _("%s - Word Completion").printf (doc.get_basename ());
174164
}
175165

176166
private void cleanup (Gtk.SourceView view) {
177167
current_view.key_press_event.disconnect (on_key_press);
178-
current_view.completion.show.disconnect (on_completion_shown);
179-
current_view.completion.hide.disconnect (on_completion_hidden);
180168

181169
current_view.completion.get_providers ().foreach ((p) => {
182170
try {
@@ -189,8 +177,6 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {
189177
warning (e.message);
190178
}
191179
});
192-
193-
completion_visible = false;
194180
}
195181
}
196182

0 commit comments

Comments
 (0)