From cbb432b9fe9fc45d3ce36b50f41585329624985d Mon Sep 17 00:00:00 2001
From: Albert Graef <aggraef@gmail.com>
Date: Sat, 13 Aug 2022 02:09:10 +0200
Subject: [PATCH] Make Tab cycle through the available completions.

This gives the user a quick way of selecting a completion without having
to interact with the autocompletion popup at all, by just cycling
through the available options. Users of fish or emacs auto-completion
mode will find this familiar.
---
 pd/nw/pd_canvas.js | 18 ++++++++++--------
 pd/nw/pdgui.js     | 10 ++++++++--
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index a2bab3f00..05d5860a4 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -50,6 +50,7 @@ var canvas_events = (function() {
         last_dropdown_menu_y,
         last_search_term = "",
         svg_view = document.getElementById("patchsvg").viewBox.baseVal,
+        last_completed = -1, // last Tab completion (autocomplete)
         textbox = function () {
             return document.getElementById("new_object_textentry");
         },
@@ -318,6 +319,9 @@ var canvas_events = (function() {
                 }
             }
         },
+        ac_dropdown = function() {
+            return document.getElementById("autocomplete_dropdown")
+        },
         events = {
             mousemove: function(evt) {
                 //pdgui.post("x: " + evt.pageX + " y: " + evt.pageY +
@@ -518,8 +522,8 @@ var canvas_events = (function() {
                 return false;
             },
             text_mousedown: function(evt) {
-                if (evt.target.parentNode === document.getElementById("autocomplete_dropdown")) {
-                    pdgui.select_result_autocomplete_dd(textbox(), document.getElementById("autocomplete_dropdown"));
+                if (evt.target.parentNode === ac_dropdown()) {
+                    last_completed = pdgui.select_result_autocomplete_dd(textbox(), ac_dropdown(), last_completed);
                     // ag: Don't do the usual object instantiation thing if
                     // we've clicked on the autocompletion dropdown. This
                     // means that the user can just go on editing, entering
@@ -563,9 +567,6 @@ var canvas_events = (function() {
                 evt.stopPropagation();
 
                 // GB: Autocomplete feature
-                let ac_dropdown = function() {
-                    return document.getElementById("autocomplete_dropdown")
-                }
                 switch (evt.keyCode) {
                     case 40: // arrowdown
                         pdgui.update_autocomplete_dd_arrowdown(ac_dropdown())
@@ -578,21 +579,22 @@ var canvas_events = (function() {
                         if(ac_dropdown() === null || ac_dropdown().getAttribute("selected_item") === "-1") {
                             grow_svg_for_element(textbox());
                         } else { // else, if there is a selected item on autocompletion tool, the selected item is written on the box
-                            pdgui.select_result_autocomplete_dd(textbox(), ac_dropdown());
+                            last_completed = pdgui.select_result_autocomplete_dd(textbox(), ac_dropdown(), last_completed);
                             caret_end();
                             // No need to instantiate the object here,
                             // presumably the user wants to go on editing.
                         }
                         break;
                     case 9: // tab
-                        // TODO: Substitute this function by one that autocompletes with the common prefix of all results
-                        pdgui.select_result_autocomplete_dd(textbox(), ac_dropdown());
+                        last_completed = pdgui.select_result_autocomplete_dd(textbox(), ac_dropdown(), last_completed);
                         caret_end();
                         break;
                     case 27: // esc
                         pdgui.delete_autocomplete_dd(ac_dropdown());
+                        last_completed = -1;
                         break;
                     default:
+                        last_completed = -1;
                         if (textbox().innerText === "") {
                             pdgui.delete_autocomplete_dd(ac_dropdown());
                         } else {
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index 09dd35b1d..0c4eed13e 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -631,15 +631,21 @@ function update_autocomplete_dd_arrowup(ac_dropdown) {
     }
 }
 
-function select_result_autocomplete_dd(textbox, ac_dropdown) {
+function select_result_autocomplete_dd(textbox, ac_dropdown, last) {
     if (ac_dropdown !== null) {
         let sel = ac_dropdown.getAttribute("selected_item");
         if (sel > -1) {
             textbox.innerText = ac_dropdown.children.item(sel).innerText;
             delete_autocomplete_dd(ac_dropdown);
+            return sel;
         } else { // it only passes here when the user presses 'tab' and there is no option selected
-            textbox.innerText = ac_dropdown.children.item(0).innerText;
+            var n = ac_dropdown.children.length;
+            var next = (last+1) % n;
+            textbox.innerText = ac_dropdown.children.item(next).innerText;
+            return next;
         }
+    } else {
+        return -1;
     }
 }
 
-- 
GitLab