Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RMarkdown: Add run & navigation commands. More customization. Refactor. #465

Merged
merged 21 commits into from
Nov 28, 2020

Conversation

kar9222
Copy link
Contributor

@kar9222 kar9222 commented Nov 24, 2020

This is my first time playing around with Typescript/javascript. So please review the codes in details thanks! 😅 😂 🤣

Summary

What problem did you solve?

  • Add and refactor RMarkdown navigation and run commands for both extension and RMarkdown code lens.
  • User-specified RMarkdown code lens commands and its orders (that is, code lens respect user-specified orders) via settings UI (Rmarkdown: Code Lens Commands) or settings.json (r.rmarkdown.codeLensCommands). See below.
  • More customizations
    • Enable/disable RMarkdown CodeLens, which are inline commands/buttons e.g. 'Run Chunk | Run Above' shown on the first line of each code chunk.
    • Customize chunk background color
  • User-friendly options message for new users (see below)

Also closes #262

Usage

Customize via Settings UI or put this at keybindings.json
{
    // Rmarkdown -------------------------------------

    // Selection & navigation
    {
        "key": "shift+alt+s",
        "command": "r.selectCurrentChunk",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },
    { 
        "key": "shift+alt+p",
        "command": "r.goToPreviousChunk",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },
    { 
        "key": "shift+alt+n",
        "command": "r.goToNextChunk",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },

    // Run  
    {
        "key": "ctrl+shift+alt+p",
        "command": "r.runPreviousChunk",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },
    {
        "key": "ctrl+shift+alt+n",
        "command": "r.runNextChunk",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },
    {
        "key": "ctrl+alt+n",
        "command": "r.runCurrentAndBelowChunks",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },
    {
        "key": "ctrl+alt+r",
        "command": "r.runAllChunks",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },

    {
        "key": "ctrl+shift+alt+n",
        "command": "r.runNextChunk",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },
    {
        "key": "ctrl+shift+alt+n",
        "command": "r.runNextChunk",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },
    {
        "key": "ctrl+alt+e",
        "command": "r.runBelowChunks",
        "when": "editorTextFocus && editorLangId == 'rmd'"
    },

    // Default: Just for reference
    // {
    //     "key": "ctrl+alt+p",
    //     "command": "r.runAboveChunks",
    //     "when": "editorTextFocus && editorLangId == 'rmd'"
    // },
    // {
    //     "key": "ctrl+shift+enter",
    //     "command": "r.runCurrentChunk",
    //     "when": "editorTextFocus && editorLangId == 'rmd'"
    // },
}

Names in settings UI Command Sample keybinding Notes
R: Select Current Chunk r.selectCurrentChunk shift+alt+s e.g. for delete/copy/cut
R: Run Current Chunk r.runCurrentChunk ctrl+shift+enter (previously implemented)
R: Run Above Chunks r.runAboveChunks ctrl+alt+p (previously implemented)
R: Run Current And Below Chunk r.runCurrentAndBelowChunks ctrl+alt+n
R: Run Below Chunks r.runBelowChunks ctrl+alt+e
R: Run Previous Chunk r.runPreviousChunk ctrl+shift+alt+p
R: Run Next Chunk r.runNextChunk ctrl+shift+alt+n
R: Run All Chunks r.runAllChunks ctrl+alt+r
R: Go To Previous Chunk r.goToPreviousChunk shift+alt+p
R: Go To Next Chunk r.goToNextChunk shift+alt+n

Animation for both

  • Add and refactor RMarkdown navigation and run commands.
  • User-specified RMarkdown code lens options and its orders (that is, code lens respect user-specified orders)

(Click to enlarge)

rmarkdown

Images for User-specified RMarkdown code lens commands and its orders (that is, code lens respect user-specified orders)

1212
zz1
zz2

Customize options and order (code lens respect user-specified orders) via settings UI (RMarkdown Code Lens: Option) or settings.json (r.rMarkdownCodeLens.option).

