diff --git a/src/Components/codeMirror/customSnippets.ts b/src/Components/codeMirror/customSnippets.ts new file mode 100644 index 00000000..8298e279 --- /dev/null +++ b/src/Components/codeMirror/customSnippets.ts @@ -0,0 +1,126 @@ +import type { Completion } from "@codemirror/autocomplete"; +import { snippetCompletion } from "@codemirror/autocomplete"; + +type ShinySnippet = { label: string; code: string[] }; + +const shinySnippets: { + [key: string]: ShinySnippet[]; +} = { + r: [ + { + label: "shinyapp", + code: [ + "library(shiny)", + "library(bslib)", + "", + "ui <- page_${1:fluid}(", + " ${0}", + ")", + "", + "server <- function(input, output, session) {", + "", + "}", + "", + "shinyApp(ui, server)", + ], + }, + { + label: "shinymod", + code: [ + "${1:name}UI <- function(id) {", + " ns <- NS(id)", + " tagList(", + " ${0}", + " )", + "}", + "", + "${1:name}Server <- function(id) {", + " moduleServer(id, function(input, output, session) {", + " ", + " })", + "}", + ], + }, + ], + python: [ + { + label: "shinyapp", + code: [ + "from shiny import App, reactive, render, req, ui", + "", + "app_ui = ui.page_fluid(", + '\tui.input_slider("n", "N", 0, 100, 20),', + '\tui.output_text_verbatim("txt"),', + ")", + "", + "", + "def server(input, output, session):", + "\t@render.text", + "\tdef txt():", + '\t\treturn f"n*2 is {input.n() * 2}"', + "", + "", + "app = App(app_ui, server)", + "", + ], + }, + { + label: "shinyexpress", + code: [ + "from shiny.express import input, render, ui", + "", + 'ui.input_slider("n", "N", 0, 100, 20)', + "", + "", + "@render.text", + "def txt():", + '\treturn f"n*2 is {input.n() * 2}"', + "", + ], + }, + { + label: "shinymod", + code: [ + "from shiny import module, reactive, render, ui", + "", + "# ============================================================", + "# Module: ${1:modname}", + "# ============================================================", + "", + "@module.ui", + 'def $1_ui(label = "Increment counter"):', + "\treturn ui.div(", + '\t\t{"style": "border: 1px solid #ccc; border-radius: 5px; margin: 5px 0;"},', + '\t\tui.h2("This is " + label),', + '\t\tui.input_action_button(id="button", label=label),', + '\t\tui.output_text_verbatim(id="out"),', + "\t)", + "", + "", + "@module.server", + "def $1_server(input, output, session, starting_value = 0):", + "\tcount = reactive.value(starting_value)", + "", + "\t@reactive.effect", + "\t@reactive.event(input.button)", + "\tdef _():", + "\t\tcount.set(count() + 1)", + "", + "\t@render.text", + "\tdef out() -> str:", + '\t\treturn f"Click count is {count()}"', + "", + ], + }, + ], +}; + +export function getShinySnippets(filetype: string): Completion[] | undefined { + if (!(filetype in shinySnippets)) return; + + return shinySnippets[filetype].map((snippet: ShinySnippet): Completion => { + return snippetCompletion(snippet.code.join("\n"), { + label: snippet.label, + }); + }); +} diff --git a/src/Components/codeMirror/extensions.ts b/src/Components/codeMirror/extensions.ts index 0bb46999..f5140e7d 100644 --- a/src/Components/codeMirror/extensions.ts +++ b/src/Components/codeMirror/extensions.ts @@ -14,7 +14,7 @@ import { import { css } from "@codemirror/lang-css"; import { html } from "@codemirror/lang-html"; import { javascript } from "@codemirror/lang-javascript"; -import { python } from "@codemirror/lang-python"; +import { python, pythonLanguage } from "@codemirror/lang-python"; import { StreamLanguage, bracketMatching, @@ -39,6 +39,7 @@ import { lineNumbers as lineNumbersExtension, rectangularSelection, } from "@codemirror/view"; +import { getShinySnippets } from "./customSnippets"; export function getExtensions({ indentSpaces = 4, @@ -110,19 +111,44 @@ export function getMinimalExtensions(): Extension { return [history()]; } +const rLanguage = StreamLanguage.define(r); const LANG_EXTENSIONS: Record Extension> = { python: python, javascript: javascript, html: html, css: css, - r: () => StreamLanguage.define(r), + r: () => rLanguage, }; -export function getLanguageExtension(filetype: string | null): Extension { +export function getLanguageExtension(filetype: string | null): Extension[] { if (filetype === null) return []; if (!(filetype in LANG_EXTENSIONS)) return []; - return LANG_EXTENSIONS[filetype](); + const langExtension = LANG_EXTENSIONS[filetype]; + const snippets = getShinySnippets(filetype); + if (!snippets) return [langExtension()]; + + console.log( + `${filetype} snippets: ${snippets.map((s) => s.label).join(", ")}`, + ); + + if (filetype === "r") { + return [ + langExtension(), + rLanguage.data.of({ + autocomplete: snippets, + }), + ]; + } else if (filetype === "python") { + return [ + langExtension(), + pythonLanguage.data.of({ + autocomplete: snippets, + }), + ]; + } + + return [langExtension()]; } function lintGutterWithCustomTheme() { diff --git a/src/Components/codeMirror/language-server/lsp-extension.ts b/src/Components/codeMirror/language-server/lsp-extension.ts index 99ae9cd3..ad58833b 100644 --- a/src/Components/codeMirror/language-server/lsp-extension.ts +++ b/src/Components/codeMirror/language-server/lsp-extension.ts @@ -60,7 +60,6 @@ export function languageServerExtensions( } } }), - autocompletion(lspClient, filename), signatureHelp(lspClient, filename, true), hover(lspClient, filename), ];