├── .github └── workflows │ └── release.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── extension.toml ├── languages └── r │ ├── brackets.scm │ ├── config.toml │ ├── highlights.scm │ ├── indents.scm │ ├── injections.scm │ ├── locals.scm │ ├── outline.scm │ ├── runnables.scm │ └── textobjects.scm └── src └── r.rs /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | 6 | jobs: 7 | homebrew: 8 | name: Release Zed Extension 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: huacnlee/zed-extension-action@v1 12 | with: 13 | extension-name: r 14 | # extension-path: extensions/${{ extension-name }} 15 | push-to: ocsmit/extensions 16 | env: 17 | # the personal access token should have "repo" & "workflow" scopes 18 | COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.R 3 | grammars 4 | target 5 | Cargo.lock 6 | extension.wasm 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "r-zed" 3 | version = "0.0.2" 4 | edition = "2021" 5 | 6 | [lib] 7 | path = "src/r.rs" 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | zed_extension_api = "0.4.0" 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zed-r 2 | 3 | Provides [tree-sitter](https://tree-sitter.github.io/tree-sitter/) queries and [Language Server Protocol (LSP)](https://github.com/REditorSupport/languageserver?tab=readme-ov-file) support for Zed. 4 | 5 | # Installing 6 | 7 | The latest version can be installed from the Zed extensions menu, or by cloning the repo and installing it locally from the Zed extensions menu. 8 | 9 | Additionally, it is recommended to run R code using Zed's REPL feature. To enable this, install either the Ark or Xeus Jupyter kernels; then in an R file create a REPL cell by typing `# %%` (or by using the shortcut available [below](https://github.com/ocsmit/zed-r/#rstudio-keybindings)) and run it by pressing `ctrl-shift-enter`. 10 | 11 | Further information can be found at: 12 | - https://zed.dev/docs/languages/r 13 | - https://zed.dev/docs/repl 14 | 15 | # LSP 16 | 17 | The R packages `languageserver` and `lintr` are needed. 18 | 19 | # RStudio keybindings 20 | 21 | - Add this to your keymap.json by opening the command palette (Ctrl+Shift+P) and selecting `zed: open keymap`. 22 | 23 | **Note: macOS users will likely want to replace `ctrl` with `cmd`.** 24 | 25 | ```json 26 | { 27 | "context": "Editor", 28 | "bindings": { 29 | "ctrl-shift-f10": "repl::Restart", // Equivalent to restarting R in RStudio 30 | "ctrl-\\": "assistant::InlineAssist", 31 | "ctrl-shift-m": ["workspace::SendKeystrokes", "space |> space"], 32 | "alt--": ["workspace::SendKeystrokes", "space < - space"], 33 | "ctrl-alt-i": ["workspace::SendKeystrokes", "\n# space %%\n"], 34 | "ctrl-shift-d": [ 35 | "task::Spawn", 36 | { "task_name": "Document local R package", "reveal_target": "center" } 37 | ], 38 | "ctrl-alt-enter": [ 39 | "workspace::SendKeystrokes", 40 | "ctrl-c ctrl-` ctrl-shift-v enter ctrl-`" 41 | ] 42 | } 43 | }, 44 | { 45 | "context": "Workspace", 46 | "bindings": { 47 | "ctrl-shift-t": [ 48 | "task::Spawn", 49 | { "task_name": "Run all R tests", "reveal_target": "center" } 50 | ], 51 | "ctrl-shift-r": [ 52 | "task::Spawn", 53 | { "task_name": "Run current R file test", "reveal_target": "center" } 54 | ], 55 | "ctrl-shift-b": [ 56 | "task::Spawn", 57 | { "task_name": "Install local R package", "reveal_target": "center" } 58 | ], 59 | "ctrl-2": [ 60 | "task::Spawn", 61 | { "task_name": "R Terminal", "reveal_target": "dock" } 62 | ], 63 | "ctrl-shift-s": [ 64 | "task::Spawn", 65 | { "task_name": "Source current file", "reveal_target": "center"} 66 | ], 67 | "ctrl-shift-e": [ 68 | "task::Spawn", 69 | { "task_name": "Check local R package", "reveal_target": "center"} 70 | ] 71 | } 72 | } 73 | ``` 74 | 75 | # Tasks for R (works with RStudio keybindings) 76 | 77 | - Add this to your tasks.json by opening the command palette (Ctrl+Shift+P) and selecting `zed: open tasks` 78 | 79 | ```json 80 | [ 81 | { 82 | "label": "R Terminal", 83 | "command": "R", // You could also use [radian](https://github.com/randy3k/radian) 84 | "cwd": "$ZED_WORKTREE_ROOT", 85 | "use_new_terminal": true 86 | }, 87 | { 88 | "label": "Install local R package", 89 | "command": "R CMD INSTALL .", 90 | "cwd": "$ZED_WORKTREE_ROOT", 91 | "use_new_terminal": false, 92 | "allow_concurrent_runs": false, 93 | "reveal": "always", 94 | "show_summary": true, 95 | "show_output": true, 96 | "show_error": true 97 | }, 98 | { 99 | "label": "Document local R package", 100 | "command": "Rscript -e \"devtools::document(roclets = c('rd', 'collate', 'namespace'))\"", 101 | "cwd": "$ZED_WORKTREE_ROOT", 102 | "use_new_terminal": false, 103 | "allow_concurrent_runs": false 104 | }, 105 | { 106 | "label": "Check local R package", 107 | "command": "Rscript -e \"devtools::check()\"", 108 | "cwd": "$ZED_WORKTREE_ROOT", 109 | "use_new_terminal": false, 110 | "allow_concurrent_runs": false 111 | }, 112 | { 113 | "label": "Run all R tests", 114 | "command": "Rscript -e \"testthat::test_dir('tests/testthat')\"", 115 | "cwd": "$ZED_WORKTREE_ROOT", 116 | "use_new_terminal": false, 117 | "allow_concurrent_runs": false, 118 | "reveal": "always", 119 | "show_summary": true, 120 | "show_output": true 121 | }, 122 | { 123 | "label": "Run current R file test", 124 | "command": "Rscript -e \"testthat::test_file('$ZED_RELATIVE_FILE')\"", 125 | "cwd": "$ZED_WORKTREE_ROOT", 126 | "use_new_terminal": false, 127 | "allow_concurrent_runs": false, 128 | "reveal": "always", 129 | "show_summary": true, 130 | "show_output": true 131 | }, 132 | { 133 | "label": "Run test_that test", // Can be triggered, without a keyboard shortcut 134 | // or clicking on the run button, by toggling the code action menu (Ctrl+.) 135 | "command": "Rscript", 136 | "cwd": "$ZED_WORKTREE_ROOT", 137 | "args": [ 138 | "-e", 139 | "\"testthat::test_file(\\\"${ZED_FILE}\\\", desc = \\\"${ZED_CUSTOM_desc}\\\")\"" 140 | ], 141 | "tags": ["r-test"] 142 | }, 143 | { 144 | "label": "Source current file", 145 | "command": "Rscript -e \"source('$ZED_RELATIVE_FILE', echo = TRUE)\"", 146 | "cwd": "$ZED_WORKTREE_ROOT", 147 | "use_new_terminal": false, 148 | "allow_concurrent_runs": false, 149 | "reveal": "always", 150 | "show_summary": true, 151 | "show_output": true 152 | } 153 | ] 154 | ``` 155 | 156 | - For a different keyboard shortcut for tasks, read [this section](https://zed.dev/docs/tasks#custom-keybindings-for-tasks) in the official Zed documentation. 157 | 158 | # Caveats 159 | - A custom startup message in `.Rprofile` can potentially cause the the LSP message headers to be decoded improperly and prevent the language server from starting in Zed ([#7](https://github.com/ocsmit/zed-r/issues/7)). 160 | 161 | 162 | # Known issues 163 | 164 | Check out the project's [issue list](https://github.com/ocsmit/zed-r/issues) for a list of unresolved issues. Feel free to fix any of them and send us a pull request! 165 | -------------------------------------------------------------------------------- /extension.toml: -------------------------------------------------------------------------------- 1 | id = "r" 2 | name = "R" 3 | version = "0.2.3" 4 | schema_version = 1 5 | authors = [ 6 | "Owen Smith ", 7 | "Aymen Nasri ", 8 | "Changtao Li <60499240+lict99@users.noreply.github.com>", 9 | ] 10 | description = "R language support." 11 | repository = "https://github.com/ocsmit/zed-r" 12 | 13 | [grammars.r] 14 | repository = "https://github.com/r-lib/tree-sitter-r" 15 | commit = "a0d3e3307489c3ca54da8c7b5b4e0c5f5fd6953a" 16 | 17 | [language_servers.r_language_server] 18 | name = "r lsp" 19 | language = "R" 20 | -------------------------------------------------------------------------------- /languages/r/brackets.scm: -------------------------------------------------------------------------------- 1 | ("(" @open ")" @close) 2 | ("[" @open "]" @close) 3 | ("{" @open "}" @close) 4 | -------------------------------------------------------------------------------- /languages/r/config.toml: -------------------------------------------------------------------------------- 1 | name = "R" 2 | grammar = "r" 3 | path_suffixes = ["r", "R"] 4 | line_comments = ["# ", "#' "] 5 | tab_size = 2 6 | autoclose_before = "$@=,}])" 7 | brackets = [ 8 | { start = "{", end = "}", close = true, newline = true }, 9 | { start = "[", end = "]", close = true, newline = true }, 10 | { start = "(", end = ")", close = true, newline = true }, 11 | { start = "\"", end = "\"", close = true, newline = false, not_in = [ 12 | "comment", 13 | "string", 14 | ] }, 15 | { start = "'", end = "'", close = false, newline = false, not_in = [ 16 | "comment", 17 | ] }, 18 | ] 19 | collapsed_placeholder = "# ..." 20 | word_characters = ["."] 21 | # increase_indent_pattern = "^[^#]*[+\\-*/<=>%&|]\\s*$" 22 | # decrease_indent_pattern = "" 23 | -------------------------------------------------------------------------------- /languages/r/highlights.scm: -------------------------------------------------------------------------------- 1 | ; highlights.scm 2 | 3 | ; Literals 4 | 5 | (integer) @number 6 | (float) @number 7 | (complex) @number 8 | 9 | (string) @string 10 | (string (string_content (escape_sequence) @string.escape)) 11 | 12 | ; Comments 13 | 14 | (comment) @comment 15 | 16 | ; Operators 17 | 18 | [ 19 | "?" ":=" "=" "<-" "<<-" "->" "->>" 20 | "~" "|>" "||" "|" "&&" "&" 21 | "<" "<=" ">" ">=" "==" "!=" 22 | "+" "-" "*" "/" "::" ":::" 23 | "**" "^" "$" "@" ":" "!" 24 | "special" 25 | ] @operator 26 | 27 | ; Punctuation 28 | 29 | [ 30 | "(" ")" 31 | "{" "}" 32 | "[" "]" 33 | "[[" "]]" 34 | ] @punctuation.bracket 35 | 36 | (comma) @punctuation.delimiter 37 | 38 | ; Variables 39 | 40 | (identifier) @variable 41 | 42 | ; Functions 43 | 44 | (binary_operator 45 | lhs: (identifier) @function 46 | operator: "<-" 47 | rhs: (function_definition) 48 | ) 49 | 50 | (binary_operator 51 | lhs: (identifier) @function 52 | operator: "=" 53 | rhs: (function_definition) 54 | ) 55 | 56 | ; Calls 57 | 58 | (call function: (identifier) @function) 59 | 60 | ; Parameters 61 | 62 | (parameters (parameter name: (identifier) @variable.parameter)) 63 | (arguments (argument name: (identifier) @variable.parameter)) 64 | 65 | ; Namespace 66 | 67 | (namespace_operator lhs: (identifier) @type) 68 | 69 | (call 70 | function: (namespace_operator rhs: (identifier) @function) 71 | )(call function: (identifier) @function) 72 | 73 | 74 | ; Keywords 75 | 76 | (function_definition name: "function" @keyword.function) 77 | (function_definition name: "\\" @keyword.function) 78 | 79 | [ 80 | "in" 81 | (return) 82 | (next) 83 | (break) 84 | "if" 85 | "else" 86 | ; "switch" 87 | "while" 88 | "repeat" 89 | "for" 90 | ] @keyword 91 | 92 | ; [ 93 | ; "if" 94 | ; "else" 95 | ; ] @conditional 96 | 97 | ; [ 98 | ; "while" 99 | ; "repeat" 100 | ; "for" 101 | ; ] @repeat 102 | 103 | [ 104 | (true) 105 | (false) 106 | ] @boolean 107 | 108 | [ 109 | (null) 110 | (inf) 111 | (nan) 112 | (na) 113 | (dots) 114 | (dot_dot_i) 115 | ] @constant.builtin 116 | 117 | ; Error 118 | 119 | (ERROR) @error 120 | 121 | (call function: (identifier) @keyword 122 | (#any-of? @keyword "library" "require" "source" "return" "stop" "try" "tryCatch")) 123 | 124 | ; roxygen 125 | ((comment) @documentation 126 | (#match? @documentation "^#'")) 127 | 128 | ; jupyter cell tag 129 | ((comment) @operator 130 | (#match? @operator "^#\\s?%%")) 131 | -------------------------------------------------------------------------------- /languages/r/indents.scm: -------------------------------------------------------------------------------- 1 | ; [ 2 | ; (brace_list) 3 | ; (paren_list) 4 | ; (special) 5 | ; (pipe) 6 | ; (call) 7 | ; "|>" 8 | ; "if" 9 | ; "else" 10 | ; "while" 11 | ; "repeat" 12 | ; "for" 13 | ; ] @indent.begin 14 | 15 | ; (binary 16 | ; operator: (special)) @indent.begin 17 | 18 | ; [ 19 | ; "}" 20 | ; ")" 21 | ; ] @indent.branch 22 | 23 | ; ((formal_parameters 24 | ; (identifier)) @indent.align 25 | ; (#set! indent.open_delimiter "(") 26 | ; (#set! indent.close_delimiter ")")) 27 | 28 | ; [ 29 | ; ")" 30 | ; "}" 31 | ; ] @indent.end 32 | 33 | ; (comment) @indent.ignore 34 | 35 | ; keep the same level of indentation 36 | 37 | (_ "[" "]" @end) @indent 38 | (_ "{" "}" @end) @indent 39 | (_ "(" ")" @end) @indent 40 | -------------------------------------------------------------------------------- /languages/r/injections.scm: -------------------------------------------------------------------------------- 1 | ((comment) @injection.content 2 | (#set! injection.language "comment")) 3 | -------------------------------------------------------------------------------- /languages/r/locals.scm: -------------------------------------------------------------------------------- 1 | ; locals.scm 2 | 3 | (function_definition) @local.scope 4 | 5 | (argument name: (identifier) @local.definition) 6 | (parameter name: (identifier) @local.definition) 7 | 8 | (binary_operator 9 | lhs: (identifier) @local.definition 10 | operator: "<-") 11 | (binary_operator 12 | lhs: (identifier) @local.definition 13 | operator: "=") 14 | (binary_operator 15 | operator: "->" 16 | rhs: (identifier) @local.definition) 17 | 18 | (identifier) @local.reference 19 | -------------------------------------------------------------------------------- /languages/r/outline.scm: -------------------------------------------------------------------------------- 1 | ; Variables by assignment operators 2 | (binary_operator 3 | lhs: (identifier) @name 4 | operator: ["<-" "=" "<<-"] 5 | rhs: (_) 6 | ) @item 7 | 8 | (binary_operator 9 | lhs: (_) 10 | operator: ["->" "->>"] 11 | rhs: (identifier) @name 12 | ) @item 13 | 14 | ; Variables by `for` loop 15 | (for_statement 16 | "for" @context 17 | "(" @context 18 | variable: (identifier) @name 19 | "in" @context 20 | sequence: (_) @context 21 | ")" @context 22 | ) @item 23 | 24 | ; Jupyter cell tag 25 | ( 26 | (comment) @name 27 | (#match? @name "^#\\s?%%") 28 | ) @item 29 | -------------------------------------------------------------------------------- /languages/r/runnables.scm: -------------------------------------------------------------------------------- 1 | ; runnables.scm 2 | 3 | ; test_that functions 4 | ( 5 | (call 6 | function: (identifier) @fn (#eq? @fn "test_that") 7 | arguments: (arguments 8 | (argument 9 | value: (string 10 | (string_content) @desc) 11 | ) 12 | (_)* 13 | ) 14 | ) @run 15 | (#set! tag r-test) 16 | ) 17 | -------------------------------------------------------------------------------- /languages/r/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; ; block 2 | ; ; call 3 | ; (call) @call.outer 4 | 5 | ; (arguments) @call.inner 6 | 7 | ; ; class 8 | ; ; comment 9 | ; (comment) @comment.outer 10 | 11 | ; ; conditional 12 | ; (if 13 | ; condition: (_)? @conditional.inner) @conditional.outer 14 | 15 | ; ; function 16 | ; [ 17 | ; (function_definition) 18 | ; (lambda_function) 19 | ; ] @function.outer 20 | 21 | ; (function_definition 22 | ; [ 23 | ; (call) 24 | ; (binary) 25 | ; (brace_list) 26 | ; ] @function.inner) @function.outer 27 | 28 | ; (lambda_function 29 | ; [ 30 | ; (call) 31 | ; (binary) 32 | ; (brace_list) 33 | ; ] @function.inner) @function.outer 34 | 35 | ; ; loop 36 | ; [ 37 | ; (while) 38 | ; (repeat) 39 | ; (for) 40 | ; ] @loop.outer 41 | 42 | ; (while 43 | ; body: (_) @loop.inner) 44 | 45 | ; (repeat 46 | ; body: (_) @loop.inner) 47 | 48 | ; (for 49 | ; body: (_) @loop.inner) 50 | 51 | ; ; statement 52 | ; (brace_list 53 | ; (_) @statement.outer) 54 | 55 | ; (program 56 | ; (_) @statement.outer) 57 | 58 | ; ; parameter 59 | ; ((formal_parameters 60 | ; "," @_start 61 | ; . 62 | ; (_) @parameter.inner) 63 | ; (#make-range! "parameter.outer" @_start @parameter.inner)) 64 | 65 | ; ((formal_parameters 66 | ; . 67 | ; (_) @parameter.inner 68 | ; . 69 | ; ","? @_end) 70 | ; (#make-range! "parameter.outer" @parameter.inner @_end)) 71 | 72 | ; ((arguments 73 | ; "," @_start 74 | ; . 75 | ; (_) @parameter.inner) 76 | ; (#make-range! "parameter.outer" @_start @parameter.inner)) 77 | 78 | ; ((arguments 79 | ; . 80 | ; (_) @parameter.inner 81 | ; . 82 | ; ","? @_end) 83 | ; (#make-range! "parameter.outer" @parameter.inner @_end)) 84 | 85 | ; ; number 86 | ; (float) @number.inner 87 | 88 | ; ; assignment 89 | ; (left_assignment 90 | ; name: (_) @assignment.lhs 91 | ; value: (_) @assignment.inner @assignment.rhs) @assignment.outer 92 | 93 | ; (left_assignment 94 | ; name: (_) @assignment.inner) 95 | 96 | ; (right_assignment 97 | ; value: (_) @assignment.inner @assignment.lhs 98 | ; name: (_) @assignment.rhs) @assignment.outer 99 | 100 | ; (right_assignment 101 | ; name: (_) @assignment.inner) 102 | 103 | ; (equals_assignment 104 | ; name: (_) @assignment.lhs 105 | ; value: (_) @assignment.inner @assignment.rhs) @assignment.outer 106 | 107 | ; (equals_assignment 108 | ; name: (_) @assignment.inner) 109 | 110 | ; (super_assignment 111 | ; name: (_) @assignment.lhs 112 | ; value: (_) @assignment.inner @assignment.rhs) @assignment.outer 113 | 114 | ; (super_assignment 115 | ; name: (_) @assignment.inner) 116 | 117 | ; (super_right_assignment 118 | ; value: (_) @assignment.inner @assignment.lhs 119 | ; name: (_) @assignment.rhs) @assignment.outer 120 | 121 | ; (super_right_assignment 122 | ; name: (_) @assignment.inner) 123 | -------------------------------------------------------------------------------- /src/r.rs: -------------------------------------------------------------------------------- 1 | use zed::LanguageServerId; 2 | use zed_extension_api::{self as zed, Result}; 3 | 4 | struct RExtension; 5 | 6 | impl zed::Extension for RExtension { 7 | fn new() -> Self { 8 | Self 9 | } 10 | 11 | fn language_server_command( 12 | &mut self, 13 | _language_server_id: &LanguageServerId, 14 | worktree: &zed::Worktree, 15 | ) -> Result { 16 | let Some(r_path) = worktree.which("R") else { 17 | return Err("R not available".to_string()); 18 | }; 19 | 20 | Ok(zed::Command { 21 | command: r_path.to_string(), 22 | args: vec![ 23 | "--slave".to_string(), 24 | "-e".to_string(), 25 | "languageserver::run()".to_string(), 26 | ], 27 | env: Default::default(), 28 | }) 29 | } 30 | } 31 | 32 | zed::register_extension!(RExtension); 33 | --------------------------------------------------------------------------------