Add basic support for ruby

Co-authored-by: Kay Simmons <kay@zed.dev>
This commit is contained in:
Max Brunsfeld 2022-11-01 15:36:47 -07:00
parent 9f3ea0c87f
commit d222904471
13 changed files with 349 additions and 7 deletions

12
Cargo.lock generated
View File

@ -3009,6 +3009,7 @@ dependencies = [
"tree-sitter-javascript",
"tree-sitter-json 0.19.0",
"tree-sitter-python",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-typescript",
"unindent",
@ -6491,6 +6492,16 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-ruby"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ac30cbb1560363ae76e1ccde543d6d99087421e228cc47afcec004b86bb711a"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-rust"
version = "0.20.3"
@ -7712,6 +7723,7 @@ dependencies = [
"tree-sitter-json 0.20.0",
"tree-sitter-markdown",
"tree-sitter-python",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-toml",
"tree-sitter-typescript",

View File

@ -71,4 +71,5 @@ tree-sitter-json = "*"
tree-sitter-rust = "*"
tree-sitter-python = "*"
tree-sitter-typescript = "*"
tree-sitter-ruby = "*"
unindent = "0.1.7"

View File

@ -1764,6 +1764,7 @@ impl BufferSnapshot {
.collect::<Vec<_>>();
let mut indent_ranges = Vec::<Range<Point>>::new();
let mut outdent_positions = Vec::<Point>::new();
while let Some(mat) = matches.peek() {
let mut start: Option<Point> = None;
let mut end: Option<Point> = None;
@ -1777,6 +1778,8 @@ impl BufferSnapshot {
start = Some(Point::from_ts_point(capture.node.end_position()));
} else if Some(capture.index) == config.end_capture_ix {
end = Some(Point::from_ts_point(capture.node.start_position()));
} else if Some(capture.index) == config.outdent_capture_ix {
outdent_positions.push(Point::from_ts_point(capture.node.start_position()));
}
}
@ -1797,6 +1800,19 @@ impl BufferSnapshot {
}
}
outdent_positions.sort();
for outdent_position in outdent_positions {
// find the innermost indent range containing this outdent_position
// set its end to the outdent position
if let Some(range_to_truncate) = indent_ranges
.iter_mut()
.filter(|indent_range| indent_range.contains(&outdent_position))
.last()
{
range_to_truncate.end = outdent_position;
}
}
// Find the suggested indentation increases and decreased based on regexes.
let mut indent_change_rows = Vec::<(u32, Ordering)>::new();
self.for_each_line(

View File

@ -1150,6 +1150,49 @@ fn test_autoindent_with_injected_languages(cx: &mut MutableAppContext) {
});
}
#[gpui::test]
fn test_autoindent_query_with_outdent_captures(cx: &mut MutableAppContext) {
let mut settings = Settings::test(cx);
settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
cx.set_global(settings);
cx.add_model(|cx| {
let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(ruby_lang()), cx);
let text = r#"
class C
def a(b, c)
puts b
puts c
rescue
puts "errored"
exit 1
end
end
"#
.unindent();
buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
assert_eq!(
buffer.text(),
r#"
class C
def a(b, c)
puts b
puts c
rescue
puts "errored"
exit 1
end
end
"#
.unindent()
);
buffer
});
}
#[gpui::test]
fn test_serialization(cx: &mut gpui::MutableAppContext) {
let mut now = Instant::now();
@ -1497,6 +1540,26 @@ impl Buffer {
}
}
fn ruby_lang() -> Language {
Language::new(
LanguageConfig {
name: "Ruby".into(),
path_suffixes: vec!["rb".to_string()],
..Default::default()
},
Some(tree_sitter_ruby::language()),
)
.with_indents_query(
r#"
(class "end" @end) @indent
(method "end" @end) @indent
(rescue) @outdent
(then) @indent
"#,
)
.unwrap()
}
fn rust_lang() -> Language {
Language::new(
LanguageConfig {

View File

@ -312,6 +312,7 @@ struct IndentConfig {
indent_capture_ix: u32,
start_capture_ix: Option<u32>,
end_capture_ix: Option<u32>,
outdent_capture_ix: Option<u32>,
}
struct OutlineConfig {
@ -670,12 +671,14 @@ impl Language {
let mut indent_capture_ix = None;
let mut start_capture_ix = None;
let mut end_capture_ix = None;
let mut outdent_capture_ix = None;
get_capture_indices(
&query,
&mut [
("indent", &mut indent_capture_ix),
("start", &mut start_capture_ix),
("end", &mut end_capture_ix),
("outdent", &mut outdent_capture_ix),
],
);
if let Some(indent_capture_ix) = indent_capture_ix {
@ -684,6 +687,7 @@ impl Language {
indent_capture_ix,
start_capture_ix,
end_capture_ix,
outdent_capture_ix,
});
}
Ok(self)

View File

@ -102,6 +102,7 @@ tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown",
tree-sitter-python = "0.20.2"
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
tree-sitter-typescript = "0.20.1"
tree-sitter-ruby = "0.20.0"
tree-sitter-html = "0.19.0"
url = "2.2"

View File

@ -15,6 +15,15 @@ mod python;
mod rust;
mod typescript;
// 1. Add tree-sitter-{language} parser to zed crate
// 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below
// 3. Add config.toml to the newly created language directory using existing languages as a template
// 4. Copy highlights from tree sitter repo for the language into a highlights.scm file.
// Note: github highlights take the last match while zed takes the first
// 5. Add indents.scm, outline.scm, and brackets.scm to implement indent on newline, outline/breadcrumbs,
// and autoclosing brackets respectively
// 6. If the language has injections add an injections.scm query file
#[derive(RustEmbed)]
#[folder = "src/languages"]
#[exclude = "*.rs"]
@ -107,6 +116,7 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
tree_sitter_html::language(),
Some(CachedLspAdapter::new(html::HtmlLspAdapter).await),
),
("ruby", tree_sitter_ruby::language(), None),
] {
languages.add(language(name, grammar, lsp_adapter));
}

View File

@ -0,0 +1,14 @@
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)
("do" @open "end" @close)
(block_parameters "|" @open "|" @close)
(interpolation "#{" @open "}" @close)
(if "if" @open "end" @close)
(unless "unless" @open "end" @close)
(begin "begin" @open "end" @close)
(module "module" @open "end" @close)
(_ . "def" @open "end" @close)
(_ . "class" @open "end" @close)

View File

@ -0,0 +1,11 @@
name = "Ruby"
path_suffixes = ["rb", "Gemfile"]
line_comment = "# "
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false },
{ start = "'", end = "'", close = false, newline = false },
]

