diff --git a/plugins/word-completion/completion-provider.vala b/plugins/word-completion/completion-provider.vala deleted file mode 100644 index 2624f4d20..000000000 --- a/plugins/word-completion/completion-provider.vala +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2024-2025 elementary, Inc. - * Copyright (c) 2013 Mario Guerriero - * - * This is a free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; see the file COPYING. If not, - * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -public class Scratch.Plugins.CompletionProvider : Gtk.SourceCompletionProvider, GLib.Object { - private const int MAX_COMPLETIONS = 10; - public string name { get; construct; } - public int priority { get; construct; } - public int interactive_delay { get; construct; } - public Gtk.SourceCompletionActivation activation { get; construct; } - - public const string COMPLETION_END_MARK_NAME = "ScratchWordCompletionEnd"; - public const string COMPLETION_START_MARK_NAME = "ScratchWordCompletionStart"; - - public Gtk.TextView? view { get; construct; } - public Euclide.Completion.Parser parser { get; construct; } - - private unowned Gtk.TextBuffer buffer { - get { - return view.buffer; - } - } - - private Gtk.TextMark completion_end_mark; - private Gtk.TextMark completion_start_mark; - private string current_text_to_find = ""; - - public signal void can_propose (bool b); - - public CompletionProvider ( - Euclide.Completion.Parser _parser, - Scratch.Services.Document _doc - ) { - - Object ( - parser: _parser, - view: _doc.source_view, - name: _("%s - Word Completion").printf (_doc.get_basename ()) - ); - } - - construct { - interactive_delay = (int) Completion.INTERACTIVE_DELAY; - activation = INTERACTIVE | USER_REQUESTED; - Gtk.TextIter iter; - view.buffer.get_iter_at_offset (out iter, 0); - completion_end_mark = buffer.create_mark (COMPLETION_END_MARK_NAME, iter, false); - completion_start_mark = buffer.create_mark (COMPLETION_START_MARK_NAME, iter, false); - } - - 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); - - return parser.match (text); - } - - public void populate (Gtk.SourceCompletionContext context) { - /*Store current insertion point for use in activate_proposal */ - GLib.List? file_props; - bool no_minimum = (context.get_activation () == Gtk.SourceCompletionActivation.USER_REQUESTED); - get_proposals (out file_props, no_minimum); - context.add_proposals (this, file_props, true); - } - - public bool activate_proposal (Gtk.SourceCompletionProposal proposal, Gtk.TextIter iter) { - 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_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); - return true; - } - - 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; - props = null; - - Gtk.TextIter start, end; - buffer.get_selection_bounds (out start, out end); - - 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); - } - - 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 */ - List 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.append (item); - } - - return true; - } - } - return false; - } -} diff --git a/plugins/word-completion/engine.vala b/plugins/word-completion/engine.vala deleted file mode 100644 index b01cca076..000000000 --- a/plugins/word-completion/engine.vala +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2011-2013 Lucas Baudin - * Copyright (c) 2014-2022 elementary Inc. (https://elementary.io) - * - * This is a free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; see the file COPYING. If not, - * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -public class Euclide.Completion.Parser : GLib.Object { - public const int MINIMUM_WORD_LENGTH = 1; - public const int MAX_TOKENS = 1000000; - - 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 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; - - public Parser () { - text_view_words = new Gee.HashMap (); - prefix_tree = new Scratch.Plugins.PrefixTree (); - } - - public bool match (string to_find) { - return prefix_tree.find_prefix (to_find); - } - - public bool get_for_word (string to_find, out List list) { - list = prefix_tree.get_all_matches (to_find); - return list.first () != null; - } - - public void rebuild_word_list (Gtk.TextView view) { - 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 (prefix_tree) { - if (text_view_words.has_key (view)) { - prefix_tree = text_view_words.@get (view); - } else { - /* 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, prefix_tree); - } - } - - public void add_word (string word) { - if (word.length < MINIMUM_WORD_LENGTH) - return; - - lock (prefix_tree) { - prefix_tree.insert (word); - } - } - - public void cancel_parsing () { - parsing_cancelled = true; - } - - private bool parse_string (string text) { - parsing_cancelled = false; - string [] word_array = text.split_set (DELIMITERS, MAX_TOKENS); - foreach (var current_word in word_array ) { - if (parsing_cancelled) { - debug ("Cancelling parse"); - return false; - } - add_word (current_word); - } - return true; - } -} diff --git a/plugins/word-completion/meson.build b/plugins/word-completion/meson.build index 23a75eee2..3e560feab 100644 --- a/plugins/word-completion/meson.build +++ b/plugins/word-completion/meson.build @@ -1,10 +1,6 @@ module_name = 'word-completion' module_files = [ - 'prefix-tree.vala', - 'prefix-tree-node.vala', - 'completion-provider.vala', - 'engine.vala', 'plugin.vala' ] diff --git a/plugins/word-completion/plugin.vala b/plugins/word-completion/plugin.vala index 81559c6f4..ce2707c74 100644 --- a/plugins/word-completion/plugin.vala +++ b/plugins/word-completion/plugin.vala @@ -26,13 +26,11 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Scratch.Services.A private List text_view_list = new List (); private Gtk.EventControllerKey key_controller; - public Euclide.Completion.Parser parser {get; private set;} public Gtk.SourceView? current_view {get; private set;} public Scratch.Services.Document current_document {get; private set;} private MainWindow main_window; private Scratch.Services.Interface plugins; - private bool completion_in_progress = false; private const uint [] ACTIVATE_KEYS = { Gdk.Key.Return, @@ -49,19 +47,11 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Scratch.Services.A public void activate () { plugins = (Scratch.Services.Interface) object; - parser = new Euclide.Completion.Parser (); plugins.hook_window.connect ((w) => { this.main_window = w; }); plugins.hook_document.connect (on_new_source_view); - plugins.hook_window.connect ((w) => { - key_controller = new Gtk.EventControllerKey (w) { - propagation_phase = CAPTURE - }; - - key_controller.key_pressed.connect (on_key_press); - }); } public void deactivate () { @@ -77,8 +67,6 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Scratch.Services.A if (current_view == doc.source_view) return; - parser.cancel_parsing (); - if (timeout_id > 0) GLib.Source.remove (timeout_id); @@ -88,96 +76,24 @@ public class Scratch.Plugins.Completion : Peas.ExtensionBase, Scratch.Services.A current_document = doc; current_view = doc.source_view; - 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 (parser, doc); + var comp_provider = new Gtk.SourceCompletionWords ( + provider_name_from_document (doc), + null + ); + comp_provider.register (current_view.buffer); try { current_view.completion.add_provider (comp_provider); 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); - } catch (Error e) { warning (e.message); } } - private bool on_timeout_update () { - try { - new Thread.try ("word-completion-thread", () => { - if (current_view != null) - parser.parse_text_view (current_view as Gtk.TextView); - - return null; - }); - } catch (Error e) { - warning (e.message); - } - - timeout_id = 0; - return false; - } - - private bool on_key_press ( - uint keyval, - uint keycode, - Gdk.ModifierType state - ) requires (current_view != null) { - - var kv = keyval; - if (!current_view.is_focus) { - return false; - } - /* Pass through any modified keypress except Shift or Capslock */ - Gdk.ModifierType mods = 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; - } - } - - 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); - } - - return false; - } - private string provider_name_from_document (Scratch.Services.Document doc) { return _("%s - Word Completion").printf (doc.get_basename ()); } diff --git a/plugins/word-completion/prefix-tree-node.vala b/plugins/word-completion/prefix-tree-node.vala deleted file mode 100644 index 375d36890..000000000 --- a/plugins/word-completion/prefix-tree-node.vala +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2024-2025 elementary, Inc. - * 2011 Lucas Baudin - * * - * This is a free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; see the file COPYING. If not, - * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -public class Scratch.Plugins.PrefixNode : Object { - public enum NodeType { - ROOT, - CHAR, - WORD_END - } - - private const unichar WORD_END_CHAR = '\0'; - private uint occurrences; // Only used for WORD_END nodes - - public unichar uc { get; construct; } - public NodeType node_type { get; construct; } - public PrefixNode? parent { get; construct; } - public unichar value { get; construct; } - - public Gee.ArrayList children; - - public PrefixNode.from_unichar (unichar c, PrefixNode? _parent) requires (c != WORD_END_CHAR) { - Object ( - value: c, - parent: _parent, - uc: c, - node_type: NodeType.CHAR - ); - } - - public PrefixNode.root () { - Object ( - parent: null, - uc: WORD_END_CHAR, - node_type: NodeType.ROOT - ); - } - - public PrefixNode.word_end (PrefixNode _parent) { - Object ( - parent: _parent, - uc: WORD_END_CHAR, - node_type: NodeType.WORD_END - ); - - occurrences = 1; - } - - construct { - children = new Gee.ArrayList (); - } -} diff --git a/plugins/word-completion/prefix-tree.vala b/plugins/word-completion/prefix-tree.vala deleted file mode 100644 index cdf2faf93..000000000 --- a/plugins/word-completion/prefix-tree.vala +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2021-2025 elementary, Inc. - */ - -namespace Scratch.Plugins { - public class PrefixTree : Object { - private PrefixNode root; - - construct { - clear (); - } - - public void clear () { - root = new PrefixNode (); - } - - public void insert (string word) { - if (word.length == 0) { - return; - } - - this.insert_at (word, this.root); - } - - private void insert_at (string word, PrefixNode node, int i = 0) { - unichar curr = '\0'; - - bool has_next_character = false; - do { - has_next_character = word.get_next_char (ref i, out curr); - } while (has_next_character && Euclide.Completion.Parser.is_delimiter (curr)); - - foreach (var child in node.children) { - if (child.value == curr) { - if (curr != '\0') { - insert_at (word, child, i); - } - return; - } - } - - var new_child = new PrefixNode.from_unichar (curr, null); - node.children.insert (0, new_child); - node.children.sort ((c1, c2) => { - if (c1.value > c2.value) { - return 1; - } else if (c1.value == c2.value) { - return 0; - } - return -1; - }); - if (curr != '\0') { - insert_at (word, new_child, i); - } - } - - public bool find_prefix (string prefix) { - return find_prefix_at (prefix, root) != null? true : false; - } - - private PrefixNode? find_prefix_at (string prefix, PrefixNode node, int i = 0) { - unichar curr; - - prefix.get_next_char (ref i, out curr); - if (curr == '\0') { - return node; - } - - foreach (var child in node.children) { - if (child.value == curr) { - return find_prefix_at (prefix, child, i); - } - } - - return null; - } - - public List get_all_matches (string prefix) { - var list = new List (); - var node = find_prefix_at (prefix, root, 0); - if (node != null) { - var sb = new StringBuilder (prefix); - get_all_matches_rec (node, ref sb, ref list); - } - - return list; - } - - private void get_all_matches_rec ( - PrefixNode node, - ref StringBuilder sbuilder, - ref List matches) { - - foreach (var child in node.children) { - if (child.value == '\0') { - matches.append (sbuilder.str); - } else { - sbuilder.append_unichar (child.value); - get_all_matches_rec (child, ref sbuilder, ref matches); - var length = child.value.to_string ().length; - sbuilder.erase (sbuilder.len - length, -1); - } - } - } - } -}