Skip to content

Support for RTypst#575

Open
jalvesaq wants to merge 20 commits into
mainfrom
Rtypst
Open

Support for RTypst#575
jalvesaq wants to merge 20 commits into
mainfrom
Rtypst

Conversation

@jalvesaq
Copy link
Copy Markdown
Member

No description provided.

@jalvesaq
Copy link
Copy Markdown
Member Author

Support for Typst was added to knitr on GitHub on Mar 22, 2026. It's not available in the last released version of knitr.

@jalvesaq
Copy link
Copy Markdown
Member Author

My approach in this pull request was to set the filetype of a .Rtyp file as typst and then enable R.nvim features for the Typst filetype. Would it be better to set a new filetype, Rtyp, and rename ftplugin/typst_rnvim.lua as ftplugin/rtyp_rnvim.lua?

Comment thread lua/r/chunk.lua Outdated
return chunks
end

--- Get code chunks from an RTypst (.Rtyp) document by scanning ```{r} and ``` markers.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not use

R.nvim/lua/r/chunk.lua

Lines 97 to 154 in 2d0cd15

local get_rmd_code_chunks = function(bufnr)
local root = require("r.utils").get_root_node()
if not root then return nil end
local query = vim.treesitter.query.parse(
"markdown",
[[
(fenced_code_block)
@fenced_code_block
]]
)
bufnr = bufnr or vim.api.nvim_get_current_buf()
local code_chunks = {}
for id, node, _ in query:iter_captures(root, bufnr, 0, -1) do
local capture_name = query.captures[id]
if capture_name == "fenced_code_block" then
-- Loop through all children of the node and print their type and text
local lang
local info_string_params = {}
local comment_params = {}
local content_text = ""
local start_row, _, end_row, _ = node:range()
for child in node:iter_children() do
if child:type() == "info_string" then
local info_string = vim.treesitter.get_node_text(child, bufnr)
lang, info_string_params = M.parse_info_string_params(info_string)
end
if child:type() == "code_fence_content" then
content_text = vim.treesitter.get_node_text(child, bufnr)
-- Get the parameters specified in the code chunk with #|
comment_params =
M.parse_comment_params(vim.treesitter.get_node_text(node, bufnr))
end
end
-- Create the chunk object with the extracted information
local chunk = Chunk:new(
content_text,
start_row + 1,
end_row,
info_string_params,
comment_params,
lang,
node
)
table.insert(code_chunks, chunk)
end
end
return code_chunks
end

I think rtyps uses the same structure?

I think

local params_str = lines[i]:match("^```{r}$")

Will not capture params in r chunks, like {r echo=FALSE} or {python}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was easier for me to use regular expressions, but if you could rewrite the function using tree-sitter, it would be great.

@PMassicotte
Copy link
Copy Markdown
Collaborator

PMassicotte commented May 10, 2026

I think we could just do that in config.lua

vim.treesitter.language.register("markdown", { "quarto", "rmd", "rtyp" })

Then we could just reuse get_rmd_code_chunks + parse_info_string_params + parse_comment_params all. My preliminary understanding is that we already have all the tooling to parse rtyp code chunks.

What do you think?

@PMassicotte
Copy link
Copy Markdown
Collaborator

My approach in this pull request was to set the filetype of a .Rtyp file as typst and then enable R.nvim features for the Typst filetype. Would it be better to set a new filetype, Rtyp, and rename ftplugin/typst_rnvim.lua as ftplugin/rtyp_rnvim.lua?

Yes, I think this is the right approach.

@jalvesaq
Copy link
Copy Markdown
Member Author

vim.treesitter.language.register("markdown", { "quarto", "rmd", "rtyp" })

It would not work. Typst is very different from Markdown. It uses = instead of # for titles, and uses # as a prefix for functions in a similar way to LaTeX, which uses \.

@jalvesaq
Copy link
Copy Markdown
Member Author

Yes, I think this is the right approach.

This was my first thought. But then I realized that if the file is a Typst file, we can send the R code to R Console, but we will not be able to process the R code when rendering the PDF because knitr converts a .Rtyp file into a .typ file. If the file extension is already .typ, it would not work because it would overwrite itself.

@jalvesaq
Copy link
Copy Markdown
Member Author

I'll be able to continue working on this only at night...

@PMassicotte
Copy link
Copy Markdown
Collaborator

PMassicotte commented May 10, 2026

Peek 2026-05-10 08-07

This is what I got so far with TS.

If ok with you, I can push my changes.

@PMassicotte
Copy link
Copy Markdown
Collaborator

Note: r_ls should be activated for that filetype.

fix(ftplugin): use pcall for treesitter query set to handle errors

refactor(chunk.lua): remove custom RTypst code chunk parsing

