From 7dbf009a050cae7533d2dfea473bad926aa5bcdf Mon Sep 17 00:00:00 2001 From: Colin Kiama Date: Sat, 2 Nov 2024 12:33:35 +0000 Subject: [PATCH 01/11] Start handling insert_text signal instead of key press signal in word completion plugin The 'insert_text' signal gives more detail about how the document contents have changed, giving us a way to more accurately update the prefix tree used for word completion --- plugins/word-completion/plugin.vala | 62 ++++++++++++----------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/plugins/word-completion/plugin.vala b/plugins/word-completion/plugin.vala index d227d12a6a..b70f2f11d1 100644 --- a/plugins/word-completion/plugin.vala +++ b/plugins/word-completion/plugin.vala @@ -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; }); @@ -120,45 +121,32 @@ 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 (completion_in_progress) { + return; } - var uc = (unichar)(Gdk.keyval_to_unicode (kv)); - if (!completion_in_progress && Euclide.Completion.Parser.is_delimiter (uc) && - (uc.isprint () || uc.isspace ())) { - - 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; - Euclide.Completion.Parser.back_to_word_start (ref word_start); - - string word = buffer.get_text (word_start, cursor_iter, false); - parser.add_word (word); + if (new_text.strip () == "") { + return; } - return false; + if (pos.ends_word ()) { + this.handle_insert_at_phrase_end (pos, new_text, new_text_length); + } + } + + 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 (); + + // Create a string of all the words from the first word start to the new text length + 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); + debug ("Full phrases:\n\n%s\n\n", full_phrases); } private string provider_name_from_document (Scratch.Services.Document doc) { @@ -166,7 +154,7 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { } 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 { From ec2cc9ccb1f8f755d8aa2205cb6ce44bfbc4a7c2 Mon Sep 17 00:00:00 2001 From: Colin Kiama Date: Sat, 2 Nov 2024 14:06:49 +0000 Subject: [PATCH 02/11] Word completion now adds words when multiple characters of text are added. Not just one character --- plugins/word-completion/engine.vala | 2 +- plugins/word-completion/plugin.vala | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/word-completion/engine.vala b/plugins/word-completion/engine.vala index eff236042f..9b093d2ac2 100644 --- a/plugins/word-completion/engine.vala +++ b/plugins/word-completion/engine.vala @@ -86,7 +86,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 ) { diff --git a/plugins/word-completion/plugin.vala b/plugins/word-completion/plugin.vala index b70f2f11d1..8c7bd98483 100644 --- a/plugins/word-completion/plugin.vala +++ b/plugins/word-completion/plugin.vala @@ -76,7 +76,7 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { current_document = doc; current_view = doc.source_view; - current_view.buffer.insert_text.connect(on_insert_text); + current_view.buffer.insert_text.connect (on_insert_text); current_view.completion.show.connect (() => { completion_in_progress = true; @@ -134,19 +134,18 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { this.handle_insert_at_phrase_end (pos, new_text, new_text_length); } } - + 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 (); - - // Create a string of all the words from the first word start to the new text length + 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); - debug ("Full phrases:\n\n%s\n\n", full_phrases); + + var full_phrases = text_start_iter.get_text (text_end_iter) + new_text; + parser.parse_string (full_phrases); } private string provider_name_from_document (Scratch.Services.Document doc) { From 79e0429a9459f6003c390344beef7191d70b17b3 Mon Sep 17 00:00:00 2001 From: Colin Kiama Date: Sat, 2 Nov 2024 22:06:11 +0000 Subject: [PATCH 03/11] Bring back handling of words from text inserted that isn't at a word boundaryto the Word Completion plugin --- plugins/word-completion/plugin.vala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/word-completion/plugin.vala b/plugins/word-completion/plugin.vala index 8c7bd98483..08726bb75f 100644 --- a/plugins/word-completion/plugin.vala +++ b/plugins/word-completion/plugin.vala @@ -132,6 +132,8 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { if (pos.ends_word ()) { this.handle_insert_at_phrase_end (pos, new_text, new_text_length); + } else { + this.handle_insert_not_at_word_boundary (pos, new_text, new_text_length); } } @@ -148,6 +150,10 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { 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 ()); } From 8a6edf0a1a9d1614efa4c828cf56ddbdf802dd4a Mon Sep 17 00:00:00 2001 From: Colin Kiama Date: Sun, 10 Nov 2024 23:43:04 +0000 Subject: [PATCH 04/11] Prepare for prefix tree word removal in word completion engine --- plugins/word-completion/engine.vala | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugins/word-completion/engine.vala b/plugins/word-completion/engine.vala index 9b093d2ac2..d4f23ef08b 100644 --- a/plugins/word-completion/engine.vala +++ b/plugins/word-completion/engine.vala @@ -98,4 +98,41 @@ 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 deletion_depth_level = word.length - word_end_index; + // TODO: Now delete word (prefix) from prefix tree with deletion_ + // depth level + } + + 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; + } } From 27d585f6732d8eb519b33ffecab27c03d596ec42 Mon Sep 17 00:00:00 2001 From: Colin Kiama Date: Thu, 21 Nov 2024 20:29:33 +0000 Subject: [PATCH 05/11] word-completion: Add "remove" method to PrefixTree --- plugins/word-completion/engine.vala | 8 +++--- plugins/word-completion/prefix-tree.vala | 32 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/plugins/word-completion/engine.vala b/plugins/word-completion/engine.vala index d4f23ef08b..c2aad1e0a3 100644 --- a/plugins/word-completion/engine.vala +++ b/plugins/word-completion/engine.vala @@ -115,9 +115,11 @@ public class Euclide.Completion.Parser : GLib.Object { return; } - uint deletion_depth_level = word.length - word_end_index; - // TODO: Now delete word (prefix) from prefix tree with deletion_ - // depth level + 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) { diff --git a/plugins/word-completion/prefix-tree.vala b/plugins/word-completion/prefix-tree.vala index ea5ca2d414..45806a35c9 100644 --- a/plugins/word-completion/prefix-tree.vala +++ b/plugins/word-completion/prefix-tree.vala @@ -63,6 +63,38 @@ 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; } From ab7fa7529949d0cce8c0b2792cf75c3f7b07585f Mon Sep 17 00:00:00 2001 From: Colin Kiama Date: Fri, 22 Nov 2024 21:14:46 +0000 Subject: [PATCH 06/11] Detect when text gets inserted between a word --- plugins/word-completion/plugin.vala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/word-completion/plugin.vala b/plugins/word-completion/plugin.vala index 08726bb75f..6498752382 100644 --- a/plugins/word-completion/plugin.vala +++ b/plugins/word-completion/plugin.vala @@ -130,8 +130,14 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { return; } - if (pos.ends_word ()) { + bool starts_word = pos.starts_word (); + bool ends_word = pos.ends_word (); + bool between_word = pos.inside_word () && !starts_word && !ends_word; + + if (ends_word) { this.handle_insert_at_phrase_end (pos, new_text, new_text_length); + } else if (between_word) { + debug ("word-completion: Text inserted between word.\n"); } else { this.handle_insert_not_at_word_boundary (pos, new_text, new_text_length); } From f748c017d6e517b54060051ed10cfe21942d9ef0 Mon Sep 17 00:00:00 2001 From: Colin Kiama Date: Sun, 24 Nov 2024 18:21:25 +0000 Subject: [PATCH 07/11] word-completion: Support text being inserted between a word --- plugins/word-completion/plugin.vala | 39 ++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/plugins/word-completion/plugin.vala b/plugins/word-completion/plugin.vala index 6498752382..623aa1d9c5 100644 --- a/plugins/word-completion/plugin.vala +++ b/plugins/word-completion/plugin.vala @@ -137,12 +137,49 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { if (ends_word) { this.handle_insert_at_phrase_end (pos, new_text, new_text_length); } else if (between_word) { - debug ("word-completion: Text inserted between word.\n"); + 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); } } + 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); + } + + 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; From 01af591de81889a3a8302482e94cef494d832331 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 26 Nov 2024 12:23:25 +0000 Subject: [PATCH 08/11] Fix minor code style --- plugins/word-completion/plugin.vala | 5 +++-- plugins/word-completion/prefix-tree.vala | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/word-completion/plugin.vala b/plugins/word-completion/plugin.vala index 623aa1d9c5..0874804b60 100644 --- a/plugins/word-completion/plugin.vala +++ b/plugins/word-completion/plugin.vala @@ -173,10 +173,11 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { return false; } - if (str.get_char(str.length - 1).isspace ()) { + + if (str.get_char (str.length - 1).isspace ()) { return true; } - + return false; } diff --git a/plugins/word-completion/prefix-tree.vala b/plugins/word-completion/prefix-tree.vala index 45806a35c9..6341949d13 100644 --- a/plugins/word-completion/prefix-tree.vala +++ b/plugins/word-completion/prefix-tree.vala @@ -96,7 +96,7 @@ namespace Scratch.Plugins { } 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) { From 12100cb04ded10f1e22cf3cb427e6225bfd24fea Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 26 Nov 2024 13:13:14 +0000 Subject: [PATCH 09/11] Continue to handle insertions even if no current completions --- plugins/word-completion/plugin.vala | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/word-completion/plugin.vala b/plugins/word-completion/plugin.vala index 0874804b60..b6c018b534 100644 --- a/plugins/word-completion/plugin.vala +++ b/plugins/word-completion/plugin.vala @@ -122,9 +122,6 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { } private void on_insert_text (Gtk.TextIter pos, string new_text, int new_text_length) { - if (completion_in_progress) { - return; - } if (new_text.strip () == "") { return; From 3654d6030eb8ddaef4a1e4c858e7a9918ee0367d Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 26 Nov 2024 13:15:48 +0000 Subject: [PATCH 10/11] Do not show current word to find in completion list --- plugins/word-completion/engine.vala | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/word-completion/engine.vala b/plugins/word-completion/engine.vala index c2aad1e0a3..30f5ed5f8d 100644 --- a/plugins/word-completion/engine.vala +++ b/plugins/word-completion/engine.vala @@ -48,6 +48,7 @@ public class Euclide.Completion.Parser : GLib.Object { public bool get_for_word (string to_find, out List list) { list = prefix_tree.get_all_matches (to_find); + list.remove_link (list.find_custom (to_find, strcmp)); return list.first () != null; } From 6176df76aae015a7609bd96cb304336cf88b8c4e Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 26 Nov 2024 14:30:46 +0000 Subject: [PATCH 11/11] Ensure PrefixNode value is immutable --- plugins/word-completion/prefix-tree.vala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/plugins/word-completion/prefix-tree.vala b/plugins/word-completion/prefix-tree.vala index 6341949d13..14e5fc787c 100644 --- a/plugins/word-completion/prefix-tree.vala +++ b/plugins/word-completion/prefix-tree.vala @@ -2,7 +2,13 @@ namespace Scratch.Plugins { private class PrefixNode : Object { public GLib.List children; - public unichar value { get; set; } + public unichar value { get; construct; } + + public PrefixNode (unichar c = '\0') { + Object ( + value: c + ); + } construct { children = new List (); @@ -17,9 +23,7 @@ namespace Scratch.Plugins { } public void clear () { - root = new PrefixNode () { - value = '\0' - }; + root = new PrefixNode (); } public void insert (string word) { @@ -47,9 +51,7 @@ namespace Scratch.Plugins { } } - var new_child = new PrefixNode () { - value = curr - }; + var new_child = new PrefixNode (curr); node.children.insert_sorted (new_child, (c1, c2) => { if (c1.value > c2.value) { return 1;