From 13b556a6887491b381b00e29d0a7241ac724ecb5 Mon Sep 17 00:00:00 2001 From: Chorgard <136748442+chorgard@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:27:23 -0700 Subject: [PATCH 1/3] Add support for Batch highlighting --- assets/highlighting-tests/batch.bat | 4 ++ assets/highlighting-tests/markdown.md | 10 ++++ crates/lsh/definitions/batch.lsh | 78 +++++++++++++++++++++++++++ crates/lsh/definitions/markdown.lsh | 10 ++++ 4 files changed, 102 insertions(+) create mode 100644 crates/lsh/definitions/batch.lsh diff --git a/assets/highlighting-tests/batch.bat b/assets/highlighting-tests/batch.bat index 962ef66007a..ed104aeaf0d 100644 --- a/assets/highlighting-tests/batch.bat +++ b/assets/highlighting-tests/batch.bat @@ -4,6 +4,7 @@ REM --- String, Variable, Label, Command, Operator, Number, Delimiter, Comment - :: Label :Start + :: Variable assignment and usage set "VAR1=Hello" set VAR2=World @@ -14,6 +15,9 @@ set "STR=Batch ^& CMD!" :: Arithmetic operation (number, operator) set /a SUM=5+10 +:: Arguments (variable, string) +echo Message from %0, referred to as "%~n0" + :: IF statement (keyword, operator, string, variable) if "%VAR1%"=="Hello" ( echo %VAR1%, %VAR2%! %STR% diff --git a/assets/highlighting-tests/markdown.md b/assets/highlighting-tests/markdown.md index 555b182f961..2572b607ecd 100644 --- a/assets/highlighting-tests/markdown.md +++ b/assets/highlighting-tests/markdown.md @@ -66,6 +66,16 @@ Reference: ![Logo][logo-ref] echo "Hello, world" | tr a-z A-Z ``` +```batch +@echo off + +if not "%~1"=="" ( + echo Hello %~1! +) else ( + echo Hi, I'm Batch, nice to meet you! +) +``` + ```javascript export function greet(name) { return `hello ${name}`; diff --git a/crates/lsh/definitions/batch.lsh b/crates/lsh/definitions/batch.lsh new file mode 100644 index 00000000000..f9f422e9e92 --- /dev/null +++ b/crates/lsh/definitions/batch.lsh @@ -0,0 +1,78 @@ +#[display_name = "Batch"] +#[path = "**/*.cmd"] +#[path = "**/*.bat"] +#[path = "**/*.btm"] + + +pub fn batch() { + until /$/ { + yield other; + + if /::.*/ { + yield comment; + } + else if /([Rr][Ee][Mm])\s(.*)/ { // Now, I would do this for all of the rest, but that would be extremely sloppy and hard to read. + yield $1 as keyword.other; + yield $2 as comment; + } + else if /\%\%\w/ { + yield variable; + } + else if /\^./ { // Escapes + yield markup.italic; + } + else if /\%[A-Za-z0-9_\s]+\%/ { + yield variable; + } + else if /![A-Za-z0-9_\s]+!/ { + yield variable; + } + else if /\%(?:\*|~[a-z0-9_]+|[0-9])/ { // Special variables (%~1, %~n1) + yield variable; + } + else if /:\w+/ { + yield method; // Most suiting out of the options that I could find that looked good + } + else if /"/ { + until /$/ { + yield string; + if /\^./ { // Escapes + yield markup.italic; + } + else if /\%[A-Za-z0-9_]+\%/ { // Variables + yield variable; + } + else if /\![A-Za-z0-9_]+\!/ { // More variables + yield variable; + } + else if /\%(?:\*|~[a-z0-9_]+|[0-9])/ { + yield variable; + } + else if /"/ { + yield string; + break; + } + await input; + } + } else if /(?:FOR|IF|ELSE|CALL|GOTO|IN|DO|EXIST|NOT)\>/ { // Upper + yield keyword.control; + } else if /(?:for|if|else|call|goto|in|do|exist|not)\>/ { // Lower + yield keyword.control; + } else if /(?:ASSOC|ATTRIB|BREAK|CTRL|CHOICE|BCDEDIT|CACLS|ACL|CD|CHCP|CHDIR|CHKDSK|CHKNTFS|CLS|CMD|COLOR|COMP|COMPACT|NTFS|CONVERT|FAT|NTFS|COPY|DATE|DEL|DIR|DISKPART|DOSKEY|DRIVERQUERY|ECHO|ENDLOCAL|ERASE|EXIT|CMD|EXE|FC|FIND|FINDSTR|FORMAT|FSUTIL|FTYPE|GPRESULT|GRAFTABL|HELP|ICACLS|ACL|LABEL|MD|MKDIR|MKLINK|MODE|MORE|MOVE|OPENFILES|PATH|PAUSE|POPD|PUSHD|PRINT|PROMPT|PUSHD|RD|RECOVER|CONFIG|SYS|REN|RENAME|REPLACE|RMDIR|ROBOCOPY|SET|SETLOCAL|SC|SCHTASKS|SHIFT|SHUTDOWN|SORT|START|SUBST|SYSTEMINFO|TASKLIST|TASKKILL|TIME|TITLE|CMD|EXE|TREE|TYPE|VER|VERIFY|VOL|XCOPY|WMIC|WMI)\>/ { // Upper + yield keyword.other; + } else if /(?:assoc|attrib|break|ctrl|bcdedit|cacls|acl|cd|chcp|chdir|chkdsk|chkntfs|cls|cmd|color|comp|compact|ntfs|convert|fat|ntfs|copy|date|del|dir|diskpart|doskey|driverquery|echo|endlocal|erase|exit|cmd|exe|fc|find|findstr|format|fsutil|ftype|gpresult|graftabl|help|icacls|acl|label|md|mkdir|mklink|mode|more|move|openfiles|path|pause|popd|pushd|print|prompt|pushd|rd|recover|config|sys|ren|rename|replace|rmdir|robocopy|set|setlocal|sc|schtasks|shift|shutdown|sort|start|subst|systeminfo|tasklist|taskkill|time|title|cmd|exe|tree|type|ver|verify|vol|xcopy|wmic|wmi|choice)\>/ { // Lower + yield keyword.other; + } + else if /(?i:-?(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+|[\d_]+\.?[\d_]*|\.[\d_]+)(?:e[+-]?[\d_]+)?)n?/ { + if /\w+/ { + // Invalid numeric literal + } else { + yield constant.numeric; + } + } else if /\w+/ { + // Gobble word chars to align the next iteration on a word boundary. + } + + yield other; + } +} diff --git a/crates/lsh/definitions/markdown.lsh b/crates/lsh/definitions/markdown.lsh index 77a005fe925..396c1de1cb5 100644 --- a/crates/lsh/definitions/markdown.lsh +++ b/crates/lsh/definitions/markdown.lsh @@ -28,6 +28,16 @@ pub fn markdown() { if /.*/ {} } } + } else if /(?i:batch|bat)/ { + loop { + await input; + if /\s*```/ { + return; + } else { + batch(); + if /.*/ {} + } + } } else if /(?i:diff)/ { loop { await input; From bf6a65a91dbf4ef2fc6920ed22ae4dc0dbe7d410 Mon Sep 17 00:00:00 2001 From: Chorgard <136748442+chorgard@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:00:04 -0700 Subject: [PATCH 2/3] Update batch.lsh --- crates/lsh/definitions/batch.lsh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/lsh/definitions/batch.lsh b/crates/lsh/definitions/batch.lsh index f9f422e9e92..399943b7d06 100644 --- a/crates/lsh/definitions/batch.lsh +++ b/crates/lsh/definitions/batch.lsh @@ -58,9 +58,9 @@ pub fn batch() { yield keyword.control; } else if /(?:for|if|else|call|goto|in|do|exist|not)\>/ { // Lower yield keyword.control; - } else if /(?:ASSOC|ATTRIB|BREAK|CTRL|CHOICE|BCDEDIT|CACLS|ACL|CD|CHCP|CHDIR|CHKDSK|CHKNTFS|CLS|CMD|COLOR|COMP|COMPACT|NTFS|CONVERT|FAT|NTFS|COPY|DATE|DEL|DIR|DISKPART|DOSKEY|DRIVERQUERY|ECHO|ENDLOCAL|ERASE|EXIT|CMD|EXE|FC|FIND|FINDSTR|FORMAT|FSUTIL|FTYPE|GPRESULT|GRAFTABL|HELP|ICACLS|ACL|LABEL|MD|MKDIR|MKLINK|MODE|MORE|MOVE|OPENFILES|PATH|PAUSE|POPD|PUSHD|PRINT|PROMPT|PUSHD|RD|RECOVER|CONFIG|SYS|REN|RENAME|REPLACE|RMDIR|ROBOCOPY|SET|SETLOCAL|SC|SCHTASKS|SHIFT|SHUTDOWN|SORT|START|SUBST|SYSTEMINFO|TASKLIST|TASKKILL|TIME|TITLE|CMD|EXE|TREE|TYPE|VER|VERIFY|VOL|XCOPY|WMIC|WMI)\>/ { // Upper + } else if /(?:ASSOC|ATTRIB|BREAK|CHOICE|BCDEDIT|CACLS|CD|CHCP|CHDIR|CHKDSK|CHKNTFS|CLS|CMD|COLOR|COMP|COMPACT|CONVERT|COPY|DATE|DEL|DIR|DISKPART|DOSKEY|DRIVERQUERY|ECHO|ENDLOCAL|ERASE|EXIT|FC|FIND|FINDSTR|FORMAT|FSUTIL|FTYPE|GPRESULT|HELP|ICACLS|LABEL|MD|MKDIR|MKLINK|MODE|MORE|MOVE|OPENFILES|PATH|PAUSE|POPD|PUSHD|PRINT|PROMPT|PUSHD|RD|RECOVER|CONFIG|REN|RENAME|REPLACE|RMDIR|ROBOCOPY|SET|SETLOCAL|SC|SCHTASKS|SHIFT|SHUTDOWN|SORT|START|SUBST|SYSTEMINFO|TASKLIST|TASKKILL|TIME|TITLE|TREE|TYPE|VER|VERIFY|VOL|XCOPY|WMIC)\>/ { // Upper yield keyword.other; - } else if /(?:assoc|attrib|break|ctrl|bcdedit|cacls|acl|cd|chcp|chdir|chkdsk|chkntfs|cls|cmd|color|comp|compact|ntfs|convert|fat|ntfs|copy|date|del|dir|diskpart|doskey|driverquery|echo|endlocal|erase|exit|cmd|exe|fc|find|findstr|format|fsutil|ftype|gpresult|graftabl|help|icacls|acl|label|md|mkdir|mklink|mode|more|move|openfiles|path|pause|popd|pushd|print|prompt|pushd|rd|recover|config|sys|ren|rename|replace|rmdir|robocopy|set|setlocal|sc|schtasks|shift|shutdown|sort|start|subst|systeminfo|tasklist|taskkill|time|title|cmd|exe|tree|type|ver|verify|vol|xcopy|wmic|wmi|choice)\>/ { // Lower + } else if /(?:assoc|attrib|break|choice|bcdedit|cacls|cd|chcp|chdir|chkdsk|chkntfs|cls|cmd|color|comp|compact|convert|copy|date|del|dir|diskpart|doskey|driverquery|echo|endlocal|erase|exit|fc|find|findstr|format|fsutil|ftype|gpresult|help|icacls|label|md|mkdir|mklink|mode|more|move|openfiles|path|pause|popd|pushd|print|prompt|pushd|rd|recover|config|ren|rename|replace|rmdir|robocopy|set|setlocal|sc|schtasks|shift|shutdown|sort|start|subst|systeminfo|tasklist|taskkill|time|title|tree|type|ver|verify|vol|xcopy|wmic)\>/ { // Lower yield keyword.other; } else if /(?i:-?(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+|[\d_]+\.?[\d_]*|\.[\d_]+)(?:e[+-]?[\d_]+)?)n?/ { From 4c60b7bcb25be977cd2bb8ba43391c762de84769 Mon Sep 17 00:00:00 2001 From: Chorgard <136748442+chorgard@users.noreply.github.com> Date: Thu, 18 Jun 2026 23:57:45 -0700 Subject: [PATCH 3/3] Refactor regexes (boiling down) Make figure out the `?i:` situation and boil down the variable regex, and in doing so, reformat the document. --- crates/lsh/definitions/batch.lsh | 98 +++++++++++++------------------- 1 file changed, 38 insertions(+), 60 deletions(-) diff --git a/crates/lsh/definitions/batch.lsh b/crates/lsh/definitions/batch.lsh index 399943b7d06..1b65bf1d93d 100644 --- a/crates/lsh/definitions/batch.lsh +++ b/crates/lsh/definitions/batch.lsh @@ -5,74 +5,52 @@ pub fn batch() { - until /$/ { - yield other; - - if /::.*/ { - yield comment; - } - else if /([Rr][Ee][Mm])\s(.*)/ { // Now, I would do this for all of the rest, but that would be extremely sloppy and hard to read. - yield $1 as keyword.other; - yield $2 as comment; - } - else if /\%\%\w/ { - yield variable; + until /$/ { + yield other; + + if /::.*/ { + yield comment; + } + else if /(?i:rem)\>\s*(.*)/ { + yield keyword.other; + yield $1 as comment; } else if /\^./ { // Escapes yield markup.italic; } - else if /\%[A-Za-z0-9_\s]+\%/ { - yield variable; - } - else if /![A-Za-z0-9_\s]+!/ { - yield variable; - } - else if /\%(?:\*|~[a-z0-9_]+|[0-9])/ { // Special variables (%~1, %~n1) - yield variable; + else if /%%~[fdpnxsatz]{0,6}[A-Za-z]?|%%[A-Za-z]|\%(?:[0-9]|[\w\s]+\%|\*|~[A-Za-z]*[0-9])|![\w\s]+!/ { // What even is a variable anymore + yield variable; } else if /:\w+/ { yield method; // Most suiting out of the options that I could find that looked good } else if /"/ { - until /$/ { - yield string; - if /\^./ { // Escapes - yield markup.italic; - } - else if /\%[A-Za-z0-9_]+\%/ { // Variables - yield variable; - } - else if /\![A-Za-z0-9_]+\!/ { // More variables - yield variable; - } - else if /\%(?:\*|~[a-z0-9_]+|[0-9])/ { - yield variable; - } - else if /"/ { - yield string; - break; - } - await input; - } - } else if /(?:FOR|IF|ELSE|CALL|GOTO|IN|DO|EXIST|NOT)\>/ { // Upper - yield keyword.control; - } else if /(?:for|if|else|call|goto|in|do|exist|not)\>/ { // Lower - yield keyword.control; - } else if /(?:ASSOC|ATTRIB|BREAK|CHOICE|BCDEDIT|CACLS|CD|CHCP|CHDIR|CHKDSK|CHKNTFS|CLS|CMD|COLOR|COMP|COMPACT|CONVERT|COPY|DATE|DEL|DIR|DISKPART|DOSKEY|DRIVERQUERY|ECHO|ENDLOCAL|ERASE|EXIT|FC|FIND|FINDSTR|FORMAT|FSUTIL|FTYPE|GPRESULT|HELP|ICACLS|LABEL|MD|MKDIR|MKLINK|MODE|MORE|MOVE|OPENFILES|PATH|PAUSE|POPD|PUSHD|PRINT|PROMPT|PUSHD|RD|RECOVER|CONFIG|REN|RENAME|REPLACE|RMDIR|ROBOCOPY|SET|SETLOCAL|SC|SCHTASKS|SHIFT|SHUTDOWN|SORT|START|SUBST|SYSTEMINFO|TASKLIST|TASKKILL|TIME|TITLE|TREE|TYPE|VER|VERIFY|VOL|XCOPY|WMIC)\>/ { // Upper - yield keyword.other; - } else if /(?:assoc|attrib|break|choice|bcdedit|cacls|cd|chcp|chdir|chkdsk|chkntfs|cls|cmd|color|comp|compact|convert|copy|date|del|dir|diskpart|doskey|driverquery|echo|endlocal|erase|exit|fc|find|findstr|format|fsutil|ftype|gpresult|help|icacls|label|md|mkdir|mklink|mode|more|move|openfiles|path|pause|popd|pushd|print|prompt|pushd|rd|recover|config|ren|rename|replace|rmdir|robocopy|set|setlocal|sc|schtasks|shift|shutdown|sort|start|subst|systeminfo|tasklist|taskkill|time|title|tree|type|ver|verify|vol|xcopy|wmic)\>/ { // Lower - yield keyword.other; - } + until /$/ { + yield string; + if /\^./ { + yield markup.italic; + } else if /%%~[fdpnxsatz]{0,6}[A-Za-z]?|%%[A-Za-z]|\%(?:[0-9]|[\w\s]+\%|\*|~[A-Za-z]*[0-9])|![\w\s]+!/ { + yield variable; + } else if /"/ { + yield string; + break; + } + } + } else if /(?i:for|if|else|call|goto|in|do|exist|not)\>/ { + yield keyword.control; + } else if /(?i:assoc|attrib|break|bcdedit|cacls|cd|chcp|chdir|chkdsk|chkntfs|choice|cls|cmd|color|comp|compact|convert|copy|date|del|dir|diskpart|doskey|driverquery|echo|endlocal|erase|exit|fc|find|findstr|format|fsutil|ftype|gpresult|help|icacls|label|md|mkdir|mklink|mode|more|move|openfiles|path|pause|popd|pushd|print|prompt|pushd|rd|recover|config|ren|rename|replace|rmdir|robocopy|set|setlocal|sc|schtasks|shift|shutdown|sort|start|subst|systeminfo|tasklist|taskkill|time|title|tree|type|ver|verify|vol|xcopy|wmic)\>/ { // Finally got the case insensitivity working properly! + yield keyword.other; + } else if /(?i:-?(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+|[\d_]+\.?[\d_]*|\.[\d_]+)(?:e[+-]?[\d_]+)?)n?/ { - if /\w+/ { - // Invalid numeric literal - } else { - yield constant.numeric; - } - } else if /\w+/ { - // Gobble word chars to align the next iteration on a word boundary. - } - - yield other; - } + if /\w+/ { + // Invalid numeric literal + } else { + yield constant.numeric; + } + } else if /\w+/ { + // Gobble word chars to align the next iteration on a word boundary. + } + + yield other; + } }