feat(cursor.lua): add typst support for moving to next code chunk

fix(lsp/init.lua): correct iteration over attached_buffers

feat(maps.lua): add typst support for RSendChunk and related mappings

feat(send.lua): add typst support for sending lines and chunks
@PMassicotte
Copy link
Copy Markdown
Collaborator

PS. I have not tested knitting, I do not have the dev version of knitr installed.

@PMassicotte
Copy link
Copy Markdown
Collaborator

I will push this, let me know if it works for you, otherwise we can just delete the commit if anything :)

@PMassicotte
Copy link
Copy Markdown
Collaborator

PMassicotte commented May 10, 2026

I am currently using Markdown parser to get the chunks code, inline params and chunks parameters, The Markdown grammar is resilient enough to work with typst filetype.

I started to implement a typst specific TS extraction code, but we have to do some manually regex job to get the inline parameters in chunks like:

```{r, eval=TRUE}
plot(
  x,
  type = "b",
  main = "Random Normal Values",
  xlab = "Index",
  ylab = "Value"
)
```

What I have done if more a general refactor on how we get the code chunks based on the file type. At the moment, it works with just using Markdown parser, but maybe I could push the current refactor later in another PR.

@jalvesaq
Copy link
Copy Markdown
Member Author

If ok with you, I can push my changes.

Yes, it was OK. I think you have already pushed...

@jalvesaq
Copy link
Copy Markdown
Member Author

I'm getting this message when trying to send a line from a chunk of R code:

Not inside a supported code chunk.

@PMassicotte
Copy link
Copy Markdown
Collaborator

I'm getting this message when trying to send a line from a chunk of R code:

Not inside a supported code chunk.

Back to the office, I will have a look.

@jalvesaq
Copy link
Copy Markdown
Member Author

I've just found that the filetype typst does have the lang node if the block is:

```r
x <- rnorm(100)
```

But not if it is:

```{r}
x <- rnorm(100)
```

@PMassicotte
Copy link
Copy Markdown
Collaborator

I have some meetings this morning, I think I forgot to push a change yesterday. Will do that soon.

@jalvesaq
Copy link
Copy Markdown
Member Author

There is no hurry, and I will only have spare time next weekend.

- Broaden tree-sitter injection regex to match chunks with parameters
  (e.g., `{r, echo=FALSE}`, `{python}`) instead of exact `{r}` only
- Use row-based offset (1,0,0,0) to skip the header line regardless
  of its length, instead of hardcoding column offsets
- Add dedicated `get_rtypst_code_chunks` using typst tree-sitter
  parser instead of forcing markdown parser on typst buffers