{
  "r.rmarkdown.codeLensCommands": [
    "r.runCurrentChunk",
    "r.runAboveChunks",
    "r.runCurrentAndBelowChunks",
    "goToPreviousChunk",
    "goToNextChunk",
    "selectAllChunk",
    // more...
  ]
}

More customizations

  • Enable/disable RMarkdown CodeLens, which are inline commands/buttons e.g. 'Run Chunk | Run Above' shown on the first line of each code chunk.
  • Customize chunk background color

User-friendly message for new users

a3

Enable RMarkdown CodeLens (default)

"r.rmarkdown.enableCodeLens": true (default)

a4

Disable RMarkdown CodeLens

"r.rmarkdown.enableCodeLens": false

  • Disable only RMarkdown CodeLens, leaving other CodeLens (e.g. Git lens) enabled.
  • Chunk background color is also enabled by default

hahaz

Customize chunk background colors

r.rmarkdown.chunkBackgroundColor: "rgba(128, 128, 128, 0.1)",

c1

r.rmarkdown.chunkBackgroundColor: "rgba(255, 165, 0, 0.1)",

c2

Disable background color
r.rmarkdown.chunkBackgroundColor: "",

c3

Use cases for both r.runFromCurrentToBelowChunks & r.runBelowChunks

  1. Edit current chunk, r.runCurrentChunk for checking values/etc and, when done, r.runBelowChunks from the next chunk (excluding the current chunk) to all chunks below
  2. After checking that the overall codes are working beautifully, edit current chunk and r.runFromCurrentToBelowChunks from the current chunk to all chunks below

use_cases

Design decisions & known bugs

By design, these commands work for both cases when the cursor is

  • within the chunk (including both 'chunk start line' and 'chunk end line')
  • outside the chunk

Case 'within the chunk'

Works as expected

Case cursor outside of chunk

The reason for including cases for 'outside the chunk' is to make it work for

  • Navigation commands: goToPreviousChunk, goToNextChunk, goToFirstChunk, goToLastChunk, which is very convenient especially for large Rmd files with many chunks and a lot of text in between chunks.
  • Less useful, but still useful for run commands (e.g. e.g. runCurrentAndBelowChunks) and to make the behaviour consistent with navigation commands

As a result, when the cursor is outside the chunk, the definition of the 'current chunk' and 'next chunk' become blur, that is, there are no 'correct' definitions, simply because there is no definition of 'current chunk'. To make it makes more sense, when cursor is outside the chunk, for the reason that navigation commands (e.g. goToNextChunk) is more useful than run commands (e.g. runCurrentAndBelowChunks), by design, the definition of the 'current chunk' and 'next chunk' are the same--the very next chunk. That is, e.g.

  • goToNextChunk, runCurrentChunk and runNextChunk trigger the very next chunk
  • both runCurrentAndBelowChunks and runBelowChunks trigger from the very next chunk to the last chunk
  • so on...

Compared to the case where

  • current chunk is the next chunk
  • next chunk is the next next chunk
  • then, for goToNextChunk will jump to next next chunk when cursor is outside the chunk, which doesn't make much sense. And it also doesn't make sense to make another command for goToCurrentChunk just for this trade-off.

So it's a trade-off for its design. I think the overall benefits e.g. goToNextChunk, goToFirstChunk outweigh the minor 'unexpected' behaviours when the cursor is outside the chunk, especially for large Rmd files with many chunks and a lot of text in between the chunks. Code navigation is often the most time consuming part. e.g.

  • after writing a lot of text in between the chunk, with just a keybinding shift+alt+p goToPreviousChunk often saves a lot of time

Lastly, a known bug is when the cursor is below the very last chunk,

  • Commands like goToFirstChunk, goToLastChunk, runAllChunks work as expected (in all cases) becauses these commands doesn't require the 'current line' as argument so the cursor position doesn't affect it at all.
  • But I couldn't make it work for goToPreviousChunk, runPreviousChunk and runAboveChunks I thought the bugs is in internal function getCurrentChunk but I spent some time on it there is still some bug. I will come back and work in near future if that's okay. The bug is just weird.

Tested on Windows WSL remote extension

