From 4d1769f0d77c675f46ad251f48e054c706d259de Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 30 Jan 2025 18:46:37 +0000 Subject: [PATCH 1/2] Introduce 3 new parser functions, replace uses of one old function --- .../word-completion/completion-provider.vala | 30 ++------- plugins/word-completion/engine.vala | 62 +++++++++++++++++-- plugins/word-completion/plugin.vala | 5 +- 3 files changed, 64 insertions(+), 33 deletions(-) diff --git a/plugins/word-completion/completion-provider.vala b/plugins/word-completion/completion-provider.vala index d9abbc7b77..85110ee6cf 100644 --- a/plugins/word-completion/completion-provider.vala +++ b/plugins/word-completion/completion-provider.vala @@ -52,11 +52,9 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider, } public bool match (Gtk.SourceCompletionContext context) { - Gtk.TextIter start, end; - buffer.get_iter_at_offset (out end, buffer.cursor_position); - start = end.copy (); - Euclide.Completion.Parser.back_to_word_start (ref start); - string text = buffer.get_text (start, end, true); + Gtk.TextIter iter; + buffer.get_iter_at_offset (out iter, buffer.cursor_position); + string text = parser.get_word_immediately_before (iter); return parser.match (text); } @@ -94,19 +92,6 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider, return 0; } - public bool get_start_iter (Gtk.SourceCompletionContext context, - Gtk.SourceCompletionProposal proposal, - out Gtk.TextIter iter) { - var mark = buffer.get_insert (); - Gtk.TextIter cursor_iter; - buffer.get_iter_at_mark (out cursor_iter, mark); - - iter = cursor_iter; - Euclide.Completion.Parser.back_to_word_start (ref iter); - return true; - } - - private bool get_proposals (out GLib.List? props, bool no_minimum) { string to_find = ""; Gtk.TextBuffer temp_buffer = buffer; @@ -118,12 +103,9 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider, to_find = temp_buffer.get_text (start, end, true); if (to_find.length == 0) { - temp_buffer.get_iter_at_offset (out end, buffer.cursor_position); - - start = end; - Euclide.Completion.Parser.back_to_word_start (ref start); - - to_find = buffer.get_text (start, end, false); + Gtk.TextIter iter; + temp_buffer.get_iter_at_offset (out iter, buffer.cursor_position); + to_find = parser.get_word_immediately_before (iter); } buffer.move_mark_by_name (COMPLETION_END_MARK_NAME, end); diff --git a/plugins/word-completion/engine.vala b/plugins/word-completion/engine.vala index eff236042f..a2832fc8de 100644 --- a/plugins/word-completion/engine.vala +++ b/plugins/word-completion/engine.vala @@ -20,6 +20,8 @@ public class Euclide.Completion.Parser : GLib.Object { public const int MINIMUM_WORD_LENGTH = 1; + public const int MAXIMUM_WORD_LENGTH = 50; + public const int MAX_TOKENS = 1000000; private Scratch.Plugins.PrefixTree prefix_tree; @@ -29,11 +31,6 @@ public class Euclide.Completion.Parser : GLib.Object { return DELIMITERS.index_of_char (c) >= 0; } - public static void back_to_word_start (ref Gtk.TextIter iter) { - iter.backward_find_char (is_delimiter, null); - iter.forward_char (); - } - public Gee.HashMap text_view_words; public bool parsing_cancelled = false; @@ -98,4 +95,59 @@ public class Euclide.Completion.Parser : GLib.Object { } return true; } + + public string get_word_immediately_before (Gtk.TextIter iter) { + int end_pos; + var text = get_sentence_at_iter (iter, out end_pos); + var pos = end_pos; + unichar uc; + text.get_prev_char (ref pos, out uc); + if (is_delimiter (uc)) { + return ""; + } + + pos = (end_pos - MAXIMUM_WORD_LENGTH - 1).clamp (0, end_pos); + if (pos >= end_pos) { + critical ("pos after end_pos"); + return ""; + } + + var sliced_text = text.slice (pos, end_pos); + var words = sliced_text.split_set (DELIMITERS); + var previous_word = words[words.length - 1]; // Maybe "" + return previous_word; + } + + public string get_word_immediately_after (Gtk.TextIter iter) { + int start_pos; + var text = get_sentence_at_iter (iter, out start_pos); + var pos = start_pos; + unichar uc; + text.get_next_char (ref pos, out uc); + if (is_delimiter (uc)) { + return ""; + } + + // Find end of search range + pos = (start_pos + MAXIMUM_WORD_LENGTH + 1).clamp (start_pos, text.length); + if (start_pos >= pos) { + critical ("start pos after pos"); + return ""; + } + + // Find first word in range + var words = text.slice (start_pos, pos).split_set (DELIMITERS, 2); + var next_word = words[0]; // Maybe "" + return next_word; + } + + private string get_sentence_at_iter (Gtk.TextIter iter, out int iter_sentence_offset) { + var start_iter = iter; + var end_iter = iter; + start_iter.backward_sentence_start (); + end_iter.forward_sentence_end (); + var text = start_iter.get_text (end_iter); + iter_sentence_offset = iter.get_offset () - start_iter.get_offset (); + return text; + } } diff --git a/plugins/word-completion/plugin.vala b/plugins/word-completion/plugin.vala index d227d12a6a..417a5aa73a 100644 --- a/plugins/word-completion/plugin.vala +++ b/plugins/word-completion/plugin.vala @@ -151,10 +151,7 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Peas.Activatable { 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); + string word = parser.get_word_immediately_before (cursor_iter); parser.add_word (word); } From 9f75b87b97c6ddf20978044d085cb72bdc9d5c3e Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 30 Jan 2025 19:34:35 +0000 Subject: [PATCH 2/2] Fix logic for completing a selection --- plugins/word-completion/completion-provider.vala | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/word-completion/completion-provider.vala b/plugins/word-completion/completion-provider.vala index 85110ee6cf..0346f8a6eb 100644 --- a/plugins/word-completion/completion-provider.vala +++ b/plugins/word-completion/completion-provider.vala @@ -94,6 +94,8 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider, private bool get_proposals (out GLib.List? props, bool no_minimum) { string to_find = ""; + string completion = ""; + bool have_selection = true; Gtk.TextBuffer temp_buffer = buffer; props = null; @@ -103,6 +105,7 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider, to_find = temp_buffer.get_text (start, end, true); if (to_find.length == 0) { + have_selection = false; Gtk.TextIter iter; temp_buffer.get_iter_at_offset (out iter, buffer.cursor_position); to_find = parser.get_word_immediately_before (iter); @@ -117,9 +120,17 @@ public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider, List prop_word_list; if (parser.get_for_word (to_find, out prop_word_list)) { foreach (var word in prop_word_list) { + // If there is a selection the start mark is at start of completion + // otherwise it is at the cursor position but we want to replace from + // the start of the word + if (have_selection) { + completion = word; + } else { + completion = word.substring (to_find.length); + } var item = new Gtk.SourceCompletionItem (); item.label = word; - item.text = word; + item.text = completion; props.append (item); }