Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

851 fix word completion logic #1487

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 41 additions & 1 deletion plugins/word-completion/engine.vala
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class Euclide.Completion.Parser : GLib.Object {

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

Expand Down Expand Up @@ -86,7 +87,7 @@ public class Euclide.Completion.Parser : GLib.Object {
parsing_cancelled = true;
}

private bool parse_string (string text) {
public bool parse_string (string text) {
parsing_cancelled = false;
string [] word_array = text.split_set (DELIMITERS, MAX_TOKENS);
foreach (var current_word in word_array ) {
Expand All @@ -98,4 +99,43 @@ public class Euclide.Completion.Parser : GLib.Object {
}
return true;
}

public void delete_word (string word, string text) requires (word.length > 0) {
bool match_found = false;
uint word_end_index = word.length - 1;

// Figure out if another instance of a word in another position before trying to delete it
// from the prefix tree
while (word_end_index > -1 && !match_found) {
match_found = prefix_in_text (word[0:word_end_index], text);
word_end_index--;
}

// All possible prefixes of the word exist in the source view
if (match_found && word_end_index == word.length - 1) {
return;
}

uint min_deletion_index = word_end_index + 1;

lock (prefix_tree) {
prefix_tree.remove (word, (int) min_deletion_index);
}
}

private bool prefix_in_text (string word, string text) {
// If there are at least two matches then the prefix
// still exists after the modifications made to the source view

try {
var search_regex = new Regex ("\\b$word\\b");
GLib.MatchInfo match_info;
search_regex.match_all (text, 0, out match_info);
return match_info.get_match_count () > 1;
} catch (GLib.Error err) {
critical ("Error while attempting regex search of prefix in document text: %s", err.message);
}

return false;
}
}
100 changes: 67 additions & 33 deletions plugins/word-completion/plugin.vala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ 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.buffer.insert_text.connect (on_insert_text);

current_view.completion.show.connect (() => {
completion_in_progress = true;
});
Expand Down Expand Up @@ -120,53 +121,86 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable {
return false;
}

private bool on_key_press (Gtk.Widget view, Gdk.EventKey event) {
var kv = event.keyval;
/* Pass through any modified keypress except Shift or Capslock */
Gdk.ModifierType mods = event.state & Gdk.ModifierType.MODIFIER_MASK
& ~Gdk.ModifierType.SHIFT_MASK
& ~Gdk.ModifierType.LOCK_MASK;
if (mods > 0 ) {
/* Default key for USER_REQUESTED completion is ControlSpace
* but this is trapped elsewhere. Control + USER_REQUESTED_KEY acts as an
* alternative and also purges spelling mistakes and unused words from the list.
* If used when a word or part of a word is selected, the selection will be
* used as the word to find. */

if ((mods & Gdk.ModifierType.CONTROL_MASK) > 0 &&
(kv == REFRESH_SHORTCUT)) {

parser.rebuild_word_list (current_view);
current_view.show_completion ();
return true;
}
private void on_insert_text (Gtk.TextIter pos, string new_text, int new_text_length) {

if (new_text.strip () == "") {
return;
}

var uc = (unichar)(Gdk.keyval_to_unicode (kv));
if (!completion_in_progress && Euclide.Completion.Parser.is_delimiter (uc) &&
(uc.isprint () || uc.isspace ())) {
bool starts_word = pos.starts_word ();
bool ends_word = pos.ends_word ();
bool between_word = pos.inside_word () && !starts_word && !ends_word;

var buffer = current_view.buffer;
var mark = buffer.get_insert ();
Gtk.TextIter cursor_iter;
buffer.get_iter_at_mark (out cursor_iter, mark);
if (ends_word) {
this.handle_insert_at_phrase_end (pos, new_text, new_text_length);
} else if (between_word) {
this.handle_insert_between_phrase (pos, new_text, new_text_length);
} else {
this.handle_insert_not_at_word_boundary (pos, new_text, new_text_length);
}
}

var word_start = cursor_iter;
Euclide.Completion.Parser.back_to_word_start (ref word_start);
private void handle_insert_between_phrase (Gtk.TextIter pos, string new_text, int new_text_length) {
debug ("word-completion: Text inserted between word.\n");
var word_start_iter = pos;
word_start_iter.backward_word_start ();

var word_end_iter = pos;
word_end_iter.forward_word_end ();

var old_word_to_delete = word_start_iter.get_text (word_end_iter);
parser.delete_word (old_word_to_delete, current_view.buffer.text);

// Check if new text ends with whitespace
if (ends_with_whitespace (new_text)) {
// The text from the insert postiion to the end of the word needs to be added as its own word
var final_word_end_iter = pos;
final_word_end_iter.forward_word_end ();

var extra_word_to_add = pos.get_text (final_word_end_iter);
parser.parse_string (extra_word_to_add);
}

string word = buffer.get_text (word_start, cursor_iter, false);
parser.add_word (word);
var full_phrases = word_start_iter.get_text (pos) + new_text;
parser.parse_string (full_phrases);
}

private bool ends_with_whitespace (string str) {
if (str.length == 0) {
return false;
}


if (str.get_char (str.length - 1).isspace ()) {
return true;
}

return false;
}

private void handle_insert_at_phrase_end (Gtk.TextIter pos, string new_text, int new_text_length) {
var text_start_iter = Gtk.TextIter ();
text_start_iter = pos;
text_start_iter.backward_word_start ();

var text_end_iter = Gtk.TextIter ();
text_end_iter.assign (pos);
text_end_iter.forward_chars (new_text_length - 1);

var full_phrases = text_start_iter.get_text (text_end_iter) + new_text;
parser.parse_string (full_phrases);
}

private void handle_insert_not_at_word_boundary (Gtk.TextIter pos, string new_text, int new_text_length) {
parser.parse_string (new_text);
}

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.buffer.insert_text.disconnect (on_insert_text);

current_view.completion.get_providers ().foreach ((p) => {
try {
Expand Down
34 changes: 33 additions & 1 deletion plugins/word-completion/prefix-tree.vala
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,40 @@ namespace Scratch.Plugins {
}
}

public void remove (string word, int min_deletion_index) {
if (word.length == 0) {
return;
}

remove_at (word, root, min_deletion_index);
}

private bool remove_at (string word, PrefixNode node, int min_deletion_index, int char_index = 0) {
unichar curr;

word.get_next_char (ref char_index, out curr);
if (curr == '\0') {
return true;
}

foreach (var child in node.children) {
if (child.value == curr) {
bool should_continue = this.remove_at (word, node, min_deletion_index, char_index + 1);

if (should_continue && child.children.length () == 0) {
node.children.remove (child);
return char_index < min_deletion_index;
}

break;
}
}

return false;
}

public bool find_prefix (string prefix) {
return find_prefix_at (prefix, root) != null? true : false;
return find_prefix_at (prefix, root) != null ? true : false;
}

private PrefixNode? find_prefix_at (string prefix, PrefixNode node, int i = 0) {
Expand Down
Loading