Detailed changes
@@ -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",
@@ -71,4 +71,5 @@ tree-sitter-json = "*"
tree-sitter-rust = "*"
tree-sitter-python = "*"
tree-sitter-typescript = "*"
+tree-sitter-ruby = "*"
unindent = "0.1.7"
@@ -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(
@@ -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 {
@@ -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)
@@ -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"
@@ -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));
}
@@ -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)
@@ -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 },
+]
@@ -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
@@ -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
@@ -0,0 +1,11 @@
+(class
+ "class" @context
+ name: (_) @name) @item
+
+(method
+ "def" @context
+ name: (_) @name) @item
+
+(module
+ "module" @context
+ name: (_) @name) @item
@@ -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: {