@kar9222 kar9222 changed the title Add runFromCurrentToBelowChunks, runBelowChunks Add run***Chunk commands for Rmd Nov 25, 2020
@kar9222
Copy link
Contributor Author

kar9222 commented Nov 25, 2020

For this same PR, I added new commands and manual tests in my very first post above. Please re-check. Also edited the PR title.

  • r.runAllChunks
  • r.runPreviousChunk
  • r.runNextChunk

src/extension.ts Outdated Show resolved Hide resolved
@renkun-ken
Copy link
Member

renkun-ken commented Nov 25, 2020

Thanks for implementing numerous run chunks commands. Previously, I didn't factor the code. The CodeLens commands and extension commands thus do not share the algorithm of chunk detection but independently does the work.

Now that there are many versions of run chunks, I guess we should implement a function that returns an array of the info of each chunk and a function that returns the chunk id of a given all chunks detected and a document position. Then all run chunk commands and CodeLens commands could be much simplified by using the functions.

Based on this, we could also implement go-to-chunk commands such as "go to previous chunk", "go to next chunk", "go to first chunk", "go to last chunk".

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 25, 2020

Hi @renkun-ken, thanks for everything you have done for VSCode-R & {languageserver}. Yeah I am happy and will try my best to help out a bit for the stuff that I used everyday 😄

This is actually my first time playing around with Typescript/Javascript. I have no experience with object-oriented languages too. Happy to learn if it's not too hard. The stuffs I did here were mainly based on my experience implementing some custom Vimscript for my Neovim editor e.g. finding patterns in documents, with very almost no/minimal understanding of Typescript and internals of VSCode extensions. So just a heads up that if the refactoring is too hard then I might need some time to learn a bit of Typescript from scratch?

So basically I just scanned through your Typescript implementations and playing around a bit here and there lol. So I'm actually a bit lost when you mention about CodeLens commands and extension commands, etc. I actually don't know what is a 'codelens' lol.

Please bare with me while I'm trying to understand what you mean here. I just read through a bit of extension.ts and rmarkdown.ts. So the CodeLens are actually the buttons for Rmd docs e.g. Run Chunk and Run Above implemented in RMarkdownCodeLensProvider.

Erm...So basically you want to refactor reusable parts

I was actually trying to figure out what you mean...in details...an hour ago but I just thought that it might be easier to try a rough refactoring and see if that's what you wanted. Because the hard part is figuring out the chunk where the cursor is positioned and also covering the cases for when the cursor is within or outside the chunk.

I just did a rough WIP commit for refactoring runFromCurrentToBelowChunks and it works. Do you mind having a look? NOTE the TODO mark in rmarkdown.ts

Based on the WIP commit, getChunkInfo returns info e.g. chunkRanges & codeRanges for both extension commands and RMarkdownCodeLensProvider

@renkun-ken
Copy link
Member

CodeLens commands are inline commands "Run Chunk | Run Above" showing in the document above each R chunk while extension commands are commands declared in packages.json and registered on extesion activation.

@renkun-ken
Copy link
Member

renkun-ken commented Nov 25, 2020

I could imagine a cleaner approach is to implement something like:

interface RMarkdownChunk {
  id: number;
  startLine: number;
  endLine: number;
  language: string;
  options: string;
  eval: boolean;
  chunkRange: Range;
  codeRange: Range;
}

function getChunks(document: TextDocument): RMarkdownChunk[]

function getCurrentChunk(chunks: RMarkdownChunk[], line: number): RMarkdownChunk
function getPreviousChunk(chunks: RMarkdownChunk[], line: number): RMarkdownChunk
function getNextChunk(chunks: RMarkdownChunk[], line: number): RMarkdownChunk

function runCurrentChunk(chunks: RMarkdownChunk[], line: number)
function runPreviousChunk(chunks: RMarkdownChunk[], line: number)
function runNextChunk(chunks: RMarkdownChunk[], line: number)