- Include "typst" in send.lua selection() filetype check
Selene linter does not support LuaJIT goto/label syntax.
Rewrite get_rtypst_code_chunks to use nested if-else instead.
The previous `pattern` key with Lua pattern matching did not work with
Neovim's `vim.filetype.match()`. Switch to `extension` which correctly
maps .Rtyp to typst filetype. Also removes the unnecessary conf override
autocmd since the extension approach takes priority.
- Fix injection offset from 1 0 0 0 to 1 -3 0 0 to account for blob
  node starting at column 3 (after ``` delimiter), resolving wrong
  highlighting on first 3 characters of each code line
- Build typst injection queries dynamically from chunk_langs config,
  including aliases (e.g., {webr} highlighted as R)
- Register chunk_lang aliases via vim.treesitter.language.register()
  so Quarto/Rmd injection resolution maps aliases to canonical parsers
  (e.g., "webr" → "r" parser)
- Add typst to r_filetypes in lsp/init.lua to fix "Index out of bounds"
  crash when editing .Rtyp files alongside Quarto files
- Add typst to get_lang() dispatch in lsp/init.lua and utils.lua for
  proper language detection in LSP operations
The blob node's end_row is the last content line, not the closing ```
fence. This caused get_chunk_section_at_cursor to treat the last content
line as "chunk_end", silently skipping it when pressing Enter.
Use the parent raw_blck node's end_row instead, which includes the
closing fence — matching how Rmd uses the full fenced_code_block range.
Lua's `and`/`or` only captures the first return value of range(),
so the previous `node:parent() and node:parent():range()` captured
start_row instead of end_row. Fix by destructuring all four return
values. Also add +1 to convert 0-indexed exclusive end to 1-indexed
cursor row, matching how the Rmd chunk detection works.

- Fixes "Not inside a supported code chunk" for Python/SQL/Bash chunks
- Fixes last content line treated as "chunk_end" instead of "chunk_body"
jiangyun-fun and others added 7 commits May 14, 2026 18:17
The refactoring to pcall(get_parser, bufnr, "markdown") broke .R file
support because it never initializes the R parser. get_root_node() calls
get_parser(bufnr) without a language argument, which initializes the
parser for the buffer's actual filetype. Typst files are unaffected
since they dispatch to get_rtypst_code_chunks before reaching this path.
Replace the typst-specific branch in get_rmd_code_chunks with a generic
get_ts_code_chunks helper plus per-format extractor functions. The Rmd
path resolves its root via get_root_node() so .R files still initialize
the R parser correctly.
…age support

Brings in Yun Jiang's (@jiangyun-fun) RTypst fixes: dynamic typst
injection queries from chunk_langs, chunk_lang alias registration,
extension-based .Rtyp filetype detection, and typst support in the
LSP/send paths.

chunk.lua is kept from our generic-helper refactor (e7f0968), which
supersedes #576's standalone get_rtypst_code_chunks while preserving
the same .R-parser fix.

Co-Authored-By: Yun Jiang <jiangyunacw@gmail.com>
Add a check to warn users if the treesitter parser for the current
filetype is not installed.
@PMassicotte
Copy link
Copy Markdown
Collaborator

I think this should work now. We might want to have knitr version updated on CRAN before merging in main?

@jalvesaq
Copy link
Copy Markdown
Member Author

Thank you, @PMassicotte and @jiangyun-fun! Everything seems to be Good. Just two notes:

  1. We have to run stylua to format the files:
stylua ftplugin/
stylua lua/
  1. The command \kr will not work for Typst, only for RTypst, and the command \kp should result in different commands being sent to R depending on the file being .typ or .Rtyp. Something like this:
diff --git a/ftplugin/typst_rnvim.lua b/ftplugin/typst_rnvim.lua
index af08276..db59dfe 100644
--- a/ftplugin/typst_rnvim.lua
+++ b/ftplugin/typst_rnvim.lua
@@ -44,7 +53,9 @@ local function build_typst_injections()
     return table.concat(parts, "\n\n")
 end
 
-pcall(vim.treesitter.query.set, "typst", "injections", build_typst_injections())
+if vim.api.nvim_buf_get_name(0):lower():find("%.[Rr][Tt][Yy][Pp]$") then
+    pcall(vim.treesitter.query.set, "typst", "injections", build_typst_injections())
+end
 
 require("r.config").real_setup()
 require("r.rmd").setup()
diff --git a/lua/r/maps.lua b/lua/r/maps.lua
index db6af20..7302531 100644
--- a/lua/r/maps.lua
+++ b/lua/r/maps.lua
@@ -210,7 +210,9 @@ local control = function(file_type)
     create_maps("nvi", "ROBCloseLists",     "r-", "<Cmd>lua require('r.browser').open_close_lists('C')")
 
     if file_type == "typst" then
-        create_maps("nvi", "RMakeRmd",  "kr", "<Cmd>lua require('r.typst').make('default')")
+        if vim.api.nvim_buf_get_name(0):lower():find("%.[Rr][Tt][Yy][Pp]$") then
+            create_maps("nvi", "RMakeRmd",  "kr", "<Cmd>lua require('r.typst').make('default')")
+        end
         create_maps("nvi", "RMakePDFK", "kp", "<Cmd>lua require('r.typst').make('pdf')")
         return
     end
diff --git a/lua/r/typst.lua b/lua/r/typst.lua
index c8becaa..9f1705c 100644
--- a/lua/r/typst.lua
+++ b/lua/r/typst.lua
@@ -4,7 +4,11 @@ M.make = function(outform)
     vim.cmd("update")
     local send = require("r.send").cmd
     if outform == "pdf" then
-        send('knitr::knit2pdf("' .. vim.api.nvim_buf_get_name(0) .. '")')
+        if vim.api.nvim_buf_get_name(0):lower():find("%.[Rr][Tt][Yy][Pp]$") then
+            send('knitr::knit2pdf("' .. vim.api.nvim_buf_get_name(0) .. '")')
+        else
+            send([[system("typst compile ']] .. vim.api.nvim_buf_get_name(0) .. [['")]])
+        end
     else
         send('knitr::knit("' .. vim.api.nvim_buf_get_name(0) .. '")')
     end

@jalvesaq
Copy link
Copy Markdown
Member Author

We might want to have knitr version updated on CRAN before merging in main?

On the one hand, I think it's wiser to wait. On the other hand, we may have to rebase the branch and possibly resolve conflicts if it takes too long for a new version of knitr to be released.

@PMassicotte
Copy link
Copy Markdown
Collaborator

Very good comments.

I am not sure what is going on with stylua, why my formatter has not picked it up. I will add a pre-hook to check that.

…ne return

fix(typst_rnvim.lua): ensure typst injections only apply to typst files

Co-Authored-By: Jakson Alves de Aquino <jalvesaq@gmail.com>
@PMassicotte PMassicotte marked this pull request as ready for review May 16, 2026 11:26
@PMassicotte
Copy link
Copy Markdown
Collaborator

@jalvesaq I added your excellent suggestions and added you as co-author of the commit.

@jalvesaq
Copy link
Copy Markdown
Member Author

Thank you, @PMassicotte!

@gueyenono, could you try the branch from this pull request?

@gueyenono
Copy link
Copy Markdown

Thank you, @PMassicotte!

@gueyenono, could you try the branch from this pull request?

My apologies. I never saw the notification for this message.
I quickly installed the branch version and everything seems to work fine. It just turns out that R code chunks are all green, so no code highlighting there. The highlighting for Typst works well though.

@jalvesaq
Copy link
Copy Markdown
Member Author

The highlighting of R code is expected to work if the file extension is .Rtyp (case insensitive). I've just simplified the pattern used to recognize RTypst, but it was a cosmetic change and shouldn't improve the file type recognition.

What is the extension of your RTypst files?

@gueyenono
Copy link
Copy Markdown

My file extension is "Rtyp", but I also use Nix. So I'm checking if the issue is not with my configuration.

@gueyenono
Copy link
Copy Markdown

Actually I found the issue. I created my code chunk with {r} instead of r. It all works perfectly now. Thank you.

@jalvesaq
Copy link
Copy Markdown
Member Author

The header of the code chunk should be {r} for .Rtyp files (which will be converted into .typ by knitr). The header should be r for .typ files.

Did you try <LocalLeader>kp to generate the PDF from the .Rtyp file? It should work with:

```{r}
x <- 100
```

The value of `x` is `r x`.

but not with:

```r
x <- 100
```

The value of `x` is `r x`.

@PMassicotte
Copy link
Copy Markdown
Collaborator

Actually I found the issue. I created my code chunk with {r} instead of r. It all works perfectly now. Thank you.

I am also on NixOS and both work on my side. Not sure what can cause this issue on your side.

@gueyenono
Copy link
Copy Markdown

You are right. With r as the chunk header, the code in the chunk gets the proper highlighing, but it does execute in the compiled PDF. Also, I did not know that knitr also supported actual typ files!

@jalvesaq
Copy link
Copy Markdown
Member Author

You are right. With r as the chunk header, the code in the chunk gets the proper highlighing, but it does execute in the compiled PDF.

I guess you meant "doesn't execute"...

I did not know that knitr also supported actual typ files!

The development version of knitr supports Typst if the extension is .Rtyp and the block header is {r}, not r.

What about this: "It just turns out that R code chunks are all green"? Did you manage to get R code highlighted in RTypst files?

@gueyenono
Copy link
Copy Markdown

gueyenono commented May 26, 2026

You are right. With r as the chunk header, the code in the chunk gets the proper highlighing, but it does execute in the compiled PDF.

I guess you meant "doesn't execute"...

I did not know that knitr also supported actual typ files!

The development version of knitr supports Typst if the extension is .Rtyp and the block header is {r}, not r.

What about this: "It just turns out that R code chunks are all green"? Did you manage to get R code highlighted in RTypst files?

  1. Yes, I meant it "doesn't" execute
  2. Okay, that's what you meant.
  3. Actually, my issue was the way I added chunk options:

#```{r plot, out.width="80%"}

#My code is here.

#```
The fact that there was no comma between r and `plot` (the chunk label) was the reason why the whole chunk was green. It still ran okay, but there was no highlight. Adding the comma fixes it.

@jalvesaq
Copy link
Copy Markdown
Member Author

The fact that there was no comma between r and plot (the chunk label) was the reason why the whole chunk was green. It still ran okay, but there was no highlight.

I fixed the pattern used to recognize the language of the block, and it now works with a space after the language name, but not with a TAB character.

@jalvesaq
Copy link
Copy Markdown
Member Author

We are listing more tree-sitter parsers in the section Installation of dependencies than are needed by everyone. The parsers for markdown and markdown_inline are installed by default. So, perhaps we don't need to list them as dependencies. The recommendation could be:

   Tree-sitter parsers for `r`, `csv`, and:
         - `yaml` for RMarkdown (`.Rmd`), Quarto (`.qmd`), Rnoweb (`.Rnw`) or
           RTypst (`.Rtyp`) documents.
         - `latex` and `rnoweb` for Rnoweb documents.
         - `typst` for Rtypst documents.
      You can install them using any tree-sitter parser manager (e.g., the
      `nvim-treesitter` or `tree-sitter-manager` plugins) or manually.
      https://github.com/nvim-treesitter/nvim-treesitter
      https://github.com/romus204/tree-sitter-manager.nvim

Perhaps even regarding csv it could be:

         - `csv` for viewing matrices and data frames.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants