Skip to content

Commit c05d696

Browse files
authored
Improve code chunk handling in base .R files (#1454)
* Fix `getChunks` for `.R` files - isChunkEndLine was selecting line with the *next* `# %%`, but needs to select the previous line - The last chunk was not selected because there is not another `# %%` after the last one. Instead, add a check for the last line of the file and close out the last chunk. * Fix chunkEndLine * Fix `getChunkLanguage`/`getChunkOptions` for `.R` file * Bug fix `getChunks` for `codeRange` * Fixes `getCurrentChunk` and makes much simpler: - Loop through chunks and grab the first one where the `chunk.endLine >= line` - Fix bug with `runPreviousChunk` when line is below last chunk * Ends code chunk in `.R` file when a section header is used, e.g. `# Header ----`
1 parent 63ee735 commit c05d696

File tree

1 file changed

+56
-37
lines changed

1 file changed

+56
-37
lines changed

src/rmarkdown/index.ts

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,26 @@ function isChunkStartLine(text: string, isRDoc: boolean) {
2525

2626
function isChunkEndLine(text: string, isRDoc: boolean) {
2727
if (isRDoc) {
28-
return (isRChunkLine(text));
28+
const isSectionHeader = text.match(/^#+\s*.*[-#+=*]{4,}/g);
29+
return (isRChunkLine(text) || isSectionHeader);
2930
} else {
3031
return (!!text.match(/^\s*```+\s*$/g));
3132
}
3233
}
3334

34-
function getChunkLanguage(text: string) {
35+
function getChunkLanguage(text: string, isRDoc: boolean = false) {
36+
if (isRDoc) {
37+
return 'r';
38+
}
3539
return text.replace(/^\s*```+\s*\{(\w+)\s*.*\}\s*$/g, '$1').toLowerCase();
3640
}
3741

38-
function getChunkOptions(text: string) {
39-
return text.replace(/^\s*```+\s*\{\w+\s*,?\s*(.*)\s*\}\s*$/g, '$1');
42+
function getChunkOptions(text: string, isRDoc: boolean = false) {
43+
if (isRDoc) {
44+
return text.replace(/^#+\s*%%/g, '');
45+
} else {
46+
return text.replace(/^\s*```+\s*\{\w+\s*,?\s*(.*)\s*\}\s*$/g, '$1');
47+
}
4048
}
4149

4250
function getChunkEval(chunkOptions: string) {
@@ -184,6 +192,7 @@ export function getChunks(document: vscode.TextDocument): RMarkdownChunk[] {
184192
let chunkId = 0; // One-based index
185193
let chunkStartLine: number | undefined = undefined;
186194
let chunkEndLine: number | undefined = undefined;
195+
let codeEndLine: number | undefined = undefined;
187196
let chunkLanguage: string | undefined = undefined;
188197
let chunkOptions: string | undefined = undefined;
189198
let chunkEval: boolean | undefined = undefined;
@@ -195,20 +204,30 @@ export function getChunks(document: vscode.TextDocument): RMarkdownChunk[] {
195204
chunkId++;
196205
chunkStartLine = line;
197206
chunkLanguage = getChunkLanguage(lines[line]);
198-
chunkOptions = getChunkOptions(lines[line]);
207+
chunkOptions = getChunkOptions(lines[line], isRDoc);
199208
chunkEval = getChunkEval(chunkOptions);
200209
}
201210
} else {
202-
if (isChunkEndLine(lines[line], isRDoc)) {
211+
// Second condition is for the last chunk in an .R file
212+
const isRDocAndFinalLine = (isRDoc && line === lines.length - 1);
213+
if (isChunkEndLine(lines[line], isRDoc) || isRDocAndFinalLine) {
203214
chunkEndLine = line;
204-
215+
codeEndLine = line - 1;
216+
217+
// isChunkEndLine looks for `# %%` in `.R` files, so if found, then need to go back one line to mark end of code chunk.
218+
if (isRDoc && !isRDocAndFinalLine) {
219+
chunkEndLine = chunkEndLine - 1;
220+
codeEndLine = chunkEndLine;
221+
line = line - 1;
222+
}
223+
205224
const chunkRange = new vscode.Range(
206225
new vscode.Position(chunkStartLine, 0),
207226
new vscode.Position(line, lines[line].length)
208227
);
209228
const codeRange = new vscode.Range(
210229
new vscode.Position(chunkStartLine + 1, 0),
211-
new vscode.Position(line - 1, lines[line - 1].length)
230+
new vscode.Position(codeEndLine, lines[codeEndLine].length)
212231
);
213232

214233
chunks.push({
@@ -237,35 +256,27 @@ function getCurrentChunk(chunks: RMarkdownChunk[], line: number): RMarkdownChunk
237256
return;
238257
}
239258

240-
const lines = textEditor.document.getText().split(/\r?\n/);
241-
242-
let chunkStartLineAtOrAbove = line;
243-
// `- 1` to cover edge case when cursor is at 'chunk end line'
244-
let chunkEndLineAbove = line - 1;
245-
246-
const isRDoc = isRDocument(textEditor.document);
247-
248-
while (chunkStartLineAtOrAbove >= 0 && !isChunkStartLine(lines[chunkStartLineAtOrAbove], isRDoc)) {
249-
chunkStartLineAtOrAbove--;
250-
}
251-
252-
while (chunkEndLineAbove >= 0 && !isChunkEndLine(lines[chunkEndLineAbove], isRDoc)) {
253-
chunkEndLineAbove--;
259+
// Case: If `chunks` is empty, return undefined
260+
if (chunks.length === 0) {
261+
return undefined;
254262
}
255-
256-
// Case: Cursor is within chunk
257-
if (chunkEndLineAbove < chunkStartLineAtOrAbove) {
258-
line = chunkStartLineAtOrAbove;
259-
} else {
260-
// Cases: Cursor is above the first chunk, at the first chunk or outside of chunk. Find the 'chunk start line' of the next chunk below the cursor.
261-
let chunkStartLineBelow = line + 1;
262-
while (!isChunkStartLine(lines[chunkStartLineBelow], isRDoc)) {
263-
chunkStartLineBelow++;
263+
264+
// Case: Cursor is above first chunk, use first chunk
265+
if (line < chunks[0].startLine) {
266+
return chunks[0];
267+
}
268+
// Case: Cursor is below last chunk, return last chunk
269+
if (line > chunks[chunks.length - 1].endLine) {
270+
return chunks[chunks.length - 1];
271+
}
272+
// chunks.filter(i => line >= i.startLine)[0];
273+
for (const chunk of chunks) {
274+
// Case: Cursor is within chunk, use current chunk
275+
// Case: Cursor is between, use next chunk below cursor
276+
if (chunk.endLine >= line) {
277+
return chunk;
264278
}
265-
line = chunkStartLineBelow;
266279
}
267-
const currentChunk = chunks.find(i => i.startLine <= line && i.endLine >= line);
268-
return currentChunk;
269280
}
270281

271282
// Alternative `getCurrentChunk` for cases:
@@ -335,7 +346,11 @@ export async function runPreviousChunk(chunks: RMarkdownChunk[] = _getChunks(),
335346
const currentChunk = getCurrentChunk(chunks, line);
336347
const previousChunk = getPreviousChunk(chunks, line);
337348

338-
if (previousChunk && previousChunk !== currentChunk) {
349+
// Case: cursor is below the last chunk, run last chunk
350+
if (currentChunk && line > currentChunk.endLine) {
351+
await(runChunksInTerm([currentChunk.codeRange]));
352+
// Case: currentChunk is not the first chunk, so run previousChunk
353+
} else if (previousChunk && previousChunk !== currentChunk) {
339354
await runChunksInTerm([previousChunk.codeRange]);
340355
}
341356

@@ -346,6 +361,7 @@ export async function runNextChunk(chunks: RMarkdownChunk[] = _getChunks(),
346361
const currentChunk = getCurrentChunk(chunks, line);
347362
const nextChunk = getNextChunk(chunks, line);
348363

364+
// Case: currentChunk is not the last chunk, so run nextChunk
349365
if (nextChunk && nextChunk !== currentChunk) {
350366
await runChunksInTerm([nextChunk.codeRange]);
351367
}
@@ -363,7 +379,8 @@ export async function runAboveChunks(chunks: RMarkdownChunk[] = _getChunks(),
363379

364380
const codeRanges: vscode.Range[] = [];
365381

366-
if (previousChunk !== currentChunk) {
382+
// Only do something if current chunk is not the first chunk
383+
if (currentChunk.id > 1) {
367384
for (let i = firstChunkId; i <= previousChunkId; i++) {
368385
const chunk = chunks.find(e => e.id === i);
369386
if (chunk?.eval) {
@@ -386,7 +403,9 @@ export async function runBelowChunks(chunks: RMarkdownChunk[] = _getChunks(),
386403
const lastChunkId = chunks.length;
387404

388405
const codeRanges: vscode.Range[] = [];
389-
if (nextChunk !== currentChunk) {
406+
407+
// Only do something if current chunk is not the last chunk
408+
if (currentChunk.id < lastChunkId) {
390409
for (let i = nextChunkId; i <= lastChunkId; i++) {
391410
const chunk = chunks.find(e => e.id === i);
392411
if (chunk?.eval) {

0 commit comments

Comments
 (0)