From 4f1f4de5d70687b516123386352400c9928e4aa1 Mon Sep 17 00:00:00 2001 From: Toshimaru Date: Thu, 30 Apr 2026 19:11:28 +0900 Subject: [PATCH 1/3] Add Ruby syntax highlighting --- crates/lsh/definitions/ruby.lsh | 69 +++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 crates/lsh/definitions/ruby.lsh diff --git a/crates/lsh/definitions/ruby.lsh b/crates/lsh/definitions/ruby.lsh new file mode 100644 index 00000000000..9d10711944c --- /dev/null +++ b/crates/lsh/definitions/ruby.lsh @@ -0,0 +1,69 @@ +#[display_name = "Ruby"] +#[path = "**/*.rb"] +#[path = "**/*.rake"] +#[path = "**/*.ru"] +#[path = "**/Gemfile"] +#[path = "**/Rakefile"] +pub fn ruby() { + until /$/ { + yield other; + + if /#.*/ { + yield comment; + } else if /=begin\>/ { + loop { + yield comment; + await input; + if /=end\>/ { + yield comment; + break; + } + } + } else if /'/ { + until /$/ { + yield string; + if /\\./ {} + else if /'/ { yield string; break; } + await input; + } + } else if /"/ { + until /$/ { + yield string; + if /\\./ {} + else if /"/ { yield string; break; } + await input; + } + } else if /`/ { + loop { + yield string; + if /\\./ {} + else if /`/ { yield string; break; } + await input; + } + } else if /(?:BEGIN|END|alias|begin|break|case|do|else|elsif|ensure|for|if|in|next|redo|rescue|retry|return|then|unless|until|when|while|yield)\>/ { + yield keyword.control; + } else if /(?:class|def|defined\?|end|module|self|super|undef)\>/ { + yield keyword.other; + } else if /(?:true|false|nil)\>/ { + yield constant.language; + } else if /(?i:-?(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+|[\d_]+\.?[\d_]*|\.[\d_]+)(?:e[+-]?[\d_]+)?r?i?)/ { + if /\w+/ { + // Invalid numeric literal + } else { + yield constant.numeric; + } + } else if /[@$]+[A-Za-z_]\w*[!?=]?/ { + yield variable; + } else if /:[A-Za-z_]\w*[!?=]?/ { + yield constant.language; + } else if /[A-Z]\w*/ { + yield constant.language; + } 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; + } +} From f34c93dd53b67c56203fea3e2f71bd8d6b5ea596 Mon Sep 17 00:00:00 2001 From: Toshimaru Date: Thu, 30 Apr 2026 19:18:56 +0900 Subject: [PATCH 2/3] Add Ruby syntax-highlighting test fixture. - Fix Ruby keyword and variable highlighting --- assets/highlighting-tests/ruby.rb | 104 ++++++++++++++++++++++++++++++ crates/lsh/definitions/ruby.lsh | 31 +++++++-- 2 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 assets/highlighting-tests/ruby.rb diff --git a/assets/highlighting-tests/ruby.rb b/assets/highlighting-tests/ruby.rb new file mode 100644 index 00000000000..a8d2222e137 --- /dev/null +++ b/assets/highlighting-tests/ruby.rb @@ -0,0 +1,104 @@ +# Comments +# Single-line comment + +=begin +Multi-line +comment +=end + +# Numbers +42 +3.14 +0.5 +1e10 +1.5e-3 +0xff +0xFF +0b1010 +0o77 +1_000_000 +3.14r +2i + +# Constants +true +false +nil +Object +String + +# Strings +'single quotes with escape: \' \n \t \\' +"double quotes with escape: \" \n \t \\" +`echo shell command` + +# Symbols and variables +:symbol +:method_name? +@instance_var +@@class_var +$global_var + +# Control flow keywords +if true + puts "yes" +elsif false + puts "no" +else + puts "maybe" +end + +unless nil + puts "not nil" +end + +case 42 +when 1 + puts "one" +else + puts "other" +end + +for i in 1..3 + next if i == 2 + break if i == 3 +end + +while false + redo +end + +begin + raise "oops" +rescue StandardError => e + retry +ensure + puts e +end + +# Definitions and method calls +module Demo + class Animal + def initialize(name) + @name = name + end + + def speak! + puts "#{@name} speaks" + end + end +end + +alias old_speak speak! +undef old_speak + +BEGIN { puts "start" } +END { puts "finish" } +defined? Demo +self +super +yield + +puts "hello" +Array.new(3) +greet("world") diff --git a/crates/lsh/definitions/ruby.lsh b/crates/lsh/definitions/ruby.lsh index 9d10711944c..a46b99b38a1 100644 --- a/crates/lsh/definitions/ruby.lsh +++ b/crates/lsh/definitions/ruby.lsh @@ -40,19 +40,42 @@ pub fn ruby() { else if /`/ { yield string; break; } await input; } - } else if /(?:BEGIN|END|alias|begin|break|case|do|else|elsif|ensure|for|if|in|next|redo|rescue|retry|return|then|unless|until|when|while|yield)\>/ { + } else if /(?:begin|break|case|do|else|elsif|ensure|for|if|in|next|redo|rescue|retry|return|then|unless|until|when|while)\>/ { yield keyword.control; - } else if /(?:class|def|defined\?|end|module|self|super|undef)\>/ { + } else if /def\s+(\w+[!?=]?)/ { + yield keyword.other; + yield $1 as method; + } else if /(?:BEGIN|END|alias|class|defined\?|def|end|module|self|super|undef|yield)\>/ { yield keyword.other; } else if /(?:true|false|nil)\>/ { yield constant.language; - } else if /(?i:-?(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+|[\d_]+\.?[\d_]*|\.[\d_]+)(?:e[+-]?[\d_]+)?r?i?)/ { + } else if /\.\.\.?/ { + // Range operator. + } else if /(?i:-?(?:0x[\da-fA-F_]+|0b[01_]+|0o[0-7_]+)r?i?)/ { + if /\w+/ { + // Invalid numeric literal + } else { + yield constant.numeric; + } + } else if /(?i:-?[\d_]+\.[\d_]+(?:e[+-]?[\d_]+)?r?i?)/ { + if /\w+/ { + // Invalid numeric literal + } else { + yield constant.numeric; + } + } else if /(?i:-?\.[\d_]+(?:e[+-]?[\d_]+)?r?i?)/ { + if /\w+/ { + // Invalid numeric literal + } else { + yield constant.numeric; + } + } else if /(?i:-?[\d_]+(?:e[+-]?[\d_]+)?r?i?)/ { if /\w+/ { // Invalid numeric literal } else { yield constant.numeric; } - } else if /[@$]+[A-Za-z_]\w*[!?=]?/ { + } else if /(?:@{1,2}|\$)[A-Za-z_]\w*/ { yield variable; } else if /:[A-Za-z_]\w*[!?=]?/ { yield constant.language; From 07cbc0dab543fec9e3cab3465cede94e3320bee5 Mon Sep 17 00:00:00 2001 From: Toshimaru Date: Mon, 4 May 2026 20:43:12 +0900 Subject: [PATCH 3/3] fix: Guard =begin/=end block comments at column 0 - use `keyword.control` for `end` --- assets/highlighting-tests/ruby.rb | 4 +--- crates/lsh/definitions/ruby.lsh | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/assets/highlighting-tests/ruby.rb b/assets/highlighting-tests/ruby.rb index a8d2222e137..69b1c682316 100644 --- a/assets/highlighting-tests/ruby.rb +++ b/assets/highlighting-tests/ruby.rb @@ -83,9 +83,7 @@ def initialize(name) @name = name end - def speak! - puts "#{@name} speaks" - end + def speak! = puts "#{@name} speaks" end end diff --git a/crates/lsh/definitions/ruby.lsh b/crates/lsh/definitions/ruby.lsh index a46b99b38a1..0e7079cd12d 100644 --- a/crates/lsh/definitions/ruby.lsh +++ b/crates/lsh/definitions/ruby.lsh @@ -5,20 +5,28 @@ #[path = "**/Gemfile"] #[path = "**/Rakefile"] pub fn ruby() { + var zero = 0; until /$/ { yield other; - if /#.*/ { - yield comment; - } else if /=begin\>/ { - loop { - yield comment; - await input; - if /=end\>/ { + if off == zero { + if /=begin\>/ { + loop { yield comment; - break; + await input; + if off == zero { + if /=end\>/ { + yield comment; + break; + } + } } + continue; } + } + + if /#.*/ { + yield comment; } else if /'/ { until /$/ { yield string; @@ -40,12 +48,12 @@ pub fn ruby() { else if /`/ { yield string; break; } await input; } - } else if /(?:begin|break|case|do|else|elsif|ensure|for|if|in|next|redo|rescue|retry|return|then|unless|until|when|while)\>/ { + } else if /(?:begin|break|case|class|do|else|elsif|end|ensure|for|if|in|module|next|redo|rescue|retry|return|then|unless|until|when|while)\>/ { yield keyword.control; } else if /def\s+(\w+[!?=]?)/ { - yield keyword.other; + yield keyword.control; yield $1 as method; - } else if /(?:BEGIN|END|alias|class|defined\?|def|end|module|self|super|undef|yield)\>/ { + } else if /(?:BEGIN|END|alias|defined\?|self|super|undef|yield)\>/ { yield keyword.other; } else if /(?:true|false|nil)\>/ { yield constant.language;