diff --git a/pd/nw/dialog_prefs.html b/pd/nw/dialog_prefs.html index 1af561488fa718d47ae520a9df7f59067781c5ef..6a01a941ea727701343e962e5ef4bfd6948a702c 100644 --- a/pd/nw/dialog_prefs.html +++ b/pd/nw/dialog_prefs.html @@ -453,6 +453,11 @@ select { <input type="checkbox" id="autocomplete_prefix" name="autocomplete_prefix"> <span data-i18n="prefs.gui.autocomplete.autocomplete_prefix"></span> </label> + <br/> + <label data-i18n="[title]prefs.gui.autocomplete.autocomplete_relevance_tt"> + <input type="checkbox" id="autocomplete_relevance" name="autocomplete_relevance"> + <span data-i18n="prefs.gui.autocomplete.autocomplete_relevance"></span> + </label> <br/><br/> <span data-i18n="prefs.gui.browser.browser_title"></span> <br/> @@ -806,6 +811,7 @@ function apply(save_prefs) { get_bool_elem("save_zoom"), get_bool_elem("autocomplete"), get_bool_elem("autocomplete_prefix"), + get_bool_elem("autocomplete_relevance"), get_bool_elem("browser_doc"), get_bool_elem("browser_path"), get_bool_elem("browser_init"), @@ -817,7 +823,8 @@ function apply(save_prefs) { get_bool_elem("browser_doc"), get_bool_elem("browser_path"), get_bool_elem("autocomplete"), - get_bool_elem("autocomplete_prefix") + get_bool_elem("autocomplete_prefix"), + get_bool_elem("autocomplete_relevance") ); // Update the grid on all open windows. pdgui.update_grid(get_bool_elem("show_grid"), @@ -1071,7 +1078,7 @@ function autopatch_yoffset_toggle(checked) { } function gui_prefs_callback(name, show_grid, grid_size, save_zoom, - autocomplete, autocomplete_prefix, + autocomplete, autocomplete_prefix, autocomplete_relevance, browser_doc, browser_path, browser_init, autopatch_yoffset) { var s = document.getElementById("gui_preset"); @@ -1107,6 +1114,7 @@ function gui_prefs_callback(name, show_grid, grid_size, save_zoom, document.getElementById("save_zoom").checked = !!save_zoom; document.getElementById("autocomplete").checked = !!autocomplete; document.getElementById("autocomplete_prefix").checked = !!autocomplete_prefix; + document.getElementById("autocomplete_relevance").checked = !!autocomplete_relevance; document.getElementById("browser_doc").checked = !!browser_doc; document.getElementById("browser_path").checked = !!browser_path; document.getElementById("browser_init").checked = !!browser_init; diff --git a/pd/nw/locales/de/translation.json b/pd/nw/locales/de/translation.json index 5ff39dab72c248374969be60f7a4a3ba1c88e59b..8a5d495192d2aa3d9604e57c129b5764adb08e0c 100644 --- a/pd/nw/locales/de/translation.json +++ b/pd/nw/locales/de/translation.json @@ -444,7 +444,9 @@ "autocomplete": "Autovervollständigung von Objekt-Namen und Argumenten (experimentell)", "autocomplete_tt": "Schlägt bei der Eingabe Vervollständigungen von Objekt-Namen und Argumenten vor", "autocomplete_prefix": "Vervollständigung per Objektnamens-Präfix", - "autocomplete_prefix_tt": "Vervollständigung nur per Objektnamens-Präfix (statt Übereinstimmung irgendwo im Objektnamen)" + "autocomplete_prefix_tt": "Vervollständigung nur per Objektnamens-Präfix (statt Übereinstimmung irgendwo im Objektnamen)", + "autocomplete_relevance": "Sortiere Vervollständigungen nach Relevanz", + "autocomplete_relevance_tt": "Zeigt die relevantesten Vervollständigungen zuerst (basierend auf der Nutzungshäufigkeit)" }, "browser": { "browser_title": "Hilfe-Browser-Einstellungen (WARNUNG: Änderungen können Startup-Zeiten beeinflussen!)", diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json index b18fd87d2dfd276122b6e312e76cf2132a734baf..d340835b204976d47a3c24a0b4d396540c971780 100644 --- a/pd/nw/locales/en/translation.json +++ b/pd/nw/locales/en/translation.json @@ -444,7 +444,9 @@ "autocomplete": "auto-complete object names and arguments (experimental)", "autocomplete_tt": "Offers completions for object names and arguments as you type", "autocomplete_prefix": "match completions by object name prefix", - "autocomplete_prefix_tt": "Only list completions whose prefix matches (rather than matches anywhere in object names)" + "autocomplete_prefix_tt": "Only list completions whose prefix matches (rather than matches anywhere in object names)", + "autocomplete_relevance": "sort completions by relevance", + "autocomplete_relevance_tt": "List most relevant completions first (based on how frequently they are used)" }, "browser": { "browser_title": "Help browser settings (WARNING: changing these may affect startup times!)", diff --git a/pd/nw/locales/fr/translation.json b/pd/nw/locales/fr/translation.json index f5dd974a93fe38c6b18b4a21d98d83cd9cb5920e..4721a9c13089a19b1fc91554409a9815f30da211 100644 --- a/pd/nw/locales/fr/translation.json +++ b/pd/nw/locales/fr/translation.json @@ -444,7 +444,9 @@ "autocomplete": "autocompléter les noms et arguments des objets (expérimental)", "autocomplete_tt": "Offre complétion pour les noms et arguments des objets au fur et à mesure de la saisie", "autocomplete_prefix": "complétion par le préfixe du nom de l'objet", - "autocomplete_prefix_tt": "Complétion uniquement par le préfixe du nom de l'objet (ignorer les correspondances au milieu du nom des objets)" + "autocomplete_prefix_tt": "Complétion uniquement par le préfixe du nom de l'objet (ignorer les correspondances au milieu du nom des objets)", + "autocomplete_relevance": "Trier les complétions par pertinence", + "autocomplete_relevance_tt": "Affiche les complétions les plus pertinentes en premier (basé sur la fréquence d'utilisation)" }, "browser": { "browser_title": "Paramètres du navigateur d'Aide (AVERTISSEMENT: les modifier peut affecter les temps de démarrage!)", diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js index 8c9441eba107b69c95010c3757b89240ba1f5fe1..05ee5068477a9c02722c5d6530cc60a5d946e41a 100644 --- a/pd/nw/pdgui.js +++ b/pd/nw/pdgui.js @@ -3,7 +3,7 @@ var pwd; var lib_dir; var help_path, browser_doc, browser_path, browser_init; -var autocomplete, autocomplete_prefix; +var autocomplete, autocomplete_prefix, autocomplete_relevance; var pd_engine_id; exports.autocomplete_enabled = function() { @@ -33,7 +33,7 @@ exports.set_pd_engine_id = function (id) { exports.defunkify_windows_path = defunkify_windows_path; function gui_set_browser_config(doc_flag, path_flag, init_flag, - ac_flag, ac_prefix_flag, + ac_flag, ac_prefix_flag, ac_relevance_flag, helppath) { // post("gui_set_browser_config: " + helppath.join(":")); browser_doc = doc_flag; @@ -49,6 +49,7 @@ function gui_set_browser_config(doc_flag, path_flag, init_flag, // user decides to enable it later. autocomplete = ac_flag; autocomplete_prefix = ac_prefix_flag; + autocomplete_relevance = ac_relevance_flag; make_completion_index(); // AG: Start building the keyword index for dialog_search.html. We do this // here so that we can be sure that lib_dir and help_path are known @@ -570,11 +571,12 @@ function rebuild_index() } // this is called from the gui tab of the prefs dialog -function update_browser(doc_flag, path_flag, ac_flag, ac_prefix_flag) +function update_browser(doc_flag, path_flag, ac_flag, ac_prefix_flag, ac_relevance_flag) { var changed = ac_flag == 1 && autocomplete == 0; autocomplete = ac_flag; autocomplete_prefix = ac_prefix_flag; + autocomplete_relevance = ac_relevance_flag; doc_flag = doc_flag?1:0; path_flag = path_flag?1:0; if (browser_doc !== doc_flag) { @@ -643,7 +645,25 @@ function search_arg(title, arg) { if (!autocomplete) return []; // for the arguments, we are only interested on the obj that match exactly the 'title', so we return only the args from this obj let results = completion_index.search({$and: [{"title": "=\"" + title + "\""}, {"args.text": "^\"" + arg + "\""}]}); - return (results.length > 0) ? results[0].matches : []; + if (results.length > 0) { + let args = results[0].item.args; + // AG: Matched args are in matches.slice(1,), extract them. + // This code originally just returned matches itself, which has the + // text of all matched arguments, but not the occurrence data, and we + // need the latter to sort based on relevance. + results = results[0].matches.slice(1,).map(a => args[a.refIndex]); + } + return results; +} + +function search_args(title) { + // like above, but look up *all* arg completions for a given object + if (!autocomplete) return []; + // for the arguments, we are only interested on the obj that match exactly the 'title', so we return only the args from this obj + let results = obj_exact_match(title); + // item.args is live data from the fuse, make sure to take a shallow copy + // with slice() which can be safely sorted in-place later + return (results.length > 0) ? results[0].item.args.slice() : []; } function index_obj_completion(obj_or_msg, obj_or_msg_text) { @@ -727,6 +747,13 @@ function select_result_autocomplete_dd(textbox, ac_dropdown, last, offs, res, di // We only come here if the user presses 'tab' and there is no // option selected. var n = res.length; + if (n == 0) { + // It seems that while pondering the mysteries of the + // universe, your computer has lost our completion list. This + // shouldn't happen, but a little bit of defensive programming + // can't hurt. + return [-1,-1]; + } var next = (dir==0 ? last : dir>0 ? last+1 : last<=0 ? n-1 : last-1) % n; // If the new index is outside the current scope of the popup, @@ -777,36 +804,37 @@ function repopulate_autocomplete_dd(doc, ac_dropdown, obj_class, text) { (3) We're in the middle of argument completion (started typing some arguments) in which case have_arg is true and arg is non-empty as well. */ - let results = (arg.length > 0) ? (search_arg(title, arg).slice(1,)) : have_arg ? (obj_exact_match(title)) : (search_obj(title)); - if (arg.length < 1 && have_arg && results.length > 0) { - results = results[0].item.args; - } + let results = (arg.length > 0) ? (search_arg(title, arg)) : have_arg ? (search_args(title)) : (search_obj(title)); /* AG: Massage the result list from what Fuse delivers, which is based on - scoring similarity and can appear pretty random at times. For now, we - just do a lexicographic sort on score first, then the completion text - (in particular, the latter makes sure that for each score the shortest - matches come first, which you'd expect but isn't always guaranteed with - Fuse). In the future we may incorporate the occurrence counts which are - already in GB's implementation, but AFAICT aren't currently used - anywhere. Finally, we condense the result list to a simple string list, - since we don't use all the other data any more beyond this point. */ - if (arg.length < 1 && have_arg) { - // all argument completions, order them lexicographically + scoring similarity and can appear pretty random at times. What we + actually want here is an order based on scores, which also takes into + account relevance (as determined by the occurences field), and, last + but not least, an alphabetic ordering of the available completions (in + particular, the latter makes sure that for each score and relevance the + shortest matches come first, which you'd expect but isn't always + guaranteed with Fuse). Finally, we condense the result list to a simple + string list, since we don't use all the other data anymore beyond this + point. */ + if (arg.length > 0 || have_arg) { + // argument completions, these don't have scores, order them by + // just relevance and text results.sort((a, b) => - a.text == b.text ? 0 : a.text < b.text ? -1 : 1); + a.occurrences == b.occurrences || !autocomplete_relevance + ? (a.text == b.text ? 0 : a.text < b.text ? -1 : 1) + : b.occurrences - a.occurrences); results = results.map(a => title + " " + a.text); - } else if (arg.length > 0) { - // matching arguments, order them lexicographically - results.sort((a, b) => - a.value == b.value ? 0 : a.value < b.value ? -1 : 1); - results = results.map(a => title + " " + a.value); } else { - // object completions, sort by score and item.title + // object completions, order by score, relevance and item.title results.sort(function (a, b) { if (a.score == b.score) { - return a.item.title == b.item.title ? 0 - : a.item.title < b.item.title ? -1 : 1; + if (a.item.occurrences == b.item.occurrences || + !autocomplete_relevance) { + return a.item.title == b.item.title ? 0 + : a.item.title < b.item.title ? -1 : 1; + } else { + return b.item.occurrences - a.item.occurrences; + } } else { let d = a.score - b.score; return d == 0 ? 0 : d < 0 ? -1 : 1; @@ -6888,12 +6916,12 @@ function gui_midi_properties(gfxstub, sys_indevs, sys_outdevs, } function gui_gui_properties(dummy, name, show_grid, grid_size, save_zoom, - autocomplete, autocomplete_prefix, + autocomplete, autocomplete_prefix, autocomplete_relevance, browser_doc, browser_path, browser_init, autopatch_yoffset) { if (dialogwin["prefs"] !== null) { dialogwin["prefs"].window.gui_prefs_callback(name, show_grid, grid_size, - save_zoom, autocomplete, autocomplete_prefix, + save_zoom, autocomplete, autocomplete_prefix, autocomplete_relevance, browser_doc, browser_path, browser_init, autopatch_yoffset); } } diff --git a/pd/src/m_glob.c b/pd/src/m_glob.c index d302ea9748dd717b061d0a03352db71c1da4e606..e4f9f85c92288149410c972668e9de98fc8ba83f 100644 --- a/pd/src/m_glob.c +++ b/pd/src/m_glob.c @@ -81,7 +81,7 @@ static void glob_perf(t_pd *dummy, float f) } extern int sys_snaptogrid, sys_gridsize, sys_zoom, - sys_autocomplete, sys_autocomplete_prefix, + sys_autocomplete, sys_autocomplete_prefix, sys_autocomplete_relevance, sys_browser_doc, sys_browser_path, sys_browser_init, sys_autopatch_yoffset; extern t_symbol *sys_gui_preset; @@ -93,6 +93,7 @@ static void glob_gui_prefs(t_pd *dummy, t_symbol *s, int argc, t_atom *argv) sys_zoom = !!atom_getintarg(0, argc--, argv++); sys_autocomplete = !!atom_getintarg(0, argc--, argv++); sys_autocomplete_prefix = !!atom_getintarg(0, argc--, argv++); + sys_autocomplete_relevance = !!atom_getintarg(0, argc--, argv++); sys_browser_doc = !!atom_getintarg(0, argc--, argv++); sys_browser_path = !!atom_getintarg(0, argc--, argv++); sys_browser_init = !!atom_getintarg(0, argc--, argv++); @@ -102,7 +103,7 @@ static void glob_gui_prefs(t_pd *dummy, t_symbol *s, int argc, t_atom *argv) /* just the gui-preset, the save-zoom toggle and various help browser options for now */ static void glob_gui_properties(t_pd *dummy) { - gui_vmess("gui_gui_properties", "xsiiiiiiiii", + gui_vmess("gui_gui_properties", "xsiiiiiiiiii", dummy, sys_gui_preset->s_name, sys_snaptogrid, @@ -110,6 +111,7 @@ static void glob_gui_properties(t_pd *dummy) sys_zoom, sys_autocomplete, sys_autocomplete_prefix, + sys_autocomplete_relevance, sys_browser_doc, sys_browser_path, sys_browser_init, diff --git a/pd/src/s_file.c b/pd/src/s_file.c index 02100c6214852e89159001d30e6aa8c079e591f1..a290215eec1ac8c51ed9183ca95855f25881507c 100644 --- a/pd/src/s_file.c +++ b/pd/src/s_file.c @@ -43,7 +43,8 @@ #endif int sys_defeatrt, sys_autopatch_yoffset, sys_snaptogrid = 1, sys_gridsize = 10, - sys_zoom, sys_autocomplete = 1, sys_autocomplete_prefix, + sys_zoom, + sys_autocomplete = 1, sys_autocomplete_prefix, sys_autocomplete_relevance, sys_browser_doc = 1, sys_browser_path, sys_browser_init; t_symbol *sys_flags = &s_; void sys_doflags( void); @@ -681,6 +682,8 @@ void sys_loadpreferences( void) sscanf(prefbuf, "%d", &sys_autocomplete); if (sys_getpreference("autocomplete_prefix", prefbuf, MAXPDSTRING)) sscanf(prefbuf, "%d", &sys_autocomplete_prefix); + if (sys_getpreference("autocomplete_relevance", prefbuf, MAXPDSTRING)) + sscanf(prefbuf, "%d", &sys_autocomplete_relevance); if (sys_getpreference("browser_doc", prefbuf, MAXPDSTRING)) sscanf(prefbuf, "%d", &sys_browser_doc); if (sys_getpreference("browser_path", prefbuf, MAXPDSTRING)) @@ -836,6 +839,8 @@ void glob_savepreferences(t_pd *dummy) sys_putpreference("autocomplete", buf1); sprintf(buf1, "%d", sys_autocomplete_prefix); sys_putpreference("autocomplete_prefix", buf1); + sprintf(buf1, "%d", sys_autocomplete_relevance); + sys_putpreference("autocomplete_relevance", buf1); sprintf(buf1, "%d", sys_browser_doc); sys_putpreference("browser_doc", buf1); sprintf(buf1, "%d", sys_browser_path); diff --git a/pd/src/s_main.c b/pd/src/s_main.c index e002b2c3f8945d9e44c34c61faf77197466c74be..0fb8cc06556a53053ba314b961296393bee3221e 100644 --- a/pd/src/s_main.c +++ b/pd/src/s_main.c @@ -331,7 +331,8 @@ void glob_forward_files_from_secondary_instance(void) extern void glob_recent_files(t_pd *dummy); extern int sys_browser_doc, sys_browser_path, sys_browser_init; -extern int sys_autocomplete, sys_autocomplete_prefix; +extern int sys_autocomplete, sys_autocomplete_prefix, + sys_autocomplete_relevance; /* this is called from main() in s_entry.c */ int sys_main(int argc, char **argv) @@ -412,9 +413,10 @@ int sys_main(int argc, char **argv) glob_recent_files(0); /* AG: send the browser config; this must come *after* gui_set_lib_dir so that the lib_dir is available when help indexing starts */ - gui_start_vmess("gui_set_browser_config", "iiiii", + gui_start_vmess("gui_set_browser_config", "iiiiii", sys_browser_doc, sys_browser_path, sys_browser_init, - sys_autocomplete, sys_autocomplete_prefix); + sys_autocomplete, sys_autocomplete_prefix, + sys_autocomplete_relevance); gui_start_array(); for (nl = sys_helppath; nl; nl = nl->nl_next) {