From cfdb80ed48083c96e851ad6428f27de92be6d080 Mon Sep 17 00:00:00 2001 From: Harsh <116981900+reharsh@users.noreply.github.com> Date: Sun, 27 Apr 2025 03:20:42 +0530 Subject: [PATCH] typeahead: Replace code block language hint text with more clear options This commit - Replace the blank option with an italicized option that's the current default language, if there is one selected with "default" label. - Make the "text" option more informative by adding (no highlighting) to the label. - Remove the hint for "text". - Prioritize as left to right, before start typing: blank/default language, text, quote, spoiler, math, everything else... fixes: #33682 --- tools/setup/build_pygments_data | 1 + tools/setup/lang.json | 7 ++-- web/src/composebox_typeahead.ts | 36 +++++++++++-------- web/src/typeahead_helper.ts | 10 +++++- web/styles/zulip.css | 1 + web/templates/typeahead_list_item.hbs | 2 +- web/tests/composebox_typeahead.test.cjs | 5 ++- web/tests/realm_playground.test.cjs | 16 ++++++--- web/tests/typeahead_helper.test.cjs | 47 +++++++++++++++++++++---- 9 files changed, 93 insertions(+), 32 deletions(-) diff --git a/tools/setup/build_pygments_data b/tools/setup/build_pygments_data index 07e631365d..6e94b29165 100755 --- a/tools/setup/build_pygments_data +++ b/tools/setup/build_pygments_data @@ -6,6 +6,7 @@ from pygments.lexers import get_all_lexers ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") # The current priorities data set is based on StackOverflow's 2020 survey. +# We also prioritize text, quote, math, spoiler over others to enhance UX. DATA_PATH = os.path.join(ZULIP_PATH, "tools", "setup", "lang.json") OUT_PATH = os.path.join(ZULIP_PATH, "web", "generated", "pygments_data.json") diff --git a/tools/setup/lang.json b/tools/setup/lang.json index 371118f821..6e591988e5 100644 --- a/tools/setup/lang.json +++ b/tools/setup/lang.json @@ -1,5 +1,6 @@ { "default": { + "text": 31, "javascript": 27, "python": 26, "java": 25, @@ -30,9 +31,9 @@ }, "custom": { "latex": 10, - "math": 5, - "quote": 5, - "spoiler": 5 + "math": 28, + "quote": 30, + "spoiler": 29 }, "aliases": { "js": 27, diff --git a/web/src/composebox_typeahead.ts b/web/src/composebox_typeahead.ts index 0096fe9f9a..fe3f64b8c6 100644 --- a/web/src/composebox_typeahead.ts +++ b/web/src/composebox_typeahead.ts @@ -843,11 +843,12 @@ export function get_candidates( } completing = "syntax"; token = current_token; - // If the code formatting button was triggered, we want to show a blank option - // to improve the discoverability of the possibility of specifying a language. - const language_list = compose_ui.code_formatting_button_triggered - ? ["", ...realm_playground.get_pygments_typeahead_list_for_composebox()] - : realm_playground.get_pygments_typeahead_list_for_composebox(); + + const default_language = realm.realm_default_code_block_language; + const language_list = realm_playground.get_pygments_typeahead_list_for_composebox(); + if (default_language) { + language_list.unshift(default_language); + } compose_ui.set_code_formatting_button_triggered(false); const matcher = get_language_matcher(token); const matches = language_list.filter((item) => matcher(item)); @@ -1109,7 +1110,12 @@ export function content_highlighter_html(item: TypeaheadSuggestion): string | un case "stream": return typeahead_helper.render_stream(item); case "syntax": - return typeahead_helper.render_typeahead_item({primary: item.language}); + return typeahead_helper.render_typeahead_item({ + primary: item.language, + is_default_language: + item.language !== "" && + item.language === realm.realm_default_code_block_language, + }); case "topic_jump": return typeahead_helper.render_typeahead_item({primary: item.message}); case "topic_list": { @@ -1415,15 +1421,6 @@ function get_footer_html(): string | false { case "silent_mention": tip_text = $t({defaultMessage: "This silent mention won't trigger notifications."}); break; - case "syntax": - if (realm.realm_default_code_block_language !== "") { - tip_text = $t( - {defaultMessage: "Default is {language}. Use 'text' to disable highlighting."}, - {language: realm.realm_default_code_block_language}, - ); - break; - } - return false; default: return false; } @@ -1465,6 +1462,15 @@ export function initialize_compose_typeahead($element: JQuery${$t({defaultMessage: "New"})}`; } + } else if (item.type === "syntax") { + if ( + item.language !== "" && + item.language === realm.realm_default_code_block_language + ) { + return `${$t({defaultMessage: "(default)"})}`; + } else if (item.language === "text") { + return `${$t({defaultMessage: "(no highlighting)"})}`; + } } return false; }, diff --git a/web/src/typeahead_helper.ts b/web/src/typeahead_helper.ts index e26b577139..c05657b8bd 100644 --- a/web/src/typeahead_helper.ts +++ b/web/src/typeahead_helper.ts @@ -113,6 +113,7 @@ export let render_typeahead_item = (args: { topic_object?: TopicSuggestion; is_stream_topic?: boolean; is_empty_string_topic?: boolean; + is_default_language?: boolean; }): string => { const has_image = args.img_src !== undefined; const has_status = args.status_emoji_info !== undefined; @@ -434,8 +435,15 @@ function retain_unique_language_aliases(matches: string[]): string[] { export function sort_languages(matches: LanguageSuggestion[], query: string): LanguageSuggestion[] { const languages = matches.map((object) => object.language); + const default_language = realm.realm_default_code_block_language; const results = typeahead.triage(query, languages, (x) => x, compare_language); - const unique_languages = retain_unique_language_aliases([...results.matches, ...results.rest]); + let language_results; + if (default_language && results.matches.includes(default_language)) { + language_results = [default_language, ...results.matches, ...results.rest]; + } else { + language_results = [...results.matches, ...results.rest]; + } + const unique_languages = retain_unique_language_aliases(language_results); return unique_languages.map((language) => ({ language, type: "syntax", diff --git a/web/styles/zulip.css b/web/styles/zulip.css index a22ba021a9..c973015115 100644 --- a/web/styles/zulip.css +++ b/web/styles/zulip.css @@ -2349,6 +2349,7 @@ body:not(.spectator-view) { } } +.default-language-display, .empty-topic-display, .empty-topic-placeholder-display::placeholder { font-style: italic; diff --git a/web/templates/typeahead_list_item.hbs b/web/templates/typeahead_list_item.hbs index 5d36ae7ecd..3c681c8a3f 100644 --- a/web/templates/typeahead_list_item.hbs +++ b/web/templates/typeahead_list_item.hbs @@ -26,7 +26,7 @@ {{else}} {{!-- Separate container to ensure overflowing text remains in this container. --}}
- + {{~#if stream~}} {{~> inline_decorated_channel_name stream=stream ~}} {{~else~}} diff --git a/web/tests/composebox_typeahead.test.cjs b/web/tests/composebox_typeahead.test.cjs index 4ddbf67773..5a2d0ed84f 100644 --- a/web/tests/composebox_typeahead.test.cjs +++ b/web/tests/composebox_typeahead.test.cjs @@ -2306,7 +2306,10 @@ test("content_highlighter_html", ({override_rewire}) => { ct.get_or_set_completing_for_tests("syntax"); th_render_typeahead_item_called = false; override_rewire(typeahead_helper, "render_typeahead_item", (item) => { - assert.deepEqual(item, {primary: "py"}); + assert.deepEqual(item, { + is_default_language: false, + primary: "py", + }); th_render_typeahead_item_called = true; }); ct.content_highlighter_html({type: "syntax", language: "py"}); diff --git a/web/tests/realm_playground.test.cjs b/web/tests/realm_playground.test.cjs index 269d93bd63..3890a98de3 100644 --- a/web/tests/realm_playground.test.cjs +++ b/web/tests/realm_playground.test.cjs @@ -78,6 +78,10 @@ run_test("get_pygments_typeahead_list_for_settings", () => { let iterator = candidates.entries(); assert.equal(iterator.next().value[1], $t({defaultMessage: "Custom language: custom_lang"})); assert.equal(iterator.next().value[1], $t({defaultMessage: "Custom language: invent_a_lang"})); + assert.equal(iterator.next().value[1], "Text only (text, text)"); + assert.equal(iterator.next().value[1], "quote (quote, quote)"); + assert.equal(iterator.next().value[1], "spoiler (spoiler, spoiler)"); + assert.equal(iterator.next().value[1], "math (math, math)"); assert.equal(iterator.next().value[1], "JavaScript (javascript, js, javascript, js)"); assert.equal( iterator.next().value[1], @@ -96,6 +100,10 @@ run_test("get_pygments_typeahead_list_for_settings", () => { ); assert.equal(iterator.next().value[1], $t({defaultMessage: "Custom language: custom_lang"})); assert.equal(iterator.next().value[1], $t({defaultMessage: "Custom language: invent_a_lang"})); + assert.equal(iterator.next().value[1], "Text only (text, text)"); + assert.equal(iterator.next().value[1], "quote (quote, quote)"); + assert.equal(iterator.next().value[1], "spoiler (spoiler, spoiler)"); + assert.equal(iterator.next().value[1], "math (math, math)"); assert.equal(iterator.next().value[1], "JavaScript (javascript, js, javascript, js)"); assert.equal( iterator.next().value[1], @@ -107,9 +115,7 @@ run_test("get_pygments_typeahead_list_for_settings", () => { iterator = candidates.entries(); assert.equal(iterator.next().value[1], $t({defaultMessage: "Custom language: invent_a_lang"})); assert.equal(iterator.next().value[1], $t({defaultMessage: "Custom language: custom_lang"})); - assert.equal(iterator.next().value[1], "JavaScript (javascript, js, javascript, js)"); - assert.equal( - iterator.next().value[1], - "Python (python, bazel, py, py3, pyi, python3, sage, starlark, python, bazel, py, py3, pyi, python3, sage, starlark)", - ); + assert.equal(iterator.next().value[1], "Text only (text, text)"); + assert.equal(iterator.next().value[1], "quote (quote, quote)"); + assert.equal(iterator.next().value[1], "spoiler (spoiler, spoiler)"); }); diff --git a/web/tests/typeahead_helper.test.cjs b/web/tests/typeahead_helper.test.cjs index 9696d18a5d..ab12ebab54 100644 --- a/web/tests/typeahead_helper.test.cjs +++ b/web/tests/typeahead_helper.test.cjs @@ -394,12 +394,15 @@ test("sort_streams", ({override, override_rewire}) => { function language_items(languages) { return languages.map((language) => ({ - type: "syntax", language, + type: "syntax", })); } -test("sort_languages", ({override_rewire}) => { +test("sort_languages", ({override, override_rewire}) => { + override(realm, "realm_default_code_block_language", "dart"); + const default_language = realm.realm_default_code_block_language; + override_rewire(pygments_data, "langs", { python: {priority: 26}, javascript: {priority: 27}, @@ -407,20 +410,52 @@ test("sort_languages", ({override_rewire}) => { pascal: {priority: 15}, perl: {priority: 3}, css: {priority: 21}, + spoiler: {priority: 29}, + text: {priority: 31}, + quote: {priority: 30}, + math: {priority: 28}, }); - let test_langs = language_items(["pascal", "perl", "php", "python", "javascript"]); + let test_langs = language_items(["pascal", "perl", "php", "python", "spoiler", "javascript"]); test_langs = th.sort_languages(test_langs, "p"); // Sort languages by matching first letter, and then by popularity - assert.deepEqual(test_langs, language_items(["python", "php", "pascal", "perl", "javascript"])); + assert.deepEqual( + test_langs, + language_items(["python", "php", "pascal", "perl", "spoiler", "javascript"]), + ); // Test if popularity between two languages are the same pygments_data.langs.php = {priority: 26}; - test_langs = language_items(["pascal", "perl", "php", "python", "javascript"]); + test_langs = language_items(["pascal", "perl", "php", "python", "spoiler", "javascript"]); test_langs = th.sort_languages(test_langs, "p"); - assert.deepEqual(test_langs, language_items(["php", "python", "pascal", "perl", "javascript"])); + assert.deepEqual( + test_langs, + language_items(["php", "python", "pascal", "perl", "spoiler", "javascript"]), + ); + + test_langs = language_items([ + default_language, + "text", + "quote", + "math", + "python", + "javascript", + ]); + const test_langs_for_default = th.sort_languages(test_langs, "d"); + + assert.deepEqual( + test_langs_for_default, + language_items([default_language, "text", "quote", "math", "javascript", "python"]), + ); + + test_langs = th.sort_languages(test_langs, "t"); + + assert.deepEqual( + test_langs, + language_items(["text", "quote", "math", "javascript", "python", default_language]), + ); }); test("sort_languages on actual data", () => {