function runAboveChunks(chunks: RMarkdownChunk[], line: number)
function runAboveAndCurrentChunks(chunks: RMarkdownChunk[], line: number)
function runBelowChunks(chunks: RMarkdownChunk[], line: number)
function runCurrentAndBelowChunks(chunks: RMarkdownChunk[], line: number)

where getChunks scans the document and returns all chunks, and all other functions just do some array filtering and mapping to obtain the code ranges of the target chunk(s) and run the code ranges in term.

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 25, 2020

Thanks very much. That looks good.

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 26, 2020

A quick update on the latest WIP: Refactor commit 990bb5b

Very briefly

  • I got them working for both extension commands and codeLends commands.
  • Added additional commands goToPreviousChunk, goToNextChunk, goToFirstChunk, goToLastChunk

TODO

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 26, 2020

The latest WIP GIF for both extension commands and codeLens buttons

r.goToPreviousChunk: "Shift+alt+p"
r.goToNextChunk: "Shift+alt+n"
r.goToFirstChunk: "Shift+alt+l"
r.goToLastChunk: "Shift+alt+n"

r.runAboveChunks: "Ctrl+alt+p"
r.runCurrentAndBelowChunks: "Ctrl+alt+n"
r.runBelowChunks: "Ctrl+alt+e"
r.runPreviousChunk: "Ctrl+shift+alt+p"
r.runNextChunk: "Ctrl+shift+alt+n"

latest

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 26, 2020

For goToPrevious and other goTo***, I intentionally set it as 1 line below chunk start line as most of the time we want to edit the codes directly instead of the chunk options? Or should I set it exactly at the chunk start line?

@renkun-ken
Copy link
Member

For goToPrevious and other goTo***, I intentionally set it as 1 line below chunk start line as most of the time we want to edit the codes directly instead of the chunk options? Or should I set it exactly at the chunk start line?

I believe it makes good sense.

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 27, 2020

It seems like values of r.rmd.codeLens.option can't be accessed? The latest things I tried on my machine with import { config } from './util';

Type array default settings

// Default package.json
"r.rmd.codeLens.option": {
  "type": "array",
  "default": [
    "r.runCurrentChunk",
    "r.runAboveChunks",
    "r.runCurrentAndBelowChunks",
    "r.runAllChunks",
    "r.goToPreviousChunk",
    "r.goToNextChunk"
  ],
  "description": "R RMarkdown CodeLens options.",
  "items": {
    "type": "string"
  }
}
import { config } from './util';

// This works
const rmdCodeLensOpt = ['r.runAllChunks', 'r.goToPreviousChunk'];

// Doesn't work
const rmdCodeLensOpt: string[] = config().get('r.rmd.codeLens.option');

return this.codeLenses.filter(e => rmdCodeLensOpt.includes(e.command.command));

...which produces

1

Similarly, for type string default settings

// Default package.json
"r.rmd.codeLens.option": {
  "type": "string",
  "default": "r.goToNextChunk",
  "description": "test"
}
// This work
const rmdCodeLensOpt = 'r.runAllChunks';

// Doesn't work
const rmdCodeLensOpt: string = config().get('r.rmd.codeLens.option');

return this.codeLenses.filter(e => e.command.command === rmdCodeLensOpt);

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 27, 2020

Oh nvm I fixed it after reading docs of workspace.getConfiguration in VSCode module. It should be rmd.codeLens.option instead of r.rmd.codeLens.option

Also tested that it works with both

  • default settings
  • user-specified in settings UI or settings.json

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 27, 2020

Personally I turn off codelens as I mostly use keyboard. But now users who use buttons can now easily customize their preferred RMarkdown codelens buttons to show directly in their editor e.g. they can use keybindings for commonly used commands like runCurrentChunk while use buttons for occasionally used commands like runCurrentAndBelowChunks or runAllChunks. It's best of both world for them. I list all available options in the settings UI and they can even customize on the fly lol. See GIF below.

I still can't believe VSCode is so customizable. The API is superb too.

rmd_codelens_options

This is the package.json options

13

