From f8c4f97f2ea185ef3b317cb5aad2f97faea3182a Mon Sep 17 00:00:00 2001 From: Miro Kakkonen Date: Wed, 8 Apr 2026 19:46:27 +0300 Subject: [PATCH 1/2] Add C/C++ highlighting --- assets/highlighting-tests/c.c | 93 +++++++++++++++++++++ assets/highlighting-tests/cpp.cpp | 112 ++++++++++++++++++++++++++ assets/highlighting-tests/markdown.md | 18 +++++ crates/lsh/definitions/c.lsh | 63 +++++++++++++++ crates/lsh/definitions/cpp.lsh | 66 +++++++++++++++ crates/lsh/definitions/markdown.lsh | 20 +++++ 6 files changed, 372 insertions(+) create mode 100644 assets/highlighting-tests/c.c create mode 100644 assets/highlighting-tests/cpp.cpp create mode 100644 crates/lsh/definitions/c.lsh create mode 100644 crates/lsh/definitions/cpp.lsh diff --git a/assets/highlighting-tests/c.c b/assets/highlighting-tests/c.c new file mode 100644 index 00000000000..40fdc016521 --- /dev/null +++ b/assets/highlighting-tests/c.c @@ -0,0 +1,93 @@ +// Comments +// Single-line comment + +/* + * Multi-line + * comment + */ + +#include +#include +#define MAX_BUFFER 1024 +#ifndef MY_HEADER_H +#define MY_HEADER_H +#endif + +// Numbers +42; +3.14; +.5; +1e10; +1.5e-3; +0xff; +0xFF; +0b1010; +0o77; +42ul; +3.14f; + +// Constants +true; +false; +NULL; + +// Strings and characters +'a'; +'\n'; +"double quotes with escape: \" \n \t \\"; + +// Control flow keywords +int main(int argc, char *argv[]) { + if (argc > 1) { + // ... + } else if (argc == 0) { + // ... + } else { + // ... + } + + for (int i = 0; i < 10; i++) { + if (i == 5) continue; + if (i == 8) break; + } + + while (false) { } + do { } while (false); + + switch (argc) { + case 1: + break; + default: + goto end; + } + +end: + return 0; +} + +// Other keywords and structures +struct Point { + int x; + int y; +}; + +typedef union { + char c; + double d; +} DataUnion; + +enum Color { RED, GREEN, BLUE }; + +extern int global_var; +static const int MAX_RETRIES = 5; +volatile int hardware_register; +register int fast_loop_counter; + +// C99/C11 specific keywords +_Atomic int atomic_counter; +_Bool is_active; +_Noreturn void die(void); + +// Function calls +printf("hello\n"); +sizeof(struct Point); diff --git a/assets/highlighting-tests/cpp.cpp b/assets/highlighting-tests/cpp.cpp new file mode 100644 index 00000000000..caf893a3e63 --- /dev/null +++ b/assets/highlighting-tests/cpp.cpp @@ -0,0 +1,112 @@ +// Comments +// Single-line comment + +/* + * Multi-line + * comment + */ + +#include +#include +#define CONST_VAL 100 + +// Numbers (including C++14 digit separators) +42; +3.14; +.5; +1e10; +1.5e-3; +0xff; +0xFF; +0b1010'1100; +1'000'000; +42ull; +3.14f; + +// Constants +true; +false; +NULL; +nullptr; + +// Strings and Characters +'c'; +'\t'; +"double quotes with escape: \" \n \t \\"; + +// Control flow keywords +int main() { + if (true) { + } else if (false) { + } else { + } + + for (int i = 0; i < 10; ++i) { + if (i == 5) continue; + if (i == 8) break; + } + + while (false) { } + do { } while (false); + + switch (1) { + case 1: break; + default: break; + } + + try { + throw std::runtime_error("oops"); + } catch (const std::exception& e) { + // handle error + } + + return 0; +} + +// Modern C++ keywords and features +template +concept Addable = requires(T a, T b) { + a + b; +}; + +class Animal { +private: + int age; +protected: + bool is_alive; +public: + Animal() : age(0), is_alive(true) {} + virtual ~Animal() = default; + virtual void speak() const = 0; +}; + +class Dog final : public Animal { +public: + void speak() const override { + std::cout << "Woof!" << std::endl; + } +}; + +constexpr int get_magic_number() { + return 42; +} + +// Other keywords +namespace my_space { + enum class State { START, STOP }; + + inline void helper() { + auto val = get_magic_number(); + decltype(val) val_copy = val; + + Dog* dog = new Dog(); + delete dog; + } +} + +using namespace my_space; +export module my_module; + +// Function calls +std::cout << "hello\n"; +sizeof(int); diff --git a/assets/highlighting-tests/markdown.md b/assets/highlighting-tests/markdown.md index 555b182f961..26acf8088c6 100644 --- a/assets/highlighting-tests/markdown.md +++ b/assets/highlighting-tests/markdown.md @@ -66,6 +66,24 @@ Reference: ![Logo][logo-ref] echo "Hello, world" | tr a-z A-Z ``` +```c +#include + +int main() { + printf("Hello, world\n"); + return 0; +} +``` + +```cpp +#include + +int main() { + std::cout << "Hello, world" << std::endl; + return 0; +} +``` + ```javascript export function greet(name) { return `hello ${name}`; diff --git a/crates/lsh/definitions/c.lsh b/crates/lsh/definitions/c.lsh new file mode 100644 index 00000000000..1c2a2802bc3 --- /dev/null +++ b/crates/lsh/definitions/c.lsh @@ -0,0 +1,63 @@ +#[display_name = "C"] +#[path = "**/*.c"] +#[path = "**/*.h"] +pub fn c() { + until /$/ { + yield other; + + if /\/\/.*/ { + yield comment; + } else if /\/\*/ { + loop { + yield comment; + await input; + if /\*\// { + yield comment; + break; + } + } + } else if /"/ { + until /$/ { + yield string; + if /\\./ { + // Skip escaped characters + } else if /"/ { + yield string; + break; + } + await input; + } + } else if /'/ { + until /$/ { + yield string; + if /\\./ { + // Skip escaped characters + } else if /'/ { + yield string; + break; + } + await input; + } + } else if /#\s*(?:include|define|undef|if|ifdef|ifndef|elif|else|endif|error|pragma|warning|line)\>/ { + yield keyword.control; + } else if /(?:break|case|continue|default|do|else|for|goto|if|return|switch|while)\>/ { + yield keyword.control; + } else if /(?:auto|char|const|double|enum|extern|float|inline|int|long|register|restrict|short|signed|sizeof|static|struct|typedef|union|unsigned|void|volatile|_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local)\>/ { + yield keyword.other; + } else if /(?:true|false|NULL)\>/ { + yield constant.language; + } else if /(?i:-?(?:0x[\da-fA-F']+|0b[01']+|[\d']+\.?[\d']*|\.[\d']+)(?:p[+-]?[\d']+|e[+-]?[\d']+)?[ulfz]*)/ { + if /\w+/ { + // Invalid numeric literal + } else { + yield constant.numeric; + } + } else if /(\w+)\s*\(/ { + yield $1 as method; + } else if /\w+/ { + // Gobble word chars to align the next iteration on a word boundary. + } + + yield other; + } +} \ No newline at end of file diff --git a/crates/lsh/definitions/cpp.lsh b/crates/lsh/definitions/cpp.lsh new file mode 100644 index 00000000000..c3d9c834d26 --- /dev/null +++ b/crates/lsh/definitions/cpp.lsh @@ -0,0 +1,66 @@ +#[display_name = "C++"] +#[path = "**/*.cpp"] +#[path = "**/*.hpp"] +#[path = "**/*.cc"] +#[path = "**/*.cxx"] +#[path = "**/*.hxx"] +pub fn cpp() { + until /$/ { + yield other; + + if /\/\/.*/ { + yield comment; + } else if /\/\*/ { + loop { + yield comment; + await input; + if /\*\// { + yield comment; + break; + } + } + } else if /"/ { + until /$/ { + yield string; + if /\\./ { + // Skip escaped characters + } else if /"/ { + yield string; + break; + } + await input; + } + } else if /'/ { + until /$/ { + yield string; + if /\\./ { + // Skip escaped characters + } else if /'/ { + yield string; + break; + } + await input; + } + } else if /#\s*(?:include|define|undef|if|ifdef|ifndef|elif|else|endif|error|pragma|warning|line)\>/ { + yield keyword.control; + } else if /(?:break|case|catch|continue|default|do|else|for|goto|if|return|switch|throw|try|while)\>/ { + yield keyword.control; + } else if /(?:alignas|alignof|asm|auto|bool|char|char16_t|char32_t|char8_t|class|concept|const|const_cast|consteval|constexpr|constinit|decltype|delete|double|dynamic_cast|enum|explicit|export|extern|float|friend|inline|int|long|mutable|namespace|new|noexcept|operator|private|protected|public|register|reinterpret_cast|requires|short|signed|sizeof|static|static_assert|static_cast|struct|template|thread_local|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t)\>/ { + yield keyword.other; + } else if /(?:true|false|NULL|nullptr)\>/ { + yield constant.language; + } else if /(?i:-?(?:0x[\da-fA-F']+|0b[01']+|[\d']+\.?[\d']*|\.[\d']+)(?:p[+-]?[\d']+|e[+-]?[\d']+)?[ulfz]*)/ { + if /\w+/ { + // Invalid numeric literal + } else { + yield constant.numeric; + } + } else if /(\w+)\s*\(/ { + yield $1 as method; + } else if /\w+/ { + // Gobble word chars to align the next iteration on a word boundary. + } + + yield other; + } +} \ No newline at end of file diff --git a/crates/lsh/definitions/markdown.lsh b/crates/lsh/definitions/markdown.lsh index 77a005fe925..44a05c6a1d8 100644 --- a/crates/lsh/definitions/markdown.lsh +++ b/crates/lsh/definitions/markdown.lsh @@ -28,6 +28,26 @@ pub fn markdown() { if /.*/ {} } } + } else if /(?i:c\+\+|cpp|cc|cxx)/ { + loop { + await input; + if /\s*```/ { + return; + } else { + cpp(); + if /.*/ {} + } + } + } else if /(?i:c)/ { + loop { + await input; + if /\s*```/ { + return; + } else { + c(); + if /.*/ {} + } + } } else if /(?i:diff)/ { loop { await input; From b7bd0b194b633ed0c927ec08eef127a5e062ed21 Mon Sep 17 00:00:00 2001 From: Miro Kakkonen Date: Wed, 8 Apr 2026 19:57:11 +0300 Subject: [PATCH 2/2] Add 'this' to C++ keyword.other --- crates/lsh/definitions/cpp.lsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lsh/definitions/cpp.lsh b/crates/lsh/definitions/cpp.lsh index c3d9c834d26..ab3189fee59 100644 --- a/crates/lsh/definitions/cpp.lsh +++ b/crates/lsh/definitions/cpp.lsh @@ -45,7 +45,7 @@ pub fn cpp() { yield keyword.control; } else if /(?:break|case|catch|continue|default|do|else|for|goto|if|return|switch|throw|try|while)\>/ { yield keyword.control; - } else if /(?:alignas|alignof|asm|auto|bool|char|char16_t|char32_t|char8_t|class|concept|const|const_cast|consteval|constexpr|constinit|decltype|delete|double|dynamic_cast|enum|explicit|export|extern|float|friend|inline|int|long|mutable|namespace|new|noexcept|operator|private|protected|public|register|reinterpret_cast|requires|short|signed|sizeof|static|static_assert|static_cast|struct|template|thread_local|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t)\>/ { + } else if /(?:alignas|alignof|asm|auto|bool|char|char16_t|char32_t|char8_t|class|concept|const|const_cast|consteval|constexpr|constinit|decltype|delete|double|dynamic_cast|enum|explicit|export|extern|float|friend|inline|int|long|mutable|namespace|new|noexcept|operator|private|protected|public|register|reinterpret_cast|requires|short|signed|sizeof|static|static_assert|static_cast|struct|template|this|thread_local|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t)\>/ { yield keyword.other; } else if /(?:true|false|NULL|nullptr)\>/ { yield constant.language;