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
This commit is contained in:
Harsh
2025-04-27 03:20:42 +05:30
committed by Tim Abbott
parent 1dcda2ba9d
commit cfdb80ed48
9 changed files with 93 additions and 32 deletions

View File

@@ -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")

View File

@@ -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,

View File

@@ -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<HTMLTextAreaElemen
if (item.is_new_topic) {
return `<em>${$t({defaultMessage: "New"})}</em>`;
}
} else if (item.type === "syntax") {
if (
item.language !== "" &&
item.language === realm.realm_default_code_block_language
) {
return `<em>${$t({defaultMessage: "(default)"})}</em>`;
} else if (item.language === "text") {
return `<em>${$t({defaultMessage: "(no highlighting)"})}</em>`;
}
}
return false;
},

View File

@@ -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",

View File

@@ -2349,6 +2349,7 @@ body:not(.spectator-view) {
}
}
.default-language-display,
.empty-topic-display,
.empty-topic-placeholder-display::placeholder {
font-style: italic;

View File

@@ -26,7 +26,7 @@
{{else}}
{{!-- Separate container to ensure overflowing text remains in this container. --}}
<div class="typeahead-text-container{{#if has_secondary_html}} has_secondary_html{{/if}}">
<strong class="typeahead-strong-section{{#if is_empty_string_topic}} empty-topic-display{{/if}}">
<strong class="typeahead-strong-section{{#if is_empty_string_topic}} empty-topic-display{{/if}}{{#if is_default_language}} default-language-display{{/if}}">
{{~#if stream~}}
{{~> inline_decorated_channel_name stream=stream ~}}
{{~else~}}

View File

@@ -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"});

View File

@@ -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)");
});

View File

@@ -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", () => {