{
  "r.rMarkdownCodeLens.option": {
    "type": "array",
    "items": {
      "type": "string"
    },
    "default": [
      "r.runCurrentChunk",
      "r.runAboveChunks",
      "r.runCurrentAndBelowChunks",
      "r.runAllChunks",
      "r.goToPreviousChunk",
      "r.goToNextChunk"
    ],
    "description": "Available options:\n\nr.runCurrentChunk\nr.runAboveChunks\nr.runCurrentAndBelowChunks\nr.runBelowChunks\nr.runAllChunks\nr.runPreviousChunk\nr.runNextChunk\nr.goToPreviousChunk\nr.goToNextChunk\nr.goToFirstChunk\nr.goToLastChunk\n\n----------------------------------\nSpecify options:"
  },

}

@kar9222 kar9222 changed the title Add run***Chunk commands for Rmd RMarkdown: Add run & navigation commands. Refactor. Nov 27, 2020
@Ikuyadeu
Copy link
Member

Thank you for your early fix.
LGTM.

@Ikuyadeu Ikuyadeu merged commit 3e5822b into REditorSupport:master Nov 28, 2020
@renkun-ken
Copy link
Member

renkun-ken commented Nov 28, 2020

For the following rmd:

# Title

This is a test document.

## Section 1

```{r}
1
```

```{r}
2
```

```{r,eval=FALSE}
3
```

```{python}
def fun(x,y):
  return x+y
```

```{sql}
select * from table
```

```{R chunk-1}
4
```

## Section 2

```{r plot1}
5
```

```{r plot2}
6
```

The run chunks commands seem to ignore chunk language, i.e. they run chunks of all languages. Instead, they should only send R chunks to the terminal.

Also, some edge cases are not properly handled yet. For example, Run Above the first chunk, or Run Below the last chunk, or select current chunk with cursor outside any chunk will cause errors like

image

I think all these commands should handle the case where no chunk is found by simply ignoring them.

@Ikuyadeu
Copy link
Member

Ikuyadeu commented Nov 28, 2020

@renkun-ken Thank you for your test, I made maintenance issues.
Your posted issue #473
My refactoring issue #472

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 28, 2020

@renkun-ken

The error handling can be easily fixed (i guess) ?

The 'case for when cursor is below chunk', I spent few hours on it but the bug is just weird. Without being able to check the values of variables like in REPL debugging is quite hard for me. I still don't get the way of Javascript way of debugging. Need to learn a bit of it. So it might take some time to fix this particular bug.

For non-R languagues bugs, I haven't looked at it yet.

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 28, 2020

@renkun-ken I figured out the non-R languages bugs, this line https://github.com/Ikuyadeu/vscode-R/blob/3e5822b0e3bf1025e77a79aaf90e72af8db948c7/src/rmarkdown.ts#L65

only handles the CodeLens but doesn't handle the extension commands, which can be hot fixed by e.g.

// Original
export async function runCurrentChunk(chunks: RMarkdownChunk[] = _getChunks(),
                                      line: number = _getStartLine()) {
  const currentChunk = getCurrentChunk(chunks, line);
    runChunksInTerm([currentChunk.codeRange]);
}

export async function runCurrentChunk(chunks: RMarkdownChunk[] = _getChunks(),
                                      line: number = _getStartLine()) {
  const currentChunk = getCurrentChunk(chunks, line);
  if (currentChunk.language === 'r') {
    return runChunksInTerm([currentChunk.codeRange]);
  }
}

@kar9222
Copy link
Contributor Author

kar9222 commented Nov 28, 2020

If you think CodeLens and extension commands should be separated, then we can make a helper e.g.

function runChunksInTerm__R(chunk: RMarkdownChunk, range: Range) {
  if (chunk.language === 'r') {
    return runChunksInTerm([range]);
  }
}

kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
kar9222 added a commit to kar9222/vscode-R that referenced this pull request Nov 30, 2020
@kar9222 kar9222 mentioned this pull request Nov 30, 2020
renkun-ken added a commit that referenced this pull request Dec 1, 2020
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.

[Feature request] RMarkdown: "Run all chunks" & "Run previous"
3 participants