Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions assets/highlighting-tests/csharp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Single-line comment
/* Block comment
spanning multiple lines */

using System;
using System.Collections.Generic;

#nullable enable

namespace Demo.App;

[Obsolete("Use NewThing instead")]
public sealed class Greeter
{
private readonly string _name;

public Greeter(string name)
{
_name = name;
}

public string SayHello(int count = 3)
{
var list = new List<int> { 1, 2, 3 };
var pi = 3.14159;
var hex = 0xDEAD_BEEF;

// Regular string
var a = "Hello, \"world\"";

// Verbatim string
var b = @"C:\\Temp\\file.txt";

// Interpolated string
var c = $"Hello {_name}, count={count}";

// Interpolated string with alignment + format specifier
var c2 = $"Name={_name,-10} Count={count:D2}";

// Interpolated verbatim string
var d = $@"Name={_name}\nCount={count}";

// Escaped literal braces in interpolated string (should stay string-colored)
var d2 = $"Literal braces: {{ and }}";

return c;
}
}

public record Person(string Name, int Age);

public static class Program
{
public static int Main(string[] args)
{
if (args.Length == 0)
{
return 1;
}

var g = new Greeter(args[0]);
Console.WriteLine(g.SayHello());
return 0;
}
}
7 changes: 7 additions & 0 deletions assets/highlighting-tests/markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,10 @@ export function greet(name) {
def greet(name: str) -> str:
return f"hello {name}"
```

```CSharp
public string Greet(string name)
{
return $"hello {name}";
}
```
1 change: 1 addition & 0 deletions crates/edit/src/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2187,6 +2187,7 @@ impl TextBuffer {
HighlightKind::MarkupList => Some(IndexedColor::BrightBlue),
HighlightKind::MarkupStrikethrough => None,
HighlightKind::MetaHeader => Some(IndexedColor::BrightBlue),
HighlightKind::MetaPreprocessor => Some(IndexedColor::BrightBlue),
};
let attr = match curr.kind {
HighlightKind::MarkupBold => Some(Attributes::Bold),
Expand Down
1 change: 1 addition & 0 deletions crates/lsh-bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ fn run_render(generator: lsh::compiler::Generator, path: &Path) -> anyhow::Resul
"markup.list" => "\x1b[94m", // Bright Blue
"markup.strikethrough" => "\x1b[9m", // Strikethrough
"meta.header" => "\x1b[94m", // Bright Blue
"meta.preprocessor" => "\x1b[94m", // Bright Blue

_ => {
unknown_kinds.push(hk.identifier.to_string());
Expand Down
158 changes: 158 additions & 0 deletions crates/lsh/definitions/csharp.lsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#[display_name = "C#"]
#[path = "**/*.cs"]
#[path = "**/*.csx"]
pub fn csharp() {
// Preprocessor directives (entire line)
if /\s*#\s*(?:if|elif|else|endif|define|undef|warning|error|line|nullable|region|endregion|pragma)\>.*/ {
yield meta.preprocessor;
return;
}

until /$/ {
yield other;

if /\/\/.*/ {
yield comment;
} else if /\/\*/ {
loop {
yield comment;
await input;
if /\*\// {
yield comment;
break;
}
}
} else if /(?:\$@|@\$)"/ {
// Interpolated verbatim string: $@"..." or @$"..."
loop {
yield string;
if /""/ {
// Escaped quote in verbatim strings
} else if /\{\{/ {
// Escaped literal '{'
} else if /\}\}/ {
// Escaped literal '}'
} else if /(\{)/ {
// Interpolation start: braces default, contents light blue.
yield $1 as other;
yield constant.language;

loop {
if /(\})/ {
yield $1 as other;
break;
} else if /[^}:,]+/ {
// Interpolation contents (up to ':' or '}')
yield constant.language;
} else if /,\s*[^}:]+/ {
// Alignment specifier: , -10, ,10, etc.
yield constant.numeric;
yield constant.language;
} else if /:[^}]+/ {
// Format specifier: :D2, :X, :N0, etc.
yield constant.numeric;
yield constant.language;
} else {
break;
}
}

// Resume string highlighting
yield string;
} else if /"/ {
yield string;
break;
}
await input;
}
} else if /@"/ {
// Verbatim string: @"..."
loop {
yield string;
if /""/ {
// Escaped quote in verbatim strings
} else if /"/ {
yield string;
break;
}
await input;
}
} else if /\$"/ {
// Interpolated string: $"..."
until /$/ {
yield string;
if /\\./ {
} else if /\{\{/ {
// Escaped literal '{'
} else if /\}\}/ {
// Escaped literal '}'
} else if /(\{)/ {
// Interpolation start: highlight braces as default, contents as light blue.
yield $1 as other;
yield constant.language;

loop {
if /(\})/ {
yield $1 as other;
break;
} else if /[^}:,]+/ {
// Interpolation contents (up to ':' or '}')
yield constant.language;
} else if /,\s*[^}:]+/ {
// Alignment specifier: , -10, ,10, etc.
yield constant.numeric;
yield constant.language;
} else if /:[^}]+/ {
// Format specifier: :D2, :X, :N0, etc.
yield constant.numeric;
yield constant.language;
} else {
break;
}
}

// Resume string highlighting
yield string;
} else if /"/ {
yield string;
break;
}
await input;
}
} else if /"/ {
until /$/ {
yield string;
if /\\./ {}
else if /"/ { yield string; break; }
await input;
}
} else if /'/ {
// Character literal
until /$/ {
yield string;
if /\\./ {}
else if /'/ { yield string; break; }
await input;
}
} else if /\[(?:assembly:|module:)?\s*([A-Z]\w*)(?:Attribute)?/ {
// Attribute names like [Obsolete]
yield $1 as markup.link;
} else if /(?:break|case|catch|continue|default|do|else|finally|for|foreach|goto|if|return|switch|throw|try|while|yield)\>/ {
yield keyword.control;
} else if /(?:abstract|as|await|base|bool|byte|char|checked|class|const|decimal|delegate|dynamic|enum|event|explicit|extern|false|fixed|float|from|get|global|group|implicit|in|int|interface|internal|into|is|join|let|lock|long|nameof|namespace|new|null|object|operator|orderby|out|override|params|partial|private|protected|public|readonly|record|ref|required|sbyte|sealed|select|set|short|sizeof|stackalloc|static|string|struct|this|true|typeof|uint|ulong|unmanaged|unchecked|unsafe|ushort|using|var|virtual|void|volatile|where|with)\>/ {
yield keyword.other;
} else if /(?i:-?(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+|[\d_]+\.?[\d_]*|\.[\d_]+)(?:e[+-]?[\d_]+)?)/ {
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;
}
}