View File

@ -0,0 +1,181 @@
; Keywords
[
"alias"
"and"
"begin"
"break"
"case"
"class"
"def"
"do"
"else"
"elsif"
"end"
"ensure"
"for"
"if"
"in"
"module"
"next"
"or"
"rescue"
"retry"
"return"
"then"
"unless"
"until"
"when"
"while"
"yield"
] @keyword
(identifier) @variable
((identifier) @keyword
(#match? @keyword "^(private|protected|public)$"))
; Function calls
((identifier) @function.method.builtin
(#eq? @function.method.builtin "require"))
"defined?" @function.method.builtin
(call
method: [(identifier) (constant)] @function.method)
; Function definitions
(alias (identifier) @function.method)
(setter (identifier) @function.method)
(method name: [(identifier) (constant)] @function.method)
(singleton_method name: [(identifier) (constant)] @function.method)
; Identifiers
[
(class_variable)
(instance_variable)
] @property
((identifier) @constant.builtin
(#match? @constant.builtin "^__(FILE|LINE|ENCODING)__$"))
(file) @constant.builtin
(line) @constant.builtin
(encoding) @constant.builtin
(hash_splat_nil
"**" @operator
) @constant.builtin
((constant) @constant
(#match? @constant "^[A-Z\\d_]+$"))
(constant) @type
(self) @variable.special
(super) @variable.special
; Literals
[
(string)
(bare_string)
(subshell)
(heredoc_body)
(heredoc_beginning)
] @string
[
(simple_symbol)
(delimited_symbol)
(hash_key_symbol)
(bare_symbol)
] @string.special.symbol
(regex) @string.special.regex
(escape_sequence) @escape
[
(integer)
(float)
] @number
[
(nil)
(true)
(false)
] @constant.builtin
(interpolation
"#{" @punctuation.special
"}" @punctuation.special) @embedded
(comment) @comment
; Operators
[
"!"
"~"
"+"
"-"
"**"
"*"
"/"
"%"
"<<"
">>"
"&"
"|"
"^"
">"
"<"
"<="
">="
"=="
"!="
"=~"
"!~"
"<=>"
"||"
"&&"
".."
"..."
"="
"**="
"*="
"/="
"%="
"+="
"-="
"<<="
">>="
"&&="
"&="
"||="
"|="
"^="
"=>"
"->"
(operator)
] @operator
[
","
";"
"."
] @punctuation.delimiter
[
"("
")"
"["
"]"
"{"
"}"
"%w("
"%i("
] @punctuation.bracket

View File

@ -0,0 +1,17 @@
(method "end" @end) @indent
(class "end" @end) @indent
(module "end" @end) @indent
(begin "end" @end) @indent
(do_block "end" @end) @indent
(then) @indent
(call) @indent
(ensure) @outdent
(rescue) @outdent
(else) @outdent
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View File

@ -0,0 +1,11 @@
(class
"class" @context
name: (_) @name) @item
(method
"def" @context
name: (_) @name) @item
(module
"module" @context
name: (_) @name) @item

View File

@ -1,10 +1,6 @@
import { fontWeights } from "../common";
import { withOpacity } from "../utils/color";
import {
ColorScheme,
Layer,
StyleSets,
} from "../themes/common/colorScheme";
import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme";
import {
background,
border,
@ -50,6 +46,11 @@ export default function editor(colorScheme: ColorScheme) {
color: colorScheme.ramps.neutral(1).hex(),
weight: fontWeights.normal,
},
"variable.special": {
// Highlights for self, this, etc
color: colorScheme.ramps.blue(0.7).hex(),
weight: fontWeights.normal,
},
comment: {
color: colorScheme.ramps.neutral(0.71).hex(),
weight: fontWeights.normal,
@ -270,9 +271,9 @@ export default function editor(colorScheme: ColorScheme) {
background: withOpacity(background(layer, "inverted"), 0.4),
border: {
width: 1,
color: borderColor(layer, 'variant'),
color: borderColor(layer, "variant"),
},
}
},
},
compositionMark: {
underline: {