-
Notifications
You must be signed in to change notification settings - Fork 139
R markdown templates #984
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
R markdown templates #984
Changes from all commits
e2d8746
04aa9c1
5d9365d
7abf2da
e82989b
d310d5a
442b825
8e70e32
fa47d9e
b7cdb6f
414db8a
c0ba218
4b0463c
a5f8f98
32b7f68
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| requireNamespace("jsonlite") | ||
| requireNamespace("yaml") | ||
|
|
||
| pkgs <- .packages(all.available = TRUE) | ||
| templates <- new.env() | ||
| template_dirs <- lapply(pkgs, function(pkg) { | ||
| dir <- system.file("rmarkdown/templates", package = pkg) | ||
| if (dir.exists(dir)) { | ||
| ids <- list.dirs(dir, full.names = FALSE, recursive = FALSE) | ||
| for (id in ids) { | ||
| file <- file.path(dir, id, "template.yaml") | ||
| if (file.exists(file)) { | ||
| data <- yaml::read_yaml(file) | ||
| data$id <- id | ||
| data$package <- pkg | ||
| templates[[paste0(pkg, "::", id)]] <- data | ||
| } | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| template_list <- unname(as.list(templates)) | ||
| lim <- Sys.getenv("VSCR_LIM") | ||
| json <- jsonlite::toJSON(template_list, auto_unbox = TRUE) | ||
| cat(lim, json, lim, sep = "\n", file = stdout()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| import { QuickPickItem, QuickPickOptions, Uri, window, workspace } from 'vscode'; | ||
| import { extensionContext } from '../extension'; | ||
| import { executeRCommand, getCurrentWorkspaceFolder, getRpath, ToRStringLiteral, spawnAsync, getConfirmation } from '../util'; | ||
| import * as cp from 'child_process'; | ||
| import * as path from 'path'; | ||
| import * as fs from 'fs'; | ||
| import * as os from 'os'; | ||
|
|
||
| interface TemplateInfo { | ||
| id: string; | ||
| package: string; | ||
| name: string; | ||
| description: string; | ||
| create_dir: boolean; | ||
| } | ||
|
|
||
| interface TemplateItem extends QuickPickItem { | ||
| info: TemplateInfo; | ||
| } | ||
|
|
||
| async function getTemplateItems(cwd: string): Promise<TemplateItem[]> { | ||
| const lim = '---vsc---'; | ||
| const rPath = await getRpath(); | ||
| const options: cp.CommonOptions = { | ||
| cwd: cwd, | ||
| env: { | ||
| ...process.env, | ||
| VSCR_LIM: lim | ||
| } | ||
| }; | ||
|
|
||
| const rScriptFile = extensionContext.asAbsolutePath('R/rmarkdown/templates.R'); | ||
| const args = [ | ||
| '--silent', | ||
| '--slave', | ||
| '--no-save', | ||
| '--no-restore', | ||
| '-f', | ||
| rScriptFile | ||
| ]; | ||
|
|
||
| try { | ||
| const result = await spawnAsync(rPath, args, options); | ||
| if (result.status !== 0) { | ||
| throw result.error || new Error(result.stderr); | ||
| } | ||
| const re = new RegExp(`${lim}(.*)${lim}`, 'ms'); | ||
| const match = re.exec(result.stdout); | ||
| if (match.length !== 2) { | ||
| throw new Error('Could not parse R output.'); | ||
| } | ||
| const json = match[1]; | ||
| const templates = <TemplateInfo[]>JSON.parse(json) || []; | ||
| const items = templates.map((x) => { | ||
| return { | ||
| alwaysShow: false, | ||
| description: `{${x.package}}`, | ||
| label: x.name + (x.create_dir ? ' $(new-folder)' : ''), | ||
| detail: x.description, | ||
| picked: false, | ||
| info: x | ||
| }; | ||
| }); | ||
| return items; | ||
| } catch (e) { | ||
| console.log(e); | ||
| void window.showErrorMessage((<{ message: string }>e).message); | ||
| } | ||
| } | ||
|
|
||
| async function launchTemplatePicker(cwd: string): Promise<TemplateItem> { | ||
| const options: QuickPickOptions = { | ||
| matchOnDescription: true, | ||
| matchOnDetail: true, | ||
| canPickMany: false, | ||
| ignoreFocusOut: false, | ||
| placeHolder: '', | ||
| onDidSelectItem: undefined | ||
| }; | ||
|
|
||
| const items = await getTemplateItems(cwd); | ||
|
|
||
| const selection: TemplateItem = await window.showQuickPick<TemplateItem>(items, options); | ||
| return selection; | ||
| } | ||
|
|
||
| async function makeDraft(file: string, template: TemplateItem, cwd: string): Promise<string> { | ||
| const fileString = ToRStringLiteral(file, ''); | ||
| const cmd = `cat(normalizePath(rmarkdown::draft(file='${fileString}', template='${template.info.id}', package='${template.info.package}', edit=FALSE)))`; | ||
| return await executeRCommand(cmd, cwd, (e: Error) => { | ||
| void window.showErrorMessage(e.message); | ||
| return ''; | ||
| }); | ||
| } | ||
|
|
||
| export async function newDraft(): Promise<void> { | ||
| const cwd = getCurrentWorkspaceFolder()?.uri.fsPath ?? os.homedir(); | ||
| const template = await launchTemplatePicker(cwd); | ||
| if (!template) { | ||
| return; | ||
| } | ||
|
|
||
| if (template.info.create_dir) { | ||
| let defaultPath = path.join(cwd, 'draft'); | ||
| let i = 1; | ||
| while (fs.existsSync(defaultPath)) { | ||
| defaultPath = path.join(cwd, `draft_${++i}`); | ||
| } | ||
| const uri = await window.showSaveDialog({ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The let defaultPath = path.join(cwd, 'draft');
let i = 1;
while(fs.existsSync(defaultPath)){
defaultPath = path.join(cwd, `draft_${++i}`);
}
const uri = await window.showSaveDialog({
defaultUri: Uri.file(defaultPath),
filters: {
'Folder': ['']
},
saveLabel: 'Create Folder',
title: 'R Markdown: New Draft'
});The open dialogue seems to have an option to select only folders, but does not allow to select non-existing folders
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is something I'm also considering. I tried
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also,
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I update the code to check if this folder already exists and let user confirm whether to remove it before creating the draft folder. |
||
| defaultUri: Uri.file(defaultPath), | ||
| filters: { | ||
| 'Folder': [''] | ||
| }, | ||
| saveLabel: 'Create Folder', | ||
| title: 'R Markdown: New Draft' | ||
| }); | ||
|
|
||
| if (uri) { | ||
| const parsedPath = path.parse(uri.fsPath); | ||
| const dir = path.join(parsedPath.dir, parsedPath.name); | ||
| if (fs.existsSync(dir)) { | ||
| if (await getConfirmation(`Folder already exists. Are you sure to replace the folder?`)) { | ||
| fs.rmdirSync(dir, { recursive: true }); | ||
| } else { | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| const draftPath = await makeDraft(uri.fsPath, template, cwd); | ||
| if (draftPath) { | ||
| await workspace.openTextDocument(draftPath) | ||
| .then(document => window.showTextDocument(document)); | ||
| } | ||
| } | ||
| } else { | ||
| const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vscode-R-')); | ||
| const tempFile = path.join(tempDir, 'draft.Rmd'); | ||
| const draftPath = await makeDraft(tempFile, template, cwd); | ||
| if (draftPath) { | ||
| const text = fs.readFileSync(draftPath, 'utf8'); | ||
| await workspace.openTextDocument({ language: 'rmd', content: text }) | ||
| .then(document => window.showTextDocument(document)); | ||
| } | ||
| fs.rmdirSync(tempDir, { recursive: true }); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.