Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 30 additions & 56 deletions plugins/word-completion/completion-provider.vala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider,
private Gtk.TextView? view;
private Gtk.TextBuffer? buffer;
private Euclide.Completion.Parser parser;
private bool proposals_found = false;
private Gtk.TextMark completion_end_mark;
private Gtk.TextMark completion_start_mark;

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

public bool match (Gtk.SourceCompletionContext context) {
return true;
Gtk.TextIter start, end;
buffer.get_iter_at_offset (out end, buffer.cursor_position);
start = end.copy ();
start.backward_word_start ();
string text = buffer.get_text (start, end, true);

return parser.match (text);
}

public void populate (Gtk.SourceCompletionContext context) {
/*Store current insertion point for use in activate_proposal */
GLib.List<Gtk.SourceCompletionItem>? file_props;
bool no_minimum = (context.get_activation () == Gtk.SourceCompletionActivation.USER_REQUESTED);
proposals_found = get_proposals (out file_props, no_minimum);

if (proposals_found)
context.add_proposals (this, file_props, true);

/* Signal to plugin whether proposals are available
* If none, the completion will be active but not visible */
can_propose (proposals_found);
get_proposals (out file_props, no_minimum);
context.add_proposals (this, file_props, true);
}

public bool activate_proposal (Gtk.SourceCompletionProposal proposal, Gtk.TextIter iter) {
if (proposals_found) {
/* Count backward from completion_mark instead of iter
* (avoids wrong insertion if the user is typing fast) */
Gtk.TextIter start;
Gtk.TextIter end;
Gtk.TextMark mark;
Gtk.TextIter start;
Gtk.TextIter end;
Gtk.TextMark mark;

mark = buffer.get_mark (COMPLETION_END_MARK_NAME);
buffer.get_iter_at_mark (out end, mark);
mark = buffer.get_mark (COMPLETION_END_MARK_NAME);
buffer.get_iter_at_mark (out end, mark);

mark = buffer.get_mark (COMPLETION_START_MARK_NAME);
buffer.get_iter_at_mark (out start, mark);
mark = buffer.get_mark (COMPLETION_START_MARK_NAME);
buffer.get_iter_at_mark (out start, mark);

buffer.@delete (ref start, ref end);
buffer.insert (ref start, proposal.get_text (), proposal.get_text ().length);
}
buffer.@delete (ref start, ref end);
buffer.insert (ref start, proposal.get_text (), proposal.get_text ().length);
return true;
}

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

public unowned Gtk.Widget? get_info_widget (Gtk.SourceCompletionProposal proposal) {
/* As no additional info is provided no widget is needed */
return null;
}

public int get_interactive_delay () {
return 0;
}
Expand All @@ -116,14 +106,8 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider,
return true;
}

public void update_info (Gtk.SourceCompletionProposal proposal, Gtk.SourceCompletionInfo info) {
/* No additional info provided on proposals */
return;
}

private bool get_proposals (out GLib.List<Gtk.SourceCompletionItem>? props, bool no_minimum) {
string to_find = "";
Gtk.TextIter iter;
Gtk.TextBuffer temp_buffer = buffer;
props = null;

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

if (to_find.length == 0) {
/* Find start of current word */
temp_buffer.get_iter_at_offset (out iter, buffer.cursor_position);
/* Mark current insertion point as end point for use in activate proposal */
buffer.move_mark_by_name (COMPLETION_END_MARK_NAME, iter);
/* TODO - Use iter.backward_word_start? */
iter.backward_find_char ((c) => {
bool valid = parser.is_delimiter (c);
if (!valid)
to_find += c.to_string ();

return valid;
}, null);
iter.forward_cursor_position ();
/* Mark start of delimited text as start point for use in activate proposal */
buffer.move_mark_by_name (COMPLETION_START_MARK_NAME, iter);
to_find = to_find.reverse ();
} else {
/* mark start and end of the selection */
buffer.move_mark_by_name (COMPLETION_END_MARK_NAME, end);
buffer.move_mark_by_name (COMPLETION_START_MARK_NAME, start);
temp_buffer.get_iter_at_offset (out end, buffer.cursor_position);

start = end;
start.backward_word_start ();

to_find = buffer.get_text (start, end, false);
}

buffer.move_mark_by_name (COMPLETION_END_MARK_NAME, end);
buffer.move_mark_by_name (COMPLETION_START_MARK_NAME, start);


/* There is no minimum length of word to find if the user requested a completion */
if (no_minimum || to_find.length >= Euclide.Completion.Parser.MINIMUM_WORD_LENGTH) {
/* Get proposals, if any */
Gee.TreeSet<string> prop_word_list;
List<string> prop_word_list;
if (parser.get_for_word (to_find, out prop_word_list)) {
foreach (var word in prop_word_list) {
var item = new Gtk.SourceCompletionItem ();
item.label = word;
item.text = word;
props.prepend (item);
props.append (item);
}

return true;
Expand Down
58 changes: 21 additions & 37 deletions plugins/word-completion/engine.vala
Original file line number Diff line number Diff line change
Expand Up @@ -22,74 +22,58 @@ public class Euclide.Completion.Parser : GLib.Object {
public const int MINIMUM_WORD_LENGTH = 1;
public const int MAX_TOKENS = 1000000;

public const string DELIMITERS = " .,;:?{}[]()0123456789+-=&|-<>*\\/\n\t\'\"";
public bool is_delimiter (unichar c) {
private Scratch.Plugins.PrefixTree prefix_tree;

public const string DELIMITERS = " .,;:?{}[]()0123456789+=&|<>*\\/\r\n\t\'\"`";
public static bool is_delimiter (unichar c) {
return DELIMITERS.index_of_char (c) >= 0;
}

public Gee.HashMap<Gtk.TextView,Gee.ArrayList<string>> text_view_words;
public Gee.HashMap<Gtk.TextView,Scratch.Plugins.PrefixTree> text_view_words;
public bool parsing_cancelled = false;

private Gee.ArrayList<string> words;
private string last_word = "";

public Parser () {
text_view_words = new Gee.HashMap<Gtk.TextView,Gee.ArrayList<string>> ();
text_view_words = new Gee.HashMap<Gtk.TextView,Scratch.Plugins.PrefixTree> ();
prefix_tree = new Scratch.Plugins.PrefixTree ();
}

public void add_last_word () {
add_word (last_word);
public bool match (string to_find) {
return prefix_tree.find_prefix (to_find);
}

public bool get_for_word (string to_find, out Gee.TreeSet<string> list) {
uint length = to_find.length;
list = new Gee.TreeSet<string> ();
last_word = to_find;
if (words != null) {
lock (words) {
foreach (var word in words) {
if (word.length > length && word.slice (0, length) == to_find) {
list.add (word);
}
}
}
}

return !list.is_empty;
public bool get_for_word (string to_find, out List<string> list) {
list = prefix_tree.get_all_matches (to_find);
return list.first () != null;
}

public void rebuild_word_list (Gtk.TextView view) {
lock (words) {
words.clear ();
}
prefix_tree.clear ();
parse_text_view (view);
}

public void parse_text_view (Gtk.TextView view) {
/* If this view has already been parsed, restore the word list */
lock (words) {
lock (prefix_tree) {
if (text_view_words.has_key (view)) {
words = text_view_words.@get (view);
prefix_tree = text_view_words.@get (view);
} else {
/* Else create a new word list and parse the buffer text */
words = new Gee.ArrayList<string> ();
/* Else create a new word list and parse the buffer text */
prefix_tree = new Scratch.Plugins.PrefixTree ();
}
}

if (view.buffer.text.length > 0) {
parse_string (view.buffer.text);
text_view_words.@set (view, words);
text_view_words.@set (view, prefix_tree);
}
}

private void add_word (string word) {
public void add_word (string word) {
if (word.length < MINIMUM_WORD_LENGTH)
return;

if (!(word in words)) {
lock (words) {
words.add (word);
}
lock (prefix_tree) {
prefix_tree.insert (word);
}
}

Expand Down
1 change: 1 addition & 0 deletions plugins/word-completion/meson.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module_name = 'word-completion'

module_files = [
'prefix-tree.vala',
'completion-provider.vala',
'engine.vala',
'plugin.vala'
Expand Down
56 changes: 21 additions & 35 deletions plugins/word-completion/plugin.vala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {

private MainWindow main_window;
private Scratch.Services.Interface plugins;
private bool completion_in_progress = false;

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

private uint timeout_id = 0;
private bool completion_visible = false;

public void activate () {
plugins = (Scratch.Services.Interface) object;
Expand Down Expand Up @@ -77,20 +77,24 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {
current_document = doc;
current_view = doc.source_view;
current_view.key_press_event.connect (on_key_press);
current_view.completion.show.connect (on_completion_shown);
current_view.completion.hide.connect (on_completion_hidden);
current_view.completion.show.connect (() => {
completion_in_progress = true;
});
current_view.completion.hide.connect (() => {
completion_in_progress = false;
});


if (text_view_list.find (current_view) == null)
text_view_list.append (current_view);

var comp_provider = new Scratch.Plugins.CompletionProvider (this);
comp_provider.priority = 1;
comp_provider.name = provider_name_from_document (doc);
comp_provider.can_propose.connect (on_propose);

try {
current_view.completion.add_provider (comp_provider);
current_view.completion.show_headers = false;
current_view.completion.show_headers = true;
current_view.completion.show_icons = true;
/* Wait a bit to allow text to load then run parser*/
timeout_id = Timeout.add (1000, on_timeout_update);
Expand Down Expand Up @@ -134,49 +138,33 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {
parser.rebuild_word_list (current_view);
current_view.show_completion ();
return true;
} else
return false;
}
}

bool activating = kv in ACTIVATE_KEYS;
if (!completion_in_progress && parser.is_delimiter (uc) &&
(uc.isprint () || uc.isspace ())) {

if (completion_visible && activating) {
current_view.completion.activate_proposal ();
parser.add_last_word ();
return true;
}
var buffer = current_view.buffer;
var mark = buffer.get_insert ();
Gtk.TextIter cursor_iter;
buffer.get_iter_at_mark (out cursor_iter, mark);

var word_start = cursor_iter;
word_start.backward_word_start ();

if (activating || (uc.isprint () && parser.is_delimiter (uc) )) {
parser.add_last_word ();
current_view.completion.hide ();
string word = buffer.get_text (word_start, cursor_iter, false);
parser.add_word (word);
}

return false;
}

private void on_completion_shown () {
completion_visible = true;
}

private void on_completion_hidden () {
completion_visible = false;
}

private void on_propose (bool can_propose) {
if (!can_propose)
current_view.completion.hide ();

completion_visible = can_propose;
}

private string provider_name_from_document (Scratch.Services.Document doc) {
return _("%s - Word Completion").printf (doc.get_basename ());
}

private void cleanup (Gtk.SourceView view) {
current_view.key_press_event.disconnect (on_key_press);
current_view.completion.show.disconnect (on_completion_shown);
current_view.completion.hide.disconnect (on_completion_hidden);

current_view.completion.get_providers ().foreach ((p) => {
try {
Expand All @@ -189,8 +177,6 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {
warning (e.message);
}
});

completion_visible = false;
}
}

Expand Down
Loading