-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement snippet support in ark (#183)
* Implement snippet support in ark * Switch to `Option`, include `documentation`
- Loading branch information
1 parent
50d3cf5
commit aa252b3
Showing
3 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
{ | ||
"lib": { | ||
"prefix": "lib", | ||
"body": "library(${1:package})", | ||
"description": "Attach an R package" | ||
}, | ||
"src": { | ||
"prefix": "src", | ||
"body": "source(\"${1:file.R}\")", | ||
"description": "Source an R file" | ||
}, | ||
"ret": { | ||
"prefix": "ret", | ||
"body": "return(${1:code})", | ||
"description": "Return a value from a function" | ||
}, | ||
"mat": { | ||
"prefix": "mat", | ||
"body": "matrix(${1:data}, nrow = ${2:rows}, ncol = ${3:cols})", | ||
"description": "Define a matrix" | ||
}, | ||
"sg": { | ||
"prefix": "sg", | ||
"body": [ | ||
"setGeneric(\"${1:generic}\", function(${2:x, ...}) {", | ||
"\tstandardGeneric(\"${1:generic}\")", | ||
"})" | ||
], | ||
"description": "Define a generic" | ||
}, | ||
"sm": { | ||
"prefix": "sm", | ||
"body": [ | ||
"setMethod(\"${1:generic}\", ${2:class}, function(${2:x, ...}) {", | ||
"\t${0}", | ||
"})" | ||
], | ||
"description": "Define a method for a generic function" | ||
}, | ||
"sc": { | ||
"prefix": "sc", | ||
"body": "setClass(\"${1:Class}\", slots = c(${2:name = \"type\"}))", | ||
"description": "Define a class definition" | ||
}, | ||
"if": { | ||
"prefix": "if", | ||
"body": [ | ||
"if (${1:condition}) {", | ||
"\t${0}", | ||
"}" | ||
], | ||
"description": "Conditional expression" | ||
}, | ||
"el": { | ||
"prefix": "el", | ||
"body": [ | ||
"else {", | ||
"\t${0}", | ||
"}" | ||
], | ||
"description": "Conditional expression" | ||
}, | ||
"ei": { | ||
"prefix": "ei", | ||
"body": [ | ||
"else if (${1:condition}) {", | ||
"\t${0}", | ||
"}" | ||
], | ||
"description": "Conditional expression" | ||
}, | ||
"fun": { | ||
"prefix": "fun", | ||
"body": [ | ||
"${1:name} <- function(${2:variables}) {", | ||
"\t${0}", | ||
"}" | ||
], | ||
"description": "Function skeleton" | ||
}, | ||
"for": { | ||
"prefix": "for", | ||
"body": [ | ||
"for (${1:variable} in ${2:vector}) {", | ||
"\t${0}", | ||
"}" | ||
], | ||
"description": "Define a loop" | ||
}, | ||
"while": { | ||
"prefix": "while", | ||
"body": [ | ||
"while (${1:condition}) {", | ||
"\t${0}", | ||
"}" | ||
], | ||
"description": "Define a loop" | ||
}, | ||
"switch": { | ||
"prefix": "switch", | ||
"body": [ | ||
"switch (${1:object},", | ||
"\t${2:case} = ${3:action}", | ||
")" | ||
], | ||
"description": "Define a switch statement" | ||
}, | ||
"apply": { | ||
"prefix": "apply", | ||
"body": "apply(${1:array}, ${2:margin}, ${3:...})", | ||
"description": "Use the apply family" | ||
}, | ||
"lapply": { | ||
"prefix": "lapply", | ||
"body": "lapply(${1:list}, ${2:function})", | ||
"description": "Use the apply family" | ||
}, | ||
"sapply": { | ||
"prefix": "sapply", | ||
"body": "sapply(${1:list}, ${2:function})", | ||
"description": "Use the apply family" | ||
}, | ||
"mapply": { | ||
"prefix": "mapply", | ||
"body": "mapply(${1:function}, ${2:...})", | ||
"description": "Use the apply family" | ||
}, | ||
"tapply": { | ||
"prefix": "tapply", | ||
"body": "tapply(${1:vector}, ${2:index}, ${3:function})", | ||
"description": "Use the apply family" | ||
}, | ||
"vapply": { | ||
"prefix": "vapply", | ||
"body": "vapply(${1:list}, ${2:function}, FUN.VALUE = ${3:type}, ${4:...})", | ||
"description": "Use the apply family" | ||
}, | ||
"rapply": { | ||
"prefix": "rapply", | ||
"body": "rapply(${1:list}, ${2:function})", | ||
"description": "Use the apply family" | ||
}, | ||
"ts": { | ||
"prefix": "ts", | ||
"body": "`r paste(\"#\", date(), \"------------------------------\\n\")`", | ||
"description": "Insert a datetime" | ||
}, | ||
"shinyapp": { | ||
"prefix": "shinyapp", | ||
"body": [ | ||
"library(shiny)", | ||
"", | ||
"ui <- fluidPage(", | ||
" ${0}", | ||
")", | ||
"", | ||
"server <- function(input, output, session) {", | ||
" ", | ||
"}", | ||
"", | ||
"shinyApp(ui, server)" | ||
], | ||
"description": "Define a Shiny app" | ||
}, | ||
"shinymod": { | ||
"prefix": "shinymod", | ||
"body": [ | ||
"${1:name}_UI <- function(id) {", | ||
" ns <- NS(id)", | ||
" tagList(", | ||
"\t${0}", | ||
" )", | ||
"}", | ||
"", | ||
"${1:name} <- function(input, output, session) {", | ||
" ", | ||
"}" | ||
], | ||
"description": "Define a Shiny module" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
crates/ark/src/lsp/completions/sources/composite/snippets.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// | ||
// snippets.rs | ||
// | ||
// Copyright (C) 2023 Posit Software, PBC. All rights reserved. | ||
// | ||
// | ||
|
||
use std::collections::HashMap; | ||
use std::sync::Once; | ||
|
||
use rust_embed::RustEmbed; | ||
use serde::Deserialize; | ||
use tower_lsp::lsp_types::CompletionItem; | ||
use tower_lsp::lsp_types::CompletionItemKind; | ||
use tower_lsp::lsp_types::Documentation; | ||
use tower_lsp::lsp_types::InsertTextFormat; | ||
use tower_lsp::lsp_types::MarkupContent; | ||
use tower_lsp::lsp_types::MarkupKind; | ||
|
||
use crate::lsp::completions::completion_item::completion_item; | ||
use crate::lsp::completions::types::CompletionData; | ||
|
||
#[derive(RustEmbed)] | ||
#[folder = "resources/snippets/"] | ||
struct Asset; | ||
|
||
#[derive(Deserialize)] | ||
struct Snippet { | ||
prefix: String, | ||
body: SnippetBody, | ||
description: String, | ||
} | ||
|
||
#[derive(Deserialize)] | ||
#[serde(untagged)] | ||
enum SnippetBody { | ||
Scalar(String), | ||
Vector(Vec<String>), | ||
} | ||
|
||
pub(super) fn completions_from_snippets() -> Vec<CompletionItem> { | ||
log::info!("completions_from_snippets()"); | ||
|
||
// Return clone of cached snippet completion items | ||
let completions = get_completions_from_snippets().clone(); | ||
|
||
completions | ||
} | ||
|
||
fn get_completions_from_snippets() -> &'static Vec<CompletionItem> { | ||
static INIT: Once = Once::new(); | ||
static mut SNIPPETS: Option<Vec<CompletionItem>> = None; | ||
|
||
INIT.call_once(|| unsafe { | ||
SNIPPETS = Some(init_completions_from_snippets()); | ||
}); | ||
|
||
unsafe { SNIPPETS.as_ref().unwrap() } | ||
} | ||
|
||
fn init_completions_from_snippets() -> Vec<CompletionItem> { | ||
// Load snippets JSON from embedded file | ||
let file = Asset::get("r.code-snippets").unwrap(); | ||
let snippets: HashMap<String, Snippet> = serde_json::from_slice(&file.data).unwrap(); | ||
|
||
let mut completions = vec![]; | ||
|
||
for snippet in snippets.values() { | ||
let label = snippet.prefix.clone(); | ||
let details = snippet.description.clone(); | ||
|
||
let body = match &snippet.body { | ||
SnippetBody::Scalar(body) => body.clone(), | ||
SnippetBody::Vector(body) => body.join("\n"), | ||
}; | ||
|
||
// Markup shows up in the quick suggestion documentation window, | ||
// so you can see what the snippet expands to | ||
let markup = vec!["```r", body.as_str(), "```"].join("\n"); | ||
let markup = MarkupContent { | ||
kind: MarkupKind::Markdown, | ||
value: markup, | ||
}; | ||
|
||
let mut item = | ||
completion_item(label, CompletionData::Snippet { text: body.clone() }).unwrap(); | ||
|
||
item.detail = Some(details); | ||
item.documentation = Some(Documentation::MarkupContent(markup)); | ||
item.kind = Some(CompletionItemKind::SNIPPET); | ||
|
||
item.insert_text = Some(body); | ||
item.insert_text_format = Some(InsertTextFormat::SNIPPET); | ||
|
||
completions.push(item); | ||
} | ||
|
||
completions | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::lsp::completions::sources::composite::snippets::completions_from_snippets; | ||
|
||
#[test] | ||
fn test_snippets() { | ||
let snippets = completions_from_snippets(); | ||
|
||
// Hash map isn't stable with regards to ordering | ||
let item = snippets.iter().find(|item| item.label == "lib").unwrap(); | ||
assert_eq!(item.detail, Some("Attach an R package".to_string())); | ||
assert_eq!(item.insert_text, Some("library(${1:package})".to_string())); | ||
|
||
// Multiline body | ||
let item = snippets.iter().find(|item| item.label == "if").unwrap(); | ||
assert_eq!( | ||
item.insert_text, | ||
Some("if (${1:condition}) {\n\t${0}\n}".to_string()) | ||
); | ||
} | ||
} |