diff --git a/.gitignore b/.gitignore index aff848d0..91e3bd58 100644 --- a/.gitignore +++ b/.gitignore @@ -263,3 +263,5 @@ renv.lock # Claude .claude/settings.local.json +node_modules/ +package-lock.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..2f6effef --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "querychat-dependencies", + "version": "1.0.0", + "private": true, + "description": "JavaScript dependencies for querychat code editor", + "scripts": { + "install-deps": "npm install --no-save prism-code-editor cpy-cli", + "copy-deps": "npm run copy-core && npm run copy-languages && npm run copy-extensions && npm run copy-themes", + "copy-core": "cpy 'node_modules/prism-code-editor/dist/*.js' pkg-r/inst/js/prism-code-editor/ && cpy 'node_modules/prism-code-editor/dist/*.css' pkg-r/inst/js/prism-code-editor/ && cpy 'node_modules/prism-code-editor/dist/utils/*.js' pkg-r/inst/js/prism-code-editor/utils/ && cpy 'node_modules/prism-code-editor/dist/prism/**/*.js' pkg-r/inst/js/prism-code-editor/prism/", + "copy-languages": "cpy 'node_modules/prism-code-editor/dist/languages/*.js' pkg-r/inst/js/prism-code-editor/languages/", + "copy-extensions": "cpy 'node_modules/prism-code-editor/dist/extensions/copyButton/*' pkg-r/inst/js/prism-code-editor/extensions/copyButton/ && cpy 'node_modules/prism-code-editor/dist/extensions/*' pkg-r/inst/js/prism-code-editor/extensions/", + "copy-themes": "cpy 'node_modules/prism-code-editor/dist/themes/*.css' pkg-r/inst/js/prism-code-editor/themes/", + "update-deps": "npm run install-deps && npm run copy-deps" + }, + "devDependencies": { + "prism-code-editor": "^3.0.0", + "cpy-cli": "^6.0.0" + } +} diff --git a/pkg-r/NAMESPACE b/pkg-r/NAMESPACE index 6db28d67..aad180cc 100644 --- a/pkg-r/NAMESPACE +++ b/pkg-r/NAMESPACE @@ -11,10 +11,12 @@ S3method(querychat_data_source,DBIConnection) S3method(querychat_data_source,data.frame) S3method(test_query,dbi_source) export(cleanup_source) +export(code_editor_themes) export(create_system_prompt) export(execute_query) export(get_db_type) export(get_schema) +export(input_code_editor) export(querychat_app) export(querychat_data_source) export(querychat_greeting) @@ -22,5 +24,7 @@ export(querychat_init) export(querychat_server) export(querychat_sidebar) export(querychat_ui) +export(querychat_ui_code) export(test_query) +export(update_code_editor) importFrom(lifecycle,deprecated) diff --git a/pkg-r/R/input_code_editor.R b/pkg-r/R/input_code_editor.R new file mode 100644 index 00000000..956b0a0d --- /dev/null +++ b/pkg-r/R/input_code_editor.R @@ -0,0 +1,319 @@ +#' Code editor input for Shiny +#' +#' Creates an interactive code editor input that can be used in Shiny +#' applications. The editor provides syntax highlighting, line numbers, and +#' other code editing features powered by Prism Code Editor. +#' +#' @section Keyboard shortcuts: +#' The editor supports the following keyboard shortcuts: +#' - `Ctrl/Cmd+Enter`: Submit the current code to R +#' - `Ctrl/Cmd+Z`: Undo +#' - `Ctrl/Cmd+Shift+Z`: Redo +#' - `Tab`: Indent selection +#' - `Shift+Tab`: Dedent selection +#' +#' @section Update triggers: +#' The editor value is sent to R when: +#' - The editor loses focus (blur event) +#' - The user presses `Ctrl/Cmd+Enter` +#' +#' @section Theme switching: +#' The editor automatically switches between `theme_light` and `theme_dark` +#' when used with [bslib::input_dark_mode()]. +#' +#' @examples +#' \dontrun{ +#' library(shiny) +#' library(querychat) +#' +#' ui <- fluidPage( +#' input_code_editor( +#' "sql_query", +#' value = "SELECT * FROM table", +#' language = "sql" +#' ) +#' ) +#' +#' server <- function(input, output, session) { +#' observe({ +#' print(input$sql_query) +#' }) +#' } +#' +#' shinyApp(ui, server) +#' } +#' +#' @param id Input ID. Access the current value with `input$`. +#' @param value Initial code content. Default is an empty string. +#' @param label Display label for the input. Default is `NULL` for no label. +#' @param ... Must be empty. Prevents accidentally passing unnamed arguments. +#' @param language Programming language for syntax highlighting. Must be one of: +#' `"sql"`, `"python"`, `"r"`, `"javascript"`, `"html"`, `"css"`, `"json"`, +#' `"bash"`, `"markdown"`, `"yaml"`, `"xml"`. Default is `"sql"`. +#' @param height CSS height of the editor. Default is `"300px"`. +#' @param width CSS width of the editor. Default is `"100%"`. +#' @param theme_light Theme to use in light mode. See [code_editor_themes()] for +#' available themes. Default is `"github-light"`. +#' @param theme_dark Theme to use in dark mode. See [code_editor_themes()] for +#' available themes. Default is `"github-dark"`. +#' @param read_only Whether the editor should be read-only. Default is `FALSE`. +#' @param line_numbers Whether to show line numbers. Default is `TRUE`. +#' @param word_wrap Whether to wrap long lines. Default is `FALSE`. +#' @param tab_size Number of spaces per tab. Default is `2`. +#' @param indentation Type of indentation: `"space"` or `"tab"`. Default is +#' `"space"`. +#' @inheritParams bslib::card +#' @param session Shiny session object, for expert use only. +#' +#' @return An HTML tag object that can be included in a Shiny UI. +#' +#' @describeIn input_code_editor Create a light-weight code editor input +#' @export +input_code_editor <- function( + id, + value = "", + label = NULL, + ..., + language = "sql", + height = "auto", + width = "100%", + theme_light = "github-light", + theme_dark = "github-dark", + read_only = FALSE, + line_numbers = TRUE, + word_wrap = FALSE, + tab_size = 2, + indentation = c("space", "tab"), + fill = TRUE +) { + # Ensure no extra arguments + rlang::check_dots_empty() + stopifnot(rlang::is_bool(fill)) + + check_value_line_count(value) + + # Validate inputs + language <- arg_match_language(language) + theme_light <- arg_match_theme(theme_light, "theme_light") + theme_dark <- arg_match_theme(theme_dark, "theme_dark") + + indentation <- rlang::arg_match(indentation) + insert_spaces <- (indentation == "space") + + # Create inner container that will hold the actual editor + editor_inner <- htmltools::tags$div( + class = "code-editor", + bslib::as_fill_item(), + style = htmltools::css( + display = "grid" + ) + ) + + label_tag <- asNamespace("shiny")[["shinyInputLabel"]](id, label) + + htmltools::tags$div( + id = id, + class = "shiny-input-code-editor", + style = htmltools::css( + height = height, + width = width + ), + if (fill) bslib::as_fill_item(), + bslib::as_fillable_container(), + `data-language` = language, + `data-initial-code` = value, + `data-theme-light` = theme_light, + `data-theme-dark` = theme_dark, + `data-read-only` = tolower(as.character(read_only)), + `data-line-numbers` = tolower(as.character(line_numbers)), + `data-word-wrap` = tolower(as.character(word_wrap)), + `data-tab-size` = as.character(tab_size), + `data-insert-spaces` = tolower(as.character(insert_spaces)), + label_tag, + editor_inner, + html_dependency_code_editor(), + ) +} + +#' @describeIn input_code_editor Update the code editor input value and settings +#' @export +update_code_editor <- function( + id, + value = NULL, + ..., + language = NULL, + theme_light = NULL, + theme_dark = NULL, + read_only = NULL, + line_numbers = NULL, + word_wrap = NULL, + tab_size = NULL, + indentation = NULL, + session = shiny::getDefaultReactiveDomain() +) { + # Ensure no extra arguments + rlang::check_dots_empty() + + # Validate inputs if provided + if (!is.null(language)) { + language <- arg_match_language(language, "language") + } + if (!is.null(theme_light)) { + theme_light <- arg_match_theme(theme_light, "theme_light") + } + if (!is.null(theme_dark)) { + theme_dark <- arg_match_theme(theme_dark, "theme_dark") + } + + # Build message with only non-NULL values + message <- list() + + if (!is.null(value)) { + check_value_line_count(value) + message$code <- value + } + if (!is.null(language)) { + message$language <- language + } + if (!is.null(theme_light)) { + message$theme_light <- theme_light + } + if (!is.null(theme_dark)) { + message$theme_dark <- theme_dark + } + if (!is.null(read_only)) { + message$read_only <- read_only + } + if (!is.null(line_numbers)) { + message$line_numbers <- line_numbers + } + if (!is.null(word_wrap)) { + message$word_wrap <- word_wrap + } + if (!is.null(tab_size)) { + message$tab_size <- tab_size + } + if (!is.null(indentation)) { + indentation <- rlang::arg_match(indentation, c("space", "tab")) + message$indentation <- indentation + } + + # Send message to JavaScript binding + session$sendInputMessage(id, message) + + invisible(NULL) +} + +#' HTML dependency for the code editor component +#' +#' This function returns an htmlDependency object that bundles all necessary +#' JavaScript and CSS files for the Prism Code Editor component. +#' +#' @return An htmlDependency object +#' @keywords internal +html_dependency_code_editor <- function() { + dep_code_editor <- htmltools::htmlDependency( + name = "shiny-input-code-editor", + version = utils::packageVersion("querychat"), + package = "querychat", + src = "js", + script = "code-editor-binding.js", + stylesheet = "code-editor.css", + all_files = FALSE + ) + + htmltools::tagList( + html_dependency_prism_code_editor(), + dep_code_editor + ) +} + +html_dependency_prism_code_editor <- function() { + htmltools::htmlDependency( + name = "prism-code-editor", + version = "3.0.0", + package = "querychat", + src = "js/prism-code-editor", + script = list(src = "index.js", type = "module"), + stylesheet = c("layout.css", "copy.css"), + all_files = TRUE + ) +} + +#' @describeIn input_code_editor List available code editor syntax highlighting +#' themes +#' @export +code_editor_themes <- function() { + themes_dir <- system.file( + "js/prism-code-editor/themes", + package = "querychat" + ) + + if (!dir.exists(themes_dir)) { + return(character(0)) + } + + theme_files <- list.files(themes_dir, pattern = "\\.css$") + sub("\\.css$", "", theme_files) +} + +arg_match_theme <- function(theme, arg_name = "theme") { + if (is.null(theme)) { + return(invisible(NULL)) + } + + available_themes <- code_editor_themes() + + rlang::arg_match( + theme, + values = available_themes, + error_arg = arg_name, + error_call = rlang::caller_env() + ) +} + +arg_match_language <- function(language, arg_name = "language") { + if (is.null(language)) { + return(invisible(NULL)) + } + + # List of initially supported languages - these match the grammar files + # we've bundled from prism-code-editor + supported_languages <- c( + "sql", + "python", + "r", + "javascript", + "html", + "css", + "json", + "bash", + "markdown", + "yaml", + "xml" + ) + + rlang::arg_match( + language, + values = supported_languages, + error_arg = arg_name, + error_call = rlang::caller_env() + ) +} + +check_value_line_count <- function(value) { + if (is.null(value) || !is.character(value) || length(value) == 0) { + return(invisible(NULL)) + } + + line_count <- length(strsplit(value, "\n", fixed = TRUE)[[1]]) + + if (line_count >= 750) { + rlang::warn(c( + sprintf("Code editor value contains %d lines.", line_count), + "i" = "The editor may experience performance issues with 750 or more lines." + )) + } + + invisible(NULL) +} diff --git a/pkg-r/R/querychat.R b/pkg-r/R/querychat.R index 1bb9d5e8..657d0307 100644 --- a/pkg-r/R/querychat.R +++ b/pkg-r/R/querychat.R @@ -142,20 +142,7 @@ querychat_init <- function( #' #' @return A UI object that can be embedded in a Shiny app. #' -#' @name querychat_ui -#' @export -querychat_sidebar <- function(id, width = 400, height = "100%", ...) { - bslib::sidebar( - width = width, - height = height, - class = "querychat-sidebar", - ..., - # purposely NOT using ns() for `id`, we're just a passthrough - querychat_ui(id) - ) -} - -#' @rdname querychat_ui +#' @describeIn querychat_ui Create a chat interface to be used in a Shiny app. #' @export querychat_ui <- function(id) { ns <- shiny::NS(id) @@ -177,6 +164,34 @@ querychat_ui <- function(id) { ) } +#' @describeIn querychat_ui Create a chat interface to be used as a sidebar in +#' pages like [bslib::page_sidebar()], [bslib::page_navbar()], or +#' [bslib::layout_sidebar()]. +#' @export +querychat_sidebar <- function(id, width = 400, height = "100%", ...) { + bslib::sidebar( + width = width, + height = height, + class = "querychat-sidebar", + ..., + # purposely NOT using ns() for `id`, we're just a passthrough + querychat_ui(id) + ) +} + + +#' @describeIn querychat_ui A code editor UI component that displays the current +#' SQL query and allows the user to edit it. +#' @export +querychat_ui_code <- function(id, ...) { + ns <- shiny::NS(id) + input_code_editor( + ns("code_editor"), + language = "sql", + ... + ) +} + #' Initialize the querychat server #' #' @param id The ID of the module instance. Must match the ID passed to @@ -266,6 +281,15 @@ querychat_server <- function(id, querychat_config) { current_title(input$chat_update$title) }) + shiny::observeEvent(current_query(), { + update_code_editor("code_editor", value = current_query()) + }) + + shiny::observeEvent(input$code_editor, { + shiny::req(input$code_editor) + current_query(input$code_editor) + }) + list( chat = chat, sql = shiny::reactive(current_query()), diff --git a/pkg-r/R/querychat_app.R b/pkg-r/R/querychat_app.R index 06d55759..73260f53 100644 --- a/pkg-r/R/querychat_app.R +++ b/pkg-r/R/querychat_app.R @@ -63,7 +63,7 @@ querychat_app <- function(config, ..., bookmark_store = "url") { ) ) ), - shiny::uiOutput("sql_output") + querychat_ui_code("chat") ), bslib::card( bslib::card_header(bsicons::bs_icon("table"), "Data"), @@ -110,22 +110,22 @@ querychat_app <- function(config, ..., bookmark_store = "url") { DT::datatable(qc$df()) }) - output$sql_output <- shiny::renderUI({ - sql <- if (shiny::isTruthy(qc$sql())) { - qc$sql() - } else { - paste("SELECT * FROM", config$data_source$table_name) - } + # output$sql_output <- shiny::renderUI({ + # sql <- if (shiny::isTruthy(qc$sql())) { + # qc$sql() + # } else { + # paste("SELECT * FROM", config$data_source$table_name) + # } - sql_code <- paste(c("```sql", sql, "```"), collapse = "\n") + # sql_code <- paste(c("```sql", sql, "```"), collapse = "\n") - shinychat::output_markdown_stream( - "sql_code", - content = sql_code, - auto_scroll = FALSE, - width = "100%" - ) - }) + # shinychat::output_markdown_stream( + # "sql_code", + # content = sql_code, + # auto_scroll = FALSE, + # width = "100%" + # ) + # }) shiny::observeEvent(input$close_btn, label = "on_close_btn", { shiny::stopApp() diff --git a/pkg-r/R/querychat_tools.R b/pkg-r/R/querychat_tools.R index 561fa3d9..02baaf25 100644 --- a/pkg-r/R/querychat_tools.R +++ b/pkg-r/R/querychat_tools.R @@ -93,7 +93,7 @@ tool_query <- function(data_source) { description = interpolate_package("tool-query.md", db_type = db_type), arguments = list( query = ellmer::type_string( - interpolate( + ellmer::interpolate( "A valid {{db_type}} SQL SELECT statement. Must follow the database schema provided in the system prompt. Use clear column aliases (e.g., 'AVG(price) AS avg_price') and include SQL comments for complex logic. Subqueries and CTEs are encouraged for readability.", db_type = db_type ) diff --git a/pkg-r/inst/examples-shiny/code-editor/README.md b/pkg-r/inst/examples-shiny/code-editor/README.md new file mode 100644 index 00000000..8c9a021d --- /dev/null +++ b/pkg-r/inst/examples-shiny/code-editor/README.md @@ -0,0 +1,53 @@ +# Code Editor Example + +This Shiny app demonstrates all features of the `input_code_editor()` component. + +## Features Demonstrated + +- **Multiple language support**: Switch between SQL, Python, R, JavaScript, HTML, CSS, and JSON +- **Theme switching**: Choose from 13 available themes for light and dark modes +- **Automatic theme switching**: Editor theme changes automatically with Bootstrap theme +- **Configurable options**: Line numbers, word wrap, read-only mode, tab size, indentation +- **Live updates**: All editor options can be updated from the server +- **Sample code**: Load example code for each supported language +- **Keyboard shortcuts**: Ctrl/Cmd+Enter to submit, standard editing shortcuts +- **Copy button**: Built-in copy-to-clipboard functionality +- **Reactive updates**: Code changes trigger reactive updates (on blur or Ctrl/Cmd+Enter) + +## Running the Example + +From R: + +```r +library(shiny) +runApp(system.file("examples-shiny/code-editor", package = "querychat")) +``` + +Or directly: + +```r +shiny::runApp("pkg-r/inst/examples-shiny/code-editor") +``` + +## What to Try + +1. **Language switching**: Select different languages from the dropdown and click "Load Sample Code" +2. **Theme switching**: Try different theme combinations for light and dark modes +3. **Bootstrap theme toggle**: Use the dark mode switch to see automatic theme switching +4. **Editor options**: Toggle line numbers, word wrap, read-only mode +5. **Tab settings**: Adjust tab size and switch between spaces and tabs +6. **Keyboard shortcuts**: + - Press Ctrl/Cmd+Enter to submit code (watch the output update) + - Try Ctrl/Cmd+Z for undo, Tab for indent, Shift+Tab for dedent +7. **Copy button**: Hover over the top-right corner to see the copy button + +## Code Structure + +The app demonstrates: + +- Creating an editor with `input_code_editor()` +- Updating editor options with `update_code_editor()` +- Accessing editor content with `input$code` +- Getting available themes with `code_editor_themes()` +- Using the editor in a `bslib::page_sidebar()` layout +- Integration with Bootstrap 5 theme switching diff --git a/pkg-r/inst/examples-shiny/code-editor/app.R b/pkg-r/inst/examples-shiny/code-editor/app.R new file mode 100644 index 00000000..cb16e67a --- /dev/null +++ b/pkg-r/inst/examples-shiny/code-editor/app.R @@ -0,0 +1,229 @@ +library(shiny) +library(bslib) +library(querychat) + +ui <- page_sidebar( + title = "Code Editor Demo", + theme = bs_theme(version = 5), + class = "bslib-page-dashboard", + + sidebar = sidebar( + width = 300, + title = "Editor Controls", + gap = "0.5rem", + + selectInput( + "language", + "Language:", + choices = c( + "sql", + "python", + "r", + "javascript", + "html", + "css", + "json", + "markdown", + "yaml" + ), + selected = "sql" + ), + actionButton( + "load_sample", + "Load Sample Code", + class = "btn-secondary btn-sm w-100 mb-2" + ), + actionButton( + "clear_code", + "Clear Editor", + class = "btn-warning btn-sm w-100 mb-2" + ), + + selectInput( + "theme_light", + "Light Theme:", + choices = code_editor_themes(), + selected = "github-light" + ), + + selectInput( + "theme_dark", + "Dark Theme:", + choices = code_editor_themes(), + selected = "github-dark" + ), + + p( + input_dark_mode( + id = "dark_mode", + mode = "light", + style = css("--vertical-correction" = "5px") + ), + "Toggle Theme", + class = "text-end" + ), + + checkboxInput("read_only", "Read Only", value = FALSE), + checkboxInput("line_numbers", "Line Numbers", value = TRUE), + checkboxInput("word_wrap", "Word Wrap", value = FALSE), + + sliderInput("tab_size", "Tab Size:", min = 2, max = 8, value = 2, step = 1), + + radioButtons( + "indentation", + "Indentation:", + choices = c("Spaces" = "space", "Tabs" = "tab"), + selected = "space", + inline = TRUE + ) + ), + + layout_columns( + card( + card_header("Code Editor"), + card_body( + p( + "This editor supports syntax highlighting, line numbers, word wrap, and more. ", + "Try pressing ", + tags$kbd("Ctrl/Cmd+Enter"), + " to submit the code." + ), + input_code_editor( + "code", + value = "SELECT * FROM table\nWHERE column = 'value'\nORDER BY id DESC\nLIMIT 10;", + label = "Code Editor", + language = "sql", + # height = "400px", + fill = TRUE + ) + ) + ), + layout_columns( + col_widths = 12, + navset_card_underline( + title = "Editor Info", + nav_panel( + "Value", + verbatimTextOutput("code_output"), + ), + nav_panel( + "Settings", + verbatimTextOutput("editor_info") + ) + ), + + card( + card_header("Features & Keyboard Shortcuts"), + card_body( + tags$ul( + tags$li( + tags$kbd("Ctrl/Cmd+Enter"), + " - Submit code to R (triggers reactive update)" + ), + tags$li(tags$kbd("Ctrl/Cmd+Z"), " - Undo"), + tags$li(tags$kbd("Ctrl/Cmd+Shift+Z"), " - Redo"), + tags$li(tags$kbd("Tab"), " - Indent selection"), + tags$li(tags$kbd("Shift+Tab"), " - Dedent selection"), + tags$li("Copy button in top-right corner"), + tags$li("Automatic theme switching based on Bootstrap theme"), + tags$li("Update on blur (when editor loses focus)") + ) + ) + ) + ) + ) +) + +server <- function(input, output, session) { + # Sample code for different languages + sample_code <- list( + sql = "SELECT \n users.id,\n users.name,\n COUNT(orders.id) as order_count\nFROM users\nLEFT JOIN orders ON users.id = orders.user_id\nGROUP BY users.id, users.name\nHAVING order_count > 5\nORDER BY order_count DESC;", + python = "def fibonacci(n):\n \"\"\"Generate Fibonacci sequence up to n terms\"\"\"\n fib_sequence = [0, 1]\n for i in range(2, n):\n next_num = fib_sequence[i-1] + fib_sequence[i-2]\n fib_sequence.append(next_num)\n return fib_sequence\n\n# Example usage\nresult = fibonacci(10)\nprint(f\"First 10 Fibonacci numbers: {result}\")", + r = "# Load libraries\nlibrary(dplyr)\nlibrary(ggplot2)\n\n# Analyze mtcars dataset\nmtcars %>%\n group_by(cyl) %>%\n summarise(\n avg_mpg = mean(mpg),\n avg_hp = mean(hp),\n count = n()\n ) %>%\n ggplot(aes(x = factor(cyl), y = avg_mpg)) +\n geom_col(fill = \"steelblue\") +\n labs(title = \"Average MPG by Cylinders\",\n x = \"Cylinders\",\n y = \"Average MPG\")", + javascript = "// Async function to fetch data\nasync function fetchUserData(userId) {\n try {\n const response = await fetch(`/api/users/${userId}`);\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n const data = await response.json();\n return data;\n } catch (error) {\n console.error('Failed to fetch user data:', error);\n return null;\n }\n}\n\n// Usage\nfetchUserData(123).then(user => {\n console.log('User data:', user);\n});", + html = "\n\n\n \n \n My Web Page\n \n\n\n
\n

Welcome to My Website

\n \n
\n
\n

This is the main content area.

\n
\n\n", + css = "/* Modern CSS with variables */\n:root {\n --primary-color: #007bff;\n --secondary-color: #6c757d;\n --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;\n}\n\nbody {\n font-family: var(--font-family);\n line-height: 1.6;\n color: #333;\n max-width: 1200px;\n margin: 0 auto;\n padding: 20px;\n}\n\n.card {\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n padding: 20px;\n transition: transform 0.2s;\n}\n\n.card:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 8px rgba(0,0,0,0.15);\n}", + json = "{\n \"name\": \"my-app\",\n \"version\": \"1.0.0\",\n \"description\": \"A sample application\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"start\": \"node index.js\",\n \"test\": \"jest\",\n \"build\": \"webpack --mode production\"\n },\n \"dependencies\": {\n \"express\": \"^4.18.0\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\"\n },\n \"devDependencies\": {\n \"jest\": \"^29.0.0\",\n \"webpack\": \"^5.75.0\"\n },\n \"keywords\": [\"example\", \"demo\", \"sample\"],\n \"author\": \"Your Name\",\n \"license\": \"MIT\"\n}", + markdown = "# Project Documentation\n\n## Overview\nThis is a sample project that demonstrates various features and capabilities.\n\n## Installation\n1. Clone the repository\n2. Install dependencies:\n ```bash\n npm install\n ```\n\n## Features\n- **Modern Architecture**: Built with the latest technologies\n- **Responsive Design**: Works on all devices\n- **Performance Optimized**: Fast loading and execution\n\n## Usage Examples\n### Basic Implementation\n```javascript\nconst app = new Application();\napp.initialize();\n```\n\n## Contributing\nWe welcome contributions! Please follow these steps:\n1. Fork the repository\n2. Create your feature branch\n3. Submit a pull request\n\n## License\nThis project is licensed under the MIT License - see the LICENSE file for details.\n\n## Contact\nEmail: example@domain.com\nTwitter: @example", + yaml = "# Application Configuration\napp:\n name: sample-application\n version: 1.0.0\n environment: production\n\nserver:\n host: localhost\n port: 3000\n ssl:\n enabled: true\n cert: /path/to/cert.pem\n key: /path/to/key.pem\n\ndatabase:\n primary:\n host: db.example.com\n port: 5432\n name: maindb\n user: admin\n max_connections: 100\n replica:\n enabled: true\n hosts:\n - replica1.example.com\n - replica2.example.com\n\nlogging:\n level: info\n format: json\n outputs:\n - type: file\n path: /var/log/app.log\n - type: stdout\n\nmonitoring:\n enabled: true\n interval: 60\n endpoints:\n - /health\n - /metrics" + ) + + # Update code editor settings + observeEvent(input$language, { + update_code_editor("code", language = input$language) + }) + observeEvent(input$theme_light, { + update_code_editor("code", theme_light = input$theme_light) + }) + observeEvent(input$theme_dark, { + update_code_editor("code", theme_dark = input$theme_dark) + }) + observeEvent(input$read_only, { + update_code_editor("code", read_only = input$read_only) + }) + observeEvent(input$line_numbers, { + update_code_editor("code", line_numbers = input$line_numbers) + }) + observeEvent(input$word_wrap, { + update_code_editor("code", word_wrap = input$word_wrap) + }) + observeEvent(input$tab_size, { + update_code_editor("code", tab_size = input$tab_size) + }) + observeEvent(input$indentation, { + update_code_editor("code", indentation = input$indentation) + }) + + # Load sample code for selected language + observeEvent(input$load_sample, { + lang <- input$language + sample <- sample_code[[lang]] + if (!is.null(sample)) { + update_code_editor( + "code", + value = sample, + language = lang + ) + } + }) + + # Clear editor + observeEvent(input$clear_code, { + update_code_editor("code", value = "") + }) + + # Display current code + output$code_output <- renderText({ + code <- input$code + if (is.null(code) || code == "") { + "[Editor is empty]" + } else { + code + } + }) + + # Display editor information + output$editor_info <- renderText({ + code <- input$code + if (is.null(code)) { + code <- "" + } + + lines <- length(strsplit(code, "\n")[[1]]) + chars <- nchar(code) + + paste( + sprintf("Language: %s", input$language), + sprintf("Lines: %d", lines), + sprintf("Characters: %d", chars), + sprintf("Read Only: %s", input$read_only), + sprintf("Line Numbers: %s", input$line_numbers), + sprintf("Word Wrap: %s", input$word_wrap), + sprintf("Tab Size: %d", input$tab_size), + sprintf("Indentation: %s", input$indentation), + sep = "\n" + ) + }) +} + +shinyApp(ui = ui, server = server) diff --git a/pkg-r/inst/js/code-editor-binding.js b/pkg-r/inst/js/code-editor-binding.js new file mode 100644 index 00000000..e7fe3015 --- /dev/null +++ b/pkg-r/inst/js/code-editor-binding.js @@ -0,0 +1,378 @@ +/** + * Shiny Input Binding for Prism Code Editor + * + * This binding creates a bidirectional connection between R and a Prism Code Editor instance, + * allowing code content to be sent from the editor to R and updated from R to the editor. + */ + +// Track which languages have been loaded to avoid duplicate imports +const loadedLanguages = new Set(); + +// Track which editor instances have been initialized +const initializedEditors = new WeakSet(); + +// Memoized base path for prism-code-editor files +let _prismCodeEditorBasePath = null; + +/** + * Discovers and memoizes the base path for prism-code-editor files + * by finding the script element that loaded index.js + * @returns {string} The base path to prism-code-editor files + */ +function getPrismCodeEditorBasePath() { + if (_prismCodeEditorBasePath !== null) { + return _prismCodeEditorBasePath; + } + + // Find the script element that loaded prism-code-editor's index.js + const scriptElement = document.querySelector('script[src*="prism-code-editor"][src$="index.js"]'); + + if (!scriptElement) { + console.error('Could not find prism-code-editor script element'); + _prismCodeEditorBasePath = ''; + return _prismCodeEditorBasePath; + } + + // Extract the base path from the src attribute + const src = scriptElement.getAttribute('src'); + + // Convert relative URL to absolute URL + const absoluteSrc = new URL(src, document.baseURI).href; + + // Remove '/index.js' from the end to get the base path + _prismCodeEditorBasePath = absoluteSrc.replace(/\/index\.js$/, ''); + + return _prismCodeEditorBasePath; +} + + +/** + * Dynamically loads a language grammar module if not already loaded + * + * Prism grammars from prism-code-editor register themselves via side effects + * when imported. They should be imported from prism/languages/ not the regular + * languages/ directory. + * + * @param {string} language - The language identifier (e.g., 'sql', 'python', 'r') + * @param {string} prismCodeEditorBasePath - The base path to the prism-code-editor files + * @returns {Promise} + */ +async function loadLanguage(language, prismCodeEditorBasePath) { + if (loadedLanguages.has(language)) { + return; + } + + // HTML is included in the clike grammar which is loaded by default + if (language === 'html') { + language = 'markup'; + } + + try { + // Import from prism/languages/ not regular languages/ + // The prism grammars register themselves through side effects + await import(`${prismCodeEditorBasePath}/prism/languages/${language}.js`); + loadedLanguages.add(language); + } catch (error) { + console.error(`Failed to load language '${language}':`, error); + throw error; + } +} + +/** + * Loads or switches the theme CSS for an editor instance + * @param {string} inputId - The editor's input ID + * @param {string} themeName - The theme name (e.g., 'github-light', 'vs-code-dark') + * @param {string} prismCodeEditorBasePath - The base path to prism-code-editor files + */ +function loadTheme(inputId, themeName, prismCodeEditorBasePath) { + const linkId = `code-editor-theme-${inputId}`; + const existingLink = document.getElementById(linkId); + + const newLink = document.createElement('link'); + newLink.id = linkId; + newLink.rel = 'stylesheet'; + newLink.href = `${prismCodeEditorBasePath}/themes/${themeName}.css`; + + // Add new link to head + document.head.appendChild(newLink); + + // Remove old link after new one loads to prevent FOUC + if (existingLink) { + newLink.addEventListener('load', () => { + existingLink.remove(); + }); + } +} + +/** + * Sets up theme watching for Bootstrap 5 data-bs-theme attribute + * @param {HTMLElement} el - The editor container element + * @param {string} themeLight - Light theme name + * @param {string} themeDark - Dark theme name + * @param {string} prismCodeEditorBasePath - Base path to prism-code-editor files + */ +function setupThemeWatcher(el, themeLight, themeDark, prismCodeEditorBasePath) { + const inputId = el.id; + + // Function to load appropriate theme based on current data-bs-theme + const updateTheme = () => { + const htmlEl = document.documentElement; + const theme = htmlEl.getAttribute('data-bs-theme'); + const themeName = (theme === 'dark') + ? el.dataset.themeDark || themeDark + : el.dataset.themeLight || themeLight; + loadTheme(inputId, themeName, prismCodeEditorBasePath); + }; + + // Set initial theme + updateTheme(); + + // Watch for theme changes + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-bs-theme') { + updateTheme(); + } + } + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-bs-theme'] + }); + + // Store observer on element for cleanup + el._themeObserver = observer; +} + +/** + * Initializes a Prism Code Editor instance for an element + * @param {HTMLElement} el - The outer editor container element (.shiny-input-code-editor) + * @returns {Promise} The created editor instance + */ +async function initializeEditor(el) { + if (initializedEditors.has(el)) { + return el.prismEditor; + } + + // Find the inner container where the editor will be initialized + const editorContainer = el.querySelector('.code-editor'); + if (!editorContainer) { + console.error('Could not find .code-editor inside .shiny-input-code-editor'); + return; + } + + // Get configuration from data attributes on the outer element + const language = el.dataset.language || 'sql'; + const initialCode = el.dataset.initialCode || ''; + const themeLight = el.dataset.themeLight || 'github-light'; + const themeDark = el.dataset.themeDark || 'github-dark'; + const readOnly = el.dataset.readOnly === 'true'; + const lineNumbers = el.dataset.lineNumbers !== 'false'; // default true + const wordWrap = el.dataset.wordWrap === 'true'; + const tabSize = parseInt(el.dataset.tabSize) || 2; + const insertSpaces = el.dataset.insertSpaces !== 'false'; // default true + + // Get the base path to prism-code-editor files + const prismCodeEditorBasePath = getPrismCodeEditorBasePath(); + + // Load required language grammar + await loadLanguage(language, prismCodeEditorBasePath); + + // Dynamically import the createEditor function and extensions + const { createEditor } = await import(`${prismCodeEditorBasePath}/index.js`); + const { copyButton } = await import(`${prismCodeEditorBasePath}/extensions/copyButton/index.js`); + const { defaultCommands } = await import(`${prismCodeEditorBasePath}/extensions/commands.js`); + + // Create editor instance in the inner container + const editor = createEditor( + editorContainer, + { + language: language, + value: initialCode, + tabSize: tabSize, + insertSpaces: insertSpaces, + lineNumbers: lineNumbers, + wordWrap: wordWrap, + readOnly: readOnly + }, + copyButton(), + defaultCommands() + ); + + const oldEnterCallback = editor.keyCommandMap.Enter + editor.keyCommandMap.Enter = (e, selection, value) => { + if (e.metaKey || e.ctrlKey) { + el.dispatchEvent(new CustomEvent('codeEditorUpdate')); + // Visual feedback: brief border flash on inner container + editorContainer.classList.add('code-editor-submit-flash'); + setTimeout(() => { + editorContainer.classList.remove('code-editor-submit-flash'); + }, 400); + return true; + } + return oldEnterCallback?.(e, selection, value); + } + + // Store editor instance on outer element + el.prismEditor = editor; + initializedEditors.add(el); + + // Set up theme management + setupThemeWatcher(el, themeLight, themeDark, prismCodeEditorBasePath); + + // Set up event listeners for value changes + const textarea = el.querySelector('textarea'); + if (textarea) { + // Blur event + textarea.addEventListener('blur', () => { + el.dispatchEvent(new CustomEvent('codeEditorUpdate')); + }); + } + + return editor; +} + +// Define the Shiny input binding +const codeEditorBinding = new Shiny.InputBinding(); + +$.extend(codeEditorBinding, { + // Find all code editor elements in the scope + find: function(scope) { + return $(scope).find('.shiny-input-code-editor'); + }, + + // Get the current value of the editor + getValue: function(el) { + if (el.prismEditor) { + return el.prismEditor.value; + } + // Return initial value if editor not yet initialized + return el.dataset.initialCode || ''; + }, + + // Set the value without triggering reactivity (for bookmark restoration) + setValue: function(el, value) { + if (el.prismEditor) { + el.prismEditor.setOptions({ value: value }); + } else { + // Update data attribute for when editor initializes + el.dataset.initialCode = value; + } + }, + + // Subscribe to value changes + subscribe: function(el, callback) { + // Initialize editor lazily on first subscription + initializeEditor(el).catch(error => { + console.error('Failed to initialize code editor:', error); + }); + + this._updateCallback = () => callback(true); // true enables rate policy + + // Listen for custom update events + el.addEventListener('codeEditorUpdate', this._updateCallback); + }, + + // Unsubscribe from value changes + unsubscribe: function(el) { + el.removeEventListener('codeEditorUpdate', this._updateCallback); + + // Clean up theme observer + if (el._themeObserver) { + el._themeObserver.disconnect(); + delete el._themeObserver; + } + }, + + // Handle messages from R (update_code_editor calls) + receiveMessage: function(el, data) { + const editor = el.prismEditor; + + if (!editor) { + console.warn('Cannot update code editor: editor not initialized'); + return; + } + + // Build options object for updates + const options = {}; + + if (data.code !== undefined) { + options.value = data.code; + } + + if (data.tab_size !== undefined) { + options.tabSize = data.tab_size; + } + + if (data.indentation !== undefined) { + options.insertSpaces = (data.indentation === 'space'); + } + + if (data.read_only !== undefined) { + options.readOnly = data.read_only; + } + + if (data.line_numbers !== undefined) { + options.lineNumbers = data.line_numbers; + } + + if (data.word_wrap !== undefined) { + options.wordWrap = data.word_wrap; + } + + // Apply options to editor + if (Object.keys(options).length > 0) { + editor.setOptions(options); + } + + // Handle language change (requires grammar loading and reinitialization) + if (data.language !== undefined && data.language !== el.dataset.language) { + const prismCodeEditorBasePath = getPrismCodeEditorBasePath(); + loadLanguage(data.language, prismCodeEditorBasePath).then(() => { + el.dataset.language = data.language; + editor.setOptions({ language: data.language }); + // Force retokenization + editor.update(); + }).catch(error => { + console.error(`Failed to change language to '${data.language}':`, error); + }); + } + + // Handle theme updates + if (data.theme_light !== undefined) { + el.dataset.themeLight = data.theme_light; + // Re-evaluate current theme + const htmlEl = document.documentElement; + const currentTheme = htmlEl.getAttribute('data-bs-theme'); + if (currentTheme !== 'dark') { + const prismCodeEditorBasePath = getPrismCodeEditorBasePath(); + loadTheme(el.id, data.theme_light, prismCodeEditorBasePath); + } + } + + if (data.theme_dark !== undefined) { + el.dataset.themeDark = data.theme_dark; + // Re-evaluate current theme + const htmlEl = document.documentElement; + const currentTheme = htmlEl.getAttribute('data-bs-theme'); + if (currentTheme === 'dark') { + const prismCodeEditorBasePath = getPrismCodeEditorBasePath(); + loadTheme(el.id, data.theme_dark, prismCodeEditorBasePath); + } + } + + el.dispatchEvent(new CustomEvent('codeEditorUpdate')); + }, + + // Rate policy: debounce to avoid excessive updates + getRatePolicy: function() { + return { + policy: 'throttle', + delay: 300 + }; + } +}); + +// Register the binding with Shiny +Shiny.inputBindings.register(codeEditorBinding, 'querychat.codeEditorBinding'); diff --git a/pkg-r/inst/js/code-editor.css b/pkg-r/inst/js/code-editor.css new file mode 100644 index 00000000..e8653d8f --- /dev/null +++ b/pkg-r/inst/js/code-editor.css @@ -0,0 +1,101 @@ +/** + * Code Editor Base Styling + * + * Provides Bootstrap 5 integration styling for the Prism Code Editor component. + * This CSS handles the "chrome" around the editor (borders, focus states, etc.) + * while theme CSS files handle syntax highlighting colors. + */ + +.shiny-input-code-editor { + /* Outer container just handles sizing and spacing */ + width: 100%; +} + +/* Label styling - outside the border */ +.shiny-input-code-editor > label { + display: inline-block; + margin-bottom: 0.5rem; +} + +/* Inner container holds the actual editor with border/focus styling */ +.shiny-input-code-editor .code-editor { + /* Required by Prism Code Editor */ + display: grid !important; + + /* Match Bootstrap form control styling */ + border: 1px solid var(--bs-border-color, #dee2e6); + border-radius: var(--bs-border-radius, 0.375rem); + background-color: var(--bs-body-bg, #fff); + color: var(--bs-body-color, #212529); + + /* Position and overflow for editor content */ + position: relative; + overflow: hidden; + + /* Smooth transitions */ + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +/* Focus state - matches Bootstrap input focus */ +.shiny-input-code-editor .code-editor:focus-within { + border-color: var(--bs-primary, #0d6efd); + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb, 13, 110, 253), 0.25); +} + +/* Dark mode adjustments */ +[data-bs-theme="dark"] .shiny-input-code-editor .code-editor { + border-color: var(--bs-border-color, #495057); + background-color: var(--bs-body-bg, #212529); + color: var(--bs-body-color, #dee2e6); +} + +[data-bs-theme="dark"] .shiny-input-code-editor .code-editor:focus-within { + border-color: var(--bs-primary, #0d6efd); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb, 13, 110, 253), 0.25); +} + +/* Submit flash animation (Ctrl/Cmd+Enter feedback) */ +.code-editor-submit-flash { + animation: code-editor-flash 400ms ease-out; +} + +@keyframes code-editor-flash { + 0% { + border-color: var(--bs-success, #198754); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb, 25, 135, 84), 0.25); + } + 100% { + border-color: var(--bs-primary, #0d6efd); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb, 13, 110, 253), 0.25); + } +} + +/* Ensure copy button appears above code but below modals */ +.shiny-input-code-editor .code-editor .pce-copy-button { + z-index: 10; +} + +/* Ensure proper font rendering */ +.shiny-input-code-editor .code-editor, +.shiny-input-code-editor .code-editor textarea, +.shiny-input-code-editor .code-editor pre, +.shiny-input-code-editor .code-editor code { + font-family: var(--bs-font-monospace, ui-monospace, "SFMono-Regular", "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace); + font-size: 0.875rem; +} + +/* Read-only state styling */ +.shiny-input-code-editor[data-read-only="true"] .code-editor { + background-color: var(--bs-secondary-bg, #e9ecef); + cursor: not-allowed; +} + +[data-bs-theme="dark"] .shiny-input-code-editor[data-read-only="true"] .code-editor { + background-color: var(--bs-secondary-bg, #343a40); +} + +/* Ensure textarea fills container properly */ +.shiny-input-code-editor .code-editor textarea { + min-height: 100%; +} diff --git a/pkg-r/inst/js/prism-code-editor/atom-one-dark.js b/pkg-r/inst/js/prism-code-editor/atom-one-dark.js new file mode 100644 index 00000000..7ae12c98 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/atom-one-dark.js @@ -0,0 +1,5 @@ +const atomOneDark = ".prism-code-editor{caret-color:#528bff;font-family:Fira Code,Fira Mono,Menlo,Consolas,DejaVu Sans Mono,monospace;--editor__bg: #292c33;--widget__border: #3a3f4b;--widget__bg: #21252b;--widget__color: #ccc;--widget__color-active: #fff;--widget__color-options: #b2b2b2;--widget__bg-input: #1b1d23;--widget__bg-hover: #5a5d5e4f;--widget__bg-active: #336699;--widget__focus-ring: #5299e0;--search__bg-find: #528bff3d;--widget__bg-error: #5a1d1d;--widget__error-ring: #be1100;--editor__bg-highlight: #99bbff0a;--editor__bg-selection-match: #4a566d66;--editor__line-number: #636d83;--editor__bg-scrollbar: 220, 13%, 41%;--editor__bg-fold: #c5c5c5;--bg-guide-indent: #abb2bf26;--pce-ac-icon-class: #ee9d28;--pce-ac-icon-enum: #ee9d28;--pce-ac-icon-function: #b180d7;--pce-ac-icon-interface: #75beff;--pce-ac-icon-variable: #75beff;color-scheme:dark}.prism-code-editor textarea::selection{background:#3e4451;color:#0000}.pce-matches .match{--search__bg-find: #515c6a}.active-line{--editor__line-number: #abb2bf}.guide-indents .active{--bg-guide-indent: #abb2c280}.token.comment,.token.prolog,.token.cdata{color:#5c6370}[class*=language-],.token.punctuation,.token.attr-equals,.language-css .token.property{color:#abb2bf}.token.keyword,.token.token.anchor,.token.regex-flags,.selector .punctuation,.selector .combinator,.selector .operator,.token.token.arrow{color:#c678dd}.token.class-name,.token.maybe-class-name{color:#e5c07b}.token.attr-name,.token.doctype,.selector .class,.selector .pseudo-element,.selector .pseudo-class,.token.regex .escape,.token.char-class,.token.char-set,.token.boolean,.token.constant,.token.number,.token.entity,.token.unit,.token.atrule,.token.keyword-null,.token.keyword-undefined{color:#d19a66}.token.property,.token.tag,.token.doctype-tag,.token.symbol,.token.deleted,.token.important,.token.keyword-this,.token.this .token.variable,.token.selector,.language-css .variable,.token.property-access{color:#e06c75}.token.string,.token.char,.token.inserted,.token.string-property,.token.attr-value,.token.string.url,.token.attr-value>.punctuation,.token.code-snippet.code{color:#98c379}.language-markdown .url>.variable,.language-markdown .url>.content,.token.function,.token.selector .id{color:#61afef}.token.url,.token.regex,.language-regex,.token.char-class .operator,.token.alternation,.token.quantifier,.token.hexcode,.token.keyword-get,.token.keyword-set,.token.builtin,.token.operator{color:#56b6c2}.language-css .token.important,.token.atrule .token.rule,.language-markdown .italic{color:#c678dd}.language-json .token.keyword-null,.language-markdown .bold *{color:#d19a66}.language-markdown .code.keyword,.language-json .token.operator,.language-markdown .token.url,.language-markdown .url>.operator,.language-markdown .token.url-reference>.string{color:#abb2bf}.language-css .function,.language-markdown .token.blockquote.punctuation,.language-markdown .token.hr.punctuation,.language-markdown .token.url>.token.url,.language-markdown .token.url-reference.url{color:#56b6c2}.language-markdown .strike *,.language-markdown .token.list.punctuation,.language-markdown .token.title.important>.token.punctuation{color:#e06c75}.token.bold{font-weight:700}.token.comment,.token.italic{font-style:italic}.token.namespace{opacity:.8}.token.bracket-level-0,.token.bracket-level-3,.token.bracket-level-6,.token.bracket-level-9{color:gold}.token.bracket-level-1,.token.bracket-level-4,.token.bracket-level-7,.token.bracket-level-10{color:orchid}.token.bracket-level-2,.token.bracket-level-5,.token.bracket-level-8,.token.bracket-level-11{color:#179fff}.token.interpolation-punctuation{color:#98c379}.token.bracket-error{color:#ff1212cc}.token.markup-bracket,.token.regex .punctuation{color:inherit}.active-bracket{box-shadow:inset 0 0 0 1px #888,inset 0 0 0 9in #0064001a}.active-tagname,.word-matches span{background:#575757b8}"; +export { + atomOneDark as default +}; +//# sourceMappingURL=atom-one-dark-OVRuEYUe.js.map diff --git a/pkg-r/inst/js/prism-code-editor/autocomplete-icons.css b/pkg-r/inst/js/prism-code-editor/autocomplete-icons.css new file mode 100644 index 00000000..afccaa37 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/autocomplete-icons.css @@ -0,0 +1 @@ +.pce-ac-icon{display:flex;flex-shrink:0;align-items:center;margin-right:-.25em}.pce-ac-icon:before{content:"";width:16px;height:16px;background:currentColor;-webkit-mask:no-repeat var(--mask);mask:no-repeat var(--mask)}.pce-ac-row[aria-selected] .pce-ac-icon:before{color:var(--widget__color-active)}.pce-ac-icon-namespace{--mask: url('data:image/svg+xml,')}.pce-ac-icon-unit{--mask: url('data:image/svg+xml,')}.pce-ac-icon-parameter{--mask: url('data:image/svg+xml,')}.pce-ac-icon-snippet{--mask: url('data:image/svg+xml,')}.pce-ac-icon-enum{--mask: url('data:image/svg+xml,')}.pce-ac-icon-interface{--mask: url('data:image/svg+xml,')}.pce-ac-icon-constant{--mask: url('data:image/svg+xml,')}.pce-ac-icon-class{--mask: url('data:image/svg+xml,')}.pce-ac-icon-property{--mask: url('data:image/svg+xml,')}.pce-ac-icon-variable{--mask: url('data:image/svg+xml,')}.pce-ac-icon-function{--mask: url('data:image/svg+xml,')}.pce-ac-icon-keyword{--mask: url('data:image/svg+xml,')} diff --git a/pkg-r/inst/js/prism-code-editor/autocomplete.css b/pkg-r/inst/js/prism-code-editor/autocomplete.css new file mode 100644 index 00000000..0a35f2c7 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/autocomplete.css @@ -0,0 +1 @@ +.pce-ac-tooltip{background:var(--widget__bg);border:1px solid var(--widget__border);pointer-events:auto;box-sizing:border-box;position:sticky;right:.5em;left:.5em;overflow:auto;border-radius:.3em;white-space:pre}.pce-ac-tooltip>ul{all:unset;display:block;box-sizing:border-box;overflow:hidden}.pce-ac-row{display:flex;padding:0 .2em;height:1.4em;align-items:center;gap:.5em;cursor:pointer}.pce-ac-row span{color:var(--pce-ac-match, #52b1ff)}.pce-ac-row div{overflow-x:hidden;text-overflow:ellipsis}.pce-ac-details{opacity:.75;font-size:90%;margin-left:auto}.pce-ac-details:empty{display:none}@media (hover: hover){.pce-ac-row:hover{background:#7772}}.pce-ac-row[aria-selected]{background:#1094ff36;color:var(--widget__color-active)}.pce-tabstops span{background:var(--pce-tabstop, #7c7c7c4d)}.pce-tabstops :empty{padding:0 1px;margin:0 -1px} diff --git a/pkg-r/inst/js/prism-code-editor/common-vQBcBSOy.js b/pkg-r/inst/js/prism-code-editor/common-vQBcBSOy.js new file mode 100644 index 00000000..8fe8f6a9 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/common-vQBcBSOy.js @@ -0,0 +1,259 @@ +import "./languages/abap.js"; +import "./languages/abnf.js"; +import "./languages/actionscript.js"; +import "./languages/ada.js"; +import "./languages/agda.js"; +import "./languages/al.js"; +import "./languages/antlr4.js"; +import "./languages/apacheconf.js"; +import "./languages/apex.js"; +import "./languages/apl.js"; +import "./languages/applescript.js"; +import "./languages/aql.js"; +import "./languages/arduino.js"; +import "./languages/arff.js"; +import "./languages/arturo.js"; +import "./languages/asciidoc.js"; +import "./languages/asm.js"; +import "./languages/aspnet.js"; +import "./languages/autohotkey.js"; +import "./languages/autoit.js"; +import "./languages/avisynth.js"; +import "./languages/avro-idl.js"; +import "./languages/awk.js"; +import "./languages/bash.js"; +import "./languages/basic.js"; +import "./languages/batch.js"; +import "./languages/bbj.js"; +import "./languages/bicep.js"; +import "./languages/birb.js"; +import "./languages/bison.js"; +import "./languages/bqn.js"; +import "./languages/brightscript.js"; +import "./languages/bro.js"; +import "./languages/bsl.js"; +import "./languages/cfscript.js"; +import "./languages/chaiscript.js"; +import "./languages/cil.js"; +import "./languages/cilk.js"; +import "./languages/clike.js"; +import "./languages/clojure.js"; +import "./languages/cmake.js"; +import "./languages/cobol.js"; +import "./languages/coffeescript.js"; +import "./languages/concurnas.js"; +import "./languages/cooklang.js"; +import "./languages/coq.js"; +import "./languages/cshtml.js"; +import "./languages/css.js"; +import "./languages/cue.js"; +import "./languages/cypher.js"; +import "./languages/dataweave.js"; +import "./languages/dax.js"; +import "./languages/dhall.js"; +import "./languages/django.js"; +import "./languages/dns-zone-file.js"; +import "./languages/docker.js"; +import "./languages/dot.js"; +import "./languages/ebnf.js"; +import "./languages/editorconfig.js"; +import "./languages/eiffel.js"; +import "./languages/ejs.js"; +import "./languages/elixir.js"; +import "./languages/elm.js"; +import "./languages/erb.js"; +import "./languages/erlang.js"; +import "./languages/etlua.js"; +import "./languages/excel-formula.js"; +import "./languages/factor.js"; +import "./languages/false.js"; +import "./languages/firestore-security-rules.js"; +import "./languages/fortran.js"; +import "./languages/fsharp.js"; +import "./languages/ftl.js"; +import "./languages/gap.js"; +import "./languages/gcode.js"; +import "./languages/gdscript.js"; +import "./languages/gettext.js"; +import "./languages/gherkin.js"; +import "./languages/git.js"; +import "./languages/glsl.js"; +import "./languages/gml.js"; +import "./languages/gn.js"; +import "./languages/go-module.js"; +import "./languages/gradle.js"; +import "./languages/graphql.js"; +import "./languages/groovy.js"; +import "./languages/haml.js"; +import "./languages/handlebars.js"; +import "./languages/haskell.js"; +import "./languages/hcl.js"; +import "./languages/hoon.js"; +import "./languages/html.js"; +import "./languages/ichigojam.js"; +import "./languages/icon.js"; +import "./languages/iecst.js"; +import "./languages/ignore.js"; +import "./languages/inform7.js"; +import "./languages/ini.js"; +import "./languages/io.js"; +import "./languages/j.js"; +import "./languages/jolie.js"; +import "./languages/jq.js"; +import "./languages/json.js"; +import "./languages/jsx.js"; +import "./languages/julia.js"; +import "./languages/keepalived.js"; +import "./languages/keyman.js"; +import "./languages/kotlin.js"; +import "./languages/kumir.js"; +import "./languages/kusto.js"; +import "./languages/latex.js"; +import "./languages/latte.js"; +import "./languages/lilypond.js"; +import "./languages/linker-script.js"; +import "./languages/liquid.js"; +import "./languages/lisp.js"; +import "./languages/livescript.js"; +import "./languages/llvm.js"; +import "./languages/lolcode.js"; +import "./languages/lua.js"; +import "./languages/magma.js"; +import "./languages/makefile.js"; +import "./languages/mata.js"; +import "./languages/matlab.js"; +import "./languages/maxscript.js"; +import "./languages/mel.js"; +import "./languages/mermaid.js"; +import "./languages/metafont.js"; +import "./languages/mizar.js"; +import "./languages/mongodb.js"; +import "./languages/monkey.js"; +import "./languages/moonscript.js"; +import "./languages/n1ql.js"; +import "./languages/n4js.js"; +import "./languages/nand2tetris-hdl.js"; +import "./languages/naniscript.js"; +import "./languages/neon.js"; +import "./languages/nevod.js"; +import "./languages/nginx.js"; +import "./languages/nim.js"; +import "./languages/nix.js"; +import "./languages/nsis.js"; +import "./languages/objectivec.js"; +import "./languages/ocaml.js"; +import "./languages/odin.js"; +import "./languages/opencl.js"; +import "./languages/openqasm.js"; +import "./languages/oz.js"; +import "./languages/parigp.js"; +import "./languages/parser.js"; +import "./languages/pascal.js"; +import "./languages/peoplecode.js"; +import "./languages/perl.js"; +import "./languages/php.js"; +import "./languages/plant-uml.js"; +import "./languages/powerquery.js"; +import "./languages/powershell.js"; +import "./languages/processing.js"; +import "./languages/prolog.js"; +import "./languages/promql.js"; +import "./languages/properties.js"; +import "./languages/protobuf.js"; +import "./languages/psl.js"; +import "./languages/pug.js"; +import "./languages/puppet.js"; +import "./languages/pure.js"; +import "./languages/purebasic.js"; +import "./languages/python.js"; +import "./languages/q.js"; +import "./languages/qml.js"; +import "./languages/qore.js"; +import "./languages/qsharp.js"; +import "./languages/r.js"; +import "./languages/reason.js"; +import "./languages/rego.js"; +import "./languages/rescript.js"; +import "./languages/rest.js"; +import "./languages/rip.js"; +import "./languages/roboconf.js"; +import "./languages/robotframework.js"; +import "./languages/ruby.js"; +import "./languages/rust.js"; +import "./languages/sas.js"; +import "./languages/scala.js"; +import "./languages/scheme.js"; +import "./languages/smali.js"; +import "./languages/smalltalk.js"; +import "./languages/smarty.js"; +import "./languages/sml.js"; +import "./languages/solidity.js"; +import "./languages/solution-file.js"; +import "./languages/soy.js"; +import "./languages/splunk-spl.js"; +import "./languages/sqf.js"; +import "./languages/sql.js"; +import "./languages/squirrel.js"; +import "./languages/stan.js"; +import "./languages/stata.js"; +import "./languages/stylus.js"; +import "./languages/supercollider.js"; +import "./languages/swift.js"; +import "./languages/systemd.js"; +import "./languages/tcl.js"; +import "./languages/textile.js"; +import "./languages/toml.js"; +import "./languages/tremor.js"; +import "./languages/tt2.js"; +import "./languages/turtle.js"; +import "./languages/twig.js"; +import "./languages/typoscript.js"; +import "./languages/unrealscript.js"; +import "./languages/uorazor.js"; +import "./languages/v.js"; +import "./languages/vala.js"; +import "./languages/vbnet.js"; +import "./languages/velocity.js"; +import "./languages/verilog.js"; +import "./languages/vhdl.js"; +import "./languages/vim.js"; +import "./languages/visual-basic.js"; +import "./languages/warpscript.js"; +import "./languages/wasm.js"; +import "./languages/web-idl.js"; +import "./languages/wgsl.js"; +import "./languages/wiki.js"; +import "./languages/wolfram.js"; +import "./languages/wren.js"; +import "./languages/xeora.js"; +import "./languages/xml.js"; +import "./languages/xojo.js"; +import "./languages/xquery.js"; +import "./languages/yaml.js"; +import "./languages/yang.js"; +import "./languages/zig.js"; +import { matchBrackets } from "./extensions/matchBrackets/index.js"; +import { highlightBracketPairs } from "./extensions/matchBrackets/highlight.js"; +import { indentGuides } from "./extensions/guides.js"; +import { cursorPosition } from "./extensions/cursor.js"; +import { defaultCommands, editHistory } from "./extensions/commands.js"; +import { h as highlightSelectionMatches } from "./selection-C1jc115I.js"; +const common = (history = editHistory()) => [ + defaultCommands(), + indentGuides(), + matchBrackets(), + highlightBracketPairs(), + cursorPosition(), + highlightSelectionMatches(), + history, + { + update(editor) { + if (editor.value != editor.textarea.value) + history.clear(); + } + } +]; +export { + common +}; +//# sourceMappingURL=common-vQBcBSOy.js.map diff --git a/pkg-r/inst/js/prism-code-editor/copy.css b/pkg-r/inst/js/prism-code-editor/copy.css new file mode 100644 index 00000000..4f8fa4c3 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/copy.css @@ -0,0 +1 @@ +.pce-copy{all:unset;cursor:pointer;position:sticky;right:.5em;top:.5em;left:.5em;box-shadow:inset 0 0 0 1px var(--widget__border);margin:-9in 0 0;padding:.6em;background:var(--widget__bg);z-index:3;color:var(--widget__color-options);pointer-events:auto;display:grid!important;align-items:center;font:400 1em/1.5 Arial,Helvetica,sans-serif}.pce-copy,.pce-copy:after,.pce-copy:before{opacity:0;border-radius:.4em;transition:opacity .1s ease-out}.pce-copy:after{content:attr(aria-label);position:absolute;right:calc(100% + .5em);background:#000000b3;color:#fff;text-align:center;width:8ch;font-size:80%;padding:.3em 0;pointer-events:none}.pce-copy:before{content:"";position:absolute;top:0;bottom:0;left:0;right:0;background:#9992;box-shadow:inset 0 0 0 1px #999}.prism-code-editor:hover .pce-copy,.pce-copy:hover:before,.pce-copy:hover:after{opacity:1}.pce-copy:focus-visible:before,.pce-copy:focus-visible,.pce-copy:focus-visible:after{opacity:1} diff --git a/pkg-r/inst/js/prism-code-editor/data-CCzVscJt.js b/pkg-r/inst/js/prism-code-editor/data-CCzVscJt.js new file mode 100644 index 00000000..15965a74 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/data-CCzVscJt.js @@ -0,0 +1,713 @@ +const attrValueB = ["true", "false"]; +const attrValueU = ["true", "false", "undefined"]; +const attrValueO = ["on", "off"]; +const attrValueY = ["yes", "no"]; +const attrValueW = ["soft", "hard"]; +const attrValueD = ["ltr", "rtl", "auto"]; +const attrValueM = ["get", "post", "dialog"]; +const attrValueFm = ["get", "post"]; +const attrValueS = ["row", "col", "rowgroup", "colgroup"]; +const attrValueT = ["hidden", "text", "search", "tel", "url", "email", "password", "datetime", "date", "month", "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio", "file", "submit", "image", "reset", "button"]; +const attrValueIm = ["none", "text", "decimal", "numeric", "tel", "search", "email", "url"]; +const attrValueBt = ["button", "submit", "reset", "menu"]; +const attrValueLt = ["1", "a", "A", "i", "I"]; +const attrValueEt = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]; +const attrValueTk = ["subtitles", "captions", "descriptions", "chapters", "metadata"]; +const attrValuePl = ["none", "metadata", "auto"]; +const attrValueSh = ["circle", "default", "poly", "rect"]; +const attrValueXo = ["anonymous", "use-credentials"]; +const attrValueTarget = ["_self", "_blank", "_parent", "_top"]; +const attrValueSb = ["allow-forms", "allow-modals", "allow-pointer-lock", "allow-popups", "allow-popups-to-escape-sandbox", "allow-same-origin", "allow-scripts", "allow-top-navigation"]; +const attrValueTristate = ["true", "false", "mixed", "undefined"]; +const attrValueInputautocomplete = ["additional-name", "address-level1", "address-level2", "address-level3", "address-level4", "address-line1", "address-line2", "address-line3", "bday", "bday-year", "bday-day", "bday-month", "billing", "cc-additional-name", "cc-csc", "cc-exp", "cc-exp-month", "cc-exp-year", "cc-family-name", "cc-given-name", "cc-name", "cc-number", "cc-type", "country", "country-name", "current-password", "email", "family-name", "fax", "given-name", "home", "honorific-prefix", "honorific-suffix", "impp", "language", "mobile", "name", "new-password", "nickname", "off", "on", "organization", "organization-title", "pager", "photo", "postal-code", "sex", "shipping", "street-address", "tel-area-code", "tel", "tel-country-code", "tel-extension", "tel-local", "tel-local-prefix", "tel-local-suffix", "tel-national", "transaction-amount", "transaction-currency", "url", "username", "work"]; +const attrValueAutocomplete = ["inline", "list", "both", "none"]; +const attrValueCurrent = ["true", "false", "page", "step", "location", "date", "time"]; +const attrValueDropeffect = ["copy", "move", "link", "execute", "popup", "none"]; +const attrValueInvalid = ["true", "false", "grammar", "spelling"]; +const attrValueLive = ["off", "polite", "assertive"]; +const attrValueOrientation = ["vertical", "horizontal", "undefined"]; +const attrValueRelevant = ["additions", "removals", "text", "all"]; +const attrValueSort = ["ascending", "descending", "none", "other"]; +const attrValueRoles = ["alert", "alertdialog", "button", "checkbox", "dialog", "gridcell", "link", "log", "marquee", "menuitem", "menuitemcheckbox", "menuitemradio", "option", "progressbar", "radio", "scrollbar", "searchbox", "slider", "spinbutton", "status", "switch", "tab", "tabpanel", "textbox", "timer", "tooltip", "treeitem", "combobox", "grid", "listbox", "menu", "menubar", "radiogroup", "tablist", "tree", "treegrid", "application", "article", "cell", "columnheader", "definition", "directory", "document", "feed", "figure", "group", "heading", "img", "list", "listitem", "math", "none", "note", "presentation", "region", "row", "rowgroup", "rowheader", "separator", "table", "term", "text", "toolbar", "banner", "complementary", "contentinfo", "form", "main", "navigation", "region", "search"]; +const attrValueHaspopup = ["true", "false", "menu", "listbox", "tree", "grid", "dialog"]; +const attrValueDecoding = ["sync", "async", "auto"]; +const attrValueLoading = ["eager", "lazy"]; +const attrValueReferrerpolicy = ["no-referrer", "no-referrer-when-downgrade", "origin", "origin-when-cross-origin", "same-origin", "strict-origin", "strict-origin-when-cross-origin", "unsafe-url"]; +const attrValueEkh = ["enter", "done", "go", "next", "previous", "search", "send"]; +const attrValueCe = ["true", "false", "plaintext-only"]; +const htmlEventHandlers = { + onabort: null, + onblur: null, + oncanplay: null, + oncanplaythrough: null, + onchange: null, + onclick: null, + oncontextmenu: null, + ondblclick: null, + ondrag: null, + ondragend: null, + ondragenter: null, + ondragleave: null, + ondragover: null, + ondragstart: null, + ondrop: null, + ondurationchange: null, + onemptied: null, + onended: null, + onerror: null, + onfocus: null, + onformchange: null, + onforminput: null, + oninput: null, + oninvalid: null, + onkeydown: null, + onkeypress: null, + onkeyup: null, + onload: null, + onloadeddata: null, + onloadedmetadata: null, + onloadstart: null, + onmousedown: null, + onmousemove: null, + onmouseout: null, + onmouseover: null, + onmouseup: null, + onmousewheel: null, + onmouseenter: null, + onmouseleave: null, + onpause: null, + onplay: null, + onplaying: null, + onprogress: null, + onratechange: null, + onreset: null, + onresize: null, + onreadystatechange: null, + onscroll: null, + onseeked: null, + onseeking: null, + onselect: null, + onshow: null, + onstalled: null, + onsubmit: null, + onsuspend: null, + ontimeupdate: null, + onvolumechange: null, + onwaiting: null, + onpointercancel: null, + onpointerdown: null, + onpointerenter: null, + onpointerleave: null, + onpointerlockchange: null, + onpointerlockerror: null, + onpointermove: null, + onpointerout: null, + onpointerover: null, + onpointerup: null +}; +const ariaAttributes = { + "aria-activedescendant": null, + "aria-atomic": attrValueB, + "aria-autocomplete": attrValueAutocomplete, + "aria-busy": attrValueB, + "aria-checked": attrValueTristate, + "aria-colcount": null, + "aria-colindex": null, + "aria-colspan": null, + "aria-controls": null, + "aria-current": attrValueCurrent, + "aria-describedby": null, + "aria-disabled": attrValueB, + "aria-dropeffect": attrValueDropeffect, + "aria-errormessage": null, + "aria-expanded": attrValueU, + "aria-flowto": null, + "aria-grabbed": attrValueU, + "aria-haspopup": attrValueHaspopup, + "aria-hidden": attrValueB, + "aria-invalid": attrValueInvalid, + "aria-label": null, + "aria-labelledby": null, + "aria-level": null, + "aria-live": attrValueLive, + "aria-modal": attrValueB, + "aria-multiline": attrValueB, + "aria-multiselectable": attrValueB, + "aria-orientation": attrValueOrientation, + "aria-owns": null, + "aria-placeholder": null, + "aria-posinset": null, + "aria-pressed": attrValueTristate, + "aria-readonly": attrValueB, + "aria-relevant": attrValueRelevant, + "aria-required": attrValueB, + "aria-roledescription": null, + "aria-rowcount": null, + "aria-rowindex": null, + "aria-rowspan": null, + "aria-selected": attrValueU, + "aria-setsize": null, + "aria-sort": attrValueSort, + "aria-valuemax": null, + "aria-valuemin": null, + "aria-valuenow": null, + "aria-valuetext": null, + "aria-details": null, + "aria-keyshortcuts": null +}; +const globalHtmlAttributes = { + ...ariaAttributes, + ...htmlEventHandlers, + accesskey: null, + autocapitalize: null, + class: null, + contenteditable: attrValueCe, + contextmenu: null, + dir: attrValueD, + draggable: attrValueB, + dropzone: null, + enterkeyhint: attrValueEkh, + exportparts: null, + hidden: null, + id: null, + inert: null, + inputmode: attrValueIm, + is: null, + itemid: null, + itemprop: null, + itemref: null, + itemscope: null, + itemtype: null, + lang: null, + part: null, + role: attrValueRoles, + slot: null, + spellcheck: attrValueB, + style: null, + tabindex: null, + title: null, + translate: attrValueY +}; +const empty = {}; +const htmlTags = { + html: { + manifest: null, + version: null, + xmlns: null + }, + head: { + profile: null + }, + title: empty, + base: { + href: null, + target: attrValueTarget + }, + link: { + href: null, + crossorigin: attrValueXo, + rel: null, + media: null, + hreflang: null, + type: null, + sizes: null, + as: null, + importance: null, + integrity: null, + referrerpolicy: null + }, + meta: { + name: null, + "http-equiv": null, + content: null, + charset: null, + scheme: null + }, + style: { + media: null, + nonce: null, + type: null, + scoped: null + }, + body: { + onafterprint: null, + onbeforeprint: null, + onbeforeunload: null, + onhashchange: null, + onlanguagechange: null, + onmessage: null, + onoffline: null, + ononline: null, + onpagehide: null, + onpageshow: null, + onpopstate: null, + onstorage: null, + onunload: null, + alink: null, + background: null, + bgcolor: null, + bottommargin: null, + leftmargin: null, + link: null, + onredo: null, + onundo: null, + rightmargin: null, + text: null, + topmargin: null, + vlink: null + }, + article: empty, + section: empty, + nav: empty, + aside: empty, + h1: empty, + h2: empty, + h3: empty, + h4: empty, + h5: empty, + h6: empty, + header: empty, + footer: empty, + address: empty, + p: empty, + hr: { + align: null, + color: null, + noshade: null, + size: null, + width: null + }, + pre: { + cols: null, + width: null, + wrap: null + }, + blockquote: { + cite: null + }, + ol: { + reversed: null, + start: null, + type: attrValueLt, + compact: null + }, + ul: { + compact: null + }, + li: { + value: null, + type: null + }, + dl: empty, + dt: empty, + dd: { + nowrap: null + }, + figure: empty, + figcaption: empty, + main: empty, + div: empty, + a: { + href: null, + target: attrValueTarget, + download: null, + ping: null, + rel: null, + hreflang: null, + type: null, + referrerpolicy: null + }, + em: empty, + strong: empty, + small: empty, + s: empty, + cite: empty, + q: { + cite: null + }, + dfn: empty, + abbr: empty, + ruby: empty, + rb: empty, + rt: empty, + rp: empty, + time: { + datetime: null + }, + code: empty, + var: empty, + samp: empty, + kbd: empty, + sub: empty, + sup: empty, + i: empty, + b: empty, + u: empty, + mark: empty, + bdi: empty, + bdo: {}, + span: empty, + br: { + clear: null + }, + wbr: empty, + ins: { + cite: null, + datetime: null + }, + del: { + cite: null, + datetime: null + }, + picture: empty, + img: { + alt: null, + src: null, + srcset: null, + crossorigin: attrValueXo, + usemap: null, + ismap: null, + width: null, + height: null, + decoding: attrValueDecoding, + loading: attrValueLoading, + referrerpolicy: attrValueReferrerpolicy, + sizes: null, + importance: null, + intrinsicsize: null + }, + iframe: { + src: null, + srcdoc: null, + name: null, + sandbox: attrValueSb, + seamless: null, + allowfullscreen: null, + width: null, + height: null, + allow: null, + allowpaymentrequest: null, + csp: null, + importance: null, + referrerpolicy: null + }, + embed: { + src: null, + type: null, + width: null, + height: null + }, + object: { + data: null, + type: null, + typemustmatch: null, + name: null, + usemap: null, + form: null, + width: null, + height: null, + archive: null, + border: null, + classid: null, + codebase: null, + codetype: null, + declare: null, + standby: null + }, + param: { + name: null, + value: null, + type: null, + valuetype: null + }, + video: { + src: null, + crossorigin: attrValueXo, + poster: null, + preload: attrValuePl, + autoplay: null, + mediagroup: null, + loop: null, + muted: null, + controls: null, + width: null, + height: null + }, + audio: { + src: null, + crossorigin: attrValueXo, + preload: attrValuePl, + autoplay: null, + mediagroup: null, + loop: null, + muted: null, + controls: null + }, + source: { + src: null, + type: null, + sizes: null, + srcset: null, + media: null + }, + track: { + default: null, + kind: attrValueTk, + label: null, + src: null, + srclang: null + }, + map: { + name: null + }, + area: { + alt: null, + coords: null, + shape: attrValueSh, + href: null, + target: attrValueTarget, + download: null, + ping: null, + rel: null, + hreflang: null, + type: null + }, + table: { + border: null, + align: null + }, + caption: { + align: null + }, + colgroup: { + span: null, + align: null + }, + col: { + span: null, + align: null + }, + tbody: { + align: null + }, + thead: { + align: null + }, + tfoot: { + align: null + }, + tr: { + align: null + }, + td: { + colspan: null, + rowspan: null, + headers: null, + abbr: null, + align: null, + axis: null, + bgcolor: null + }, + th: { + colspan: null, + rowspan: null, + headers: null, + scope: attrValueS, + sorted: null, + abbr: null, + align: null, + axis: null, + bgcolor: null + }, + form: { + "accept-charset": null, + action: null, + autocomplete: attrValueO, + enctype: attrValueEt, + method: attrValueM, + name: null, + novalidate: null, + target: attrValueTarget, + accept: null + }, + label: { + form: null, + for: null + }, + input: { + accept: null, + alt: null, + autocomplete: attrValueInputautocomplete, + autofocus: null, + checked: null, + dirname: null, + disabled: null, + form: null, + formaction: null, + formenctype: attrValueEt, + formmethod: attrValueFm, + formnovalidate: null, + formtarget: null, + height: null, + list: null, + max: null, + maxlength: null, + min: null, + minlength: null, + multiple: null, + name: null, + pattern: null, + placeholder: null, + readonly: null, + required: null, + size: null, + src: null, + step: null, + type: attrValueT, + value: null, + width: null + }, + button: { + autofocus: null, + disabled: null, + form: null, + formaction: null, + formenctype: attrValueEt, + formmethod: attrValueFm, + formnovalidate: null, + formtarget: null, + name: null, + type: attrValueBt, + value: null, + autocomplete: null + }, + select: { + autocomplete: attrValueInputautocomplete, + autofocus: null, + disabled: null, + form: null, + multiple: null, + name: null, + required: null, + size: null + }, + datalist: empty, + optgroup: { + disabled: null, + label: null + }, + option: { + disabled: null, + label: null, + selected: null, + value: null + }, + textarea: { + autocomplete: attrValueInputautocomplete, + autofocus: null, + cols: null, + dirname: null, + disabled: null, + form: null, + maxlength: null, + minlength: null, + name: null, + placeholder: null, + readonly: null, + required: null, + rows: null, + wrap: attrValueW + }, + output: { + for: null, + form: null, + name: null + }, + progress: { + value: null, + max: null + }, + meter: { + value: null, + min: null, + max: null, + low: null, + high: null, + optimum: null, + form: null + }, + fieldset: { + disabled: null, + form: null, + name: null + }, + legend: empty, + details: { + open: null + }, + summary: empty, + dialog: { + open: null + }, + script: { + src: null, + type: null, + charset: null, + async: null, + defer: null, + crossorigin: attrValueXo, + nonce: null, + integrity: null, + nomodule: null, + referrerpolicy: null, + text: null + }, + noscript: empty, + template: empty, + canvas: { + width: null, + height: null, + "moz-opaque": null + }, + slot: { + name: null + }, + data: { + value: null + }, + hgroup: empty, + menu: empty +}; +export { + attrValueT as A, + attrValueBt as B, + attrValueW as C, + attrValuePl as D, + htmlEventHandlers as a, + attrValueReferrerpolicy as b, + attrValueTarget as c, + attrValueB as d, + attrValueXo as e, + attrValueDecoding as f, + globalHtmlAttributes as g, + htmlTags as h, + ariaAttributes as i, + attrValueCe as j, + attrValueD as k, + attrValueEkh as l, + attrValueY as m, + attrValueRoles as n, + attrValueO as o, + attrValueIm as p, + attrValueLt as q, + attrValueLoading as r, + attrValueSb as s, + attrValueTk as t, + attrValueSh as u, + attrValueS as v, + attrValueEt as w, + attrValueM as x, + attrValueInputautocomplete as y, + attrValueFm as z +}; +//# sourceMappingURL=data-CCzVscJt.js.map diff --git a/pkg-r/inst/js/prism-code-editor/dracula.js b/pkg-r/inst/js/prism-code-editor/dracula.js new file mode 100644 index 00000000..a11ce623 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/dracula.js @@ -0,0 +1,5 @@ +const dracula = ".prism-code-editor{caret-color:#aeafad;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;--editor__bg: #282a36;--widget__border: #454545;--widget__bg: #21222c;--widget__color: #f8f8f2;--widget__color-active: #fff;--widget__color-options: #b2b2a8;--widget__bg-input: #282a36;--widget__bg-hover: #5a5d5e80;--widget__bg-active: #6272a466;--widget__focus-ring: #bd93f9;--search__bg-find: #ffffff40;--widget__bg-error: #5a1d1d;--widget__error-ring: #ff5555;--editor__border-highlight: 2px solid #44475a;--editor__bg-selection-match: #424450;--editor__line-number: #6272a4;--editor__bg-scrollbar: 0, 0%, 50%;--editor__bg-fold: #c5c5c5;--bg-guide-indent: #ffffff1a;--pce-ac-icon-class: #ee9d28;--pce-ac-icon-enum: #ee9d28;--pce-ac-icon-function: #b180d7;--pce-ac-icon-interface: #75beff;--pce-ac-icon-variable: #75beff;color-scheme:dark}.match-highlight{--editor__bg-highlight: #44475a}.prism-code-editor textarea::selection{background:#44475a;color:#0000}.pce-matches .match{--search__bg-find: #ffb86c80}.active-line{--editor__line-number: #c6c6c6}.guide-indents .active{--bg-guide-indent: #ffffff45}.token.comment,.token.prolog,.token.cdata{color:#6272a4}[class*=language-],.token.punctuation,.language-markdown .code.keyword{color:#f8f8f2}.token.namespace{opacity:.7}.token.tag,.token.operator,.token.attr-equals,.token.doctype-tag,.token.symbol,.token.unit,.token.selector,.token.deleted,.token.keyword,.token.quantifier.number,.token.token.anchor,.token.regex-flags,.token.atrule .rule,.language-markdown .url .content{color:#ff79c6}.token.class-name,.token.maybe-class-name,.language-css .token.function,.language-css .token.property,.language-markdown .url{color:#8be9fd}.token.boolean,.token.number,.token.color,.token.entity,.token.char-set,.token.keyword-this,.token.keyword-null,.token.keyword-undefined,.language-markdown .token.title{color:#bd93f9}.selector .class,.selector .id,.token.pseudo-element,.token.pseudo-class,.token.attr-name,.token.builtin,.token.inserted,.token.function,.token.doctype,.language-markdown .code-language,.language-markdown .code-snippet.code{color:#50fa7b}.token.url,.token.variable{color:#f8f8f2}.token.regex,.language-regex,.token.attr-value,.token.char,.token.string,.token.string-property,.language-markdown .italic *{color:#f1fa8c}.token.regex-delimiter,.token.char-class .operator{color:#f55}.token.important,.token.parameter,.token.group,.token.regex .punctuation,.token.generic,.token.url,.token.atrule,.language-markdown .bold *,.token.constant{color:#ffb86c}.language-tsx .token.builtin,.token.char-class .punctuation,.language-typescript .token.builtin{color:#8be9fd}.token.important,.token.bold{font-weight:700}.token.italic,.token.parameter,.token.attr-name,.doctype .name,.selector .class,.token.pseudo-element,.token.pseudo-class,.tag>.class-name{font-style:italic}.parameter .punctuation{font-style:normal}.token.bracket-level-0,.token.bracket-level-6{color:#f8f8f2}.token.bracket-level-1,.token.bracket-level-7{color:#ff79c6}.token.bracket-level-2,.token.bracket-level-8{color:#8be9fd}.token.bracket-level-3,.token.bracket-level-9{color:#50fa7b}.token.bracket-level-4,.token.bracket-level-10{color:#bd93f9}.token.bracket-level-5,.token.bracket-level-11{color:#ffb86c}.token.interpolation-punctuation{color:#ff79c6}.token.bracket-error{color:#f55}.token.markup-bracket{color:inherit}.active-bracket{box-shadow:inset 0 0 0 1px #b9b9b9,inset 0 0 0 9in #0064001a}.active-tagname,.word-matches span{background:#8be9fd4f}"; +export { + dracula as default +}; +//# sourceMappingURL=dracula-D0vPkjan.js.map diff --git a/pkg-r/inst/js/prism-code-editor/extensions/commands.d.ts b/pkg-r/inst/js/prism-code-editor/extensions/commands.d.ts new file mode 100644 index 00000000..fc593c41 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/extensions/commands.d.ts @@ -0,0 +1,79 @@ +import { BasicExtension } from '../index.js'; + +declare let ignoreTab: boolean; +/** + * Sets whether editors should ignore tab or use it for indentation. + * Users can always toggle this using Ctrl+M / Ctrl+Shift+M (Mac). + */ +declare const setIgnoreTab: (newState: boolean) => boolean; +/** + * Extension that will add automatic indentation, closing of brackets, + * quotes and tags along with the following commands: + * + * - Alt+ArrowUp/Down: Move line up/down + * - Ctrl+ArrowUp/Down (Not on MacOS): Scroll up/down 1 line + * - Shift+Alt+ArrowUp/Down: Copy line up/down + * - Ctrl+Enter (Cmd+Enter on MacOS): Insert blank line + * - Ctrl+[ (Cmd+[ on MacOS): Outdent line + * - Ctrl+] (Cmd+] on MacOS): Indent line + * - Shift+Ctrl+K (Shift+Cmd + K on MacOS): Delete line + * - Ctrl+/ (Cmd+/ on MacOS): Toggle comment + * - Shift+Alt+A: Toggle block comment + * - Ctrl+M (Ctrl+Shift+M on MacOS): Toggle Tab capturing + * + * The shortcuts for the commands are not easily customizable. If you want to customize + * them, you can copy the {@link https://github.com/FIameCaster/prism-code-editor/blob/main/package/src/extensions/commands.ts|source} + * and change the conditions. + * + * @param selfClosePairs Pairs of self-closing brackets and quotes. + * Must be an array of strings with 2 characters each. + * Defaults to `['""', "''", '``', '()', '[]', '{}']`. + * @param selfCloseRegex Regex controlling whether or not a bracket/quote should + * automatically close based on the character before and after the cursor. + * Defaults to ``/([^$\w'"`]["'`]|.[[({])[.,:;\])}>\s]|.[[({]`/s``. + */ +declare const defaultCommands: (selfClosePairs?: string[], selfCloseRegex?: RegExp) => BasicExtension; +export interface EditHistory extends BasicExtension { + /** Clears the history stack. Probably necessary after changing the value of the editor. */ + clear(): void; + /** + * Sets the active entry relative to the current entry. + * + * @param offset The position you want to move to relative to the current entry. + * + * `EditHistory.go(-1)` would be equivalent to an undo while `EditHistory.go(1)` would + * be equivalent to a redo. + * + * If there's no entry at the specified offset, the call does nothing. + */ + go(offset: number): void; + /** + * Returns whether or not there exists a history entry at the specified offset relative + * to the current entry. + * + * This method can be used to determine whether a call to {@link EditHistory.go} with the + * same offset will succeed or do nothing. + */ + has(offset: number): boolean; +} +/** + * History extension that overrides the undo/redo behavior of the browser. + * + * Without this extension, the browser's native undo/redo is used, which can be sufficient + * in some cases. + * + * It should be noted that the history stack is not automatically cleared when the editors + * value is changed programmatically using `editor.setOptions` Instead you can clear the + * stack any time using {@link EditHistory.clear}. + * + * Once added to an editor, this extension can be accessed from `editor.extensions.history`. + * + * If you want to create a new editor with different extensions while keeping the undo/redo + * history of an old editor, you can! Just add the old editor's history extension instance + * to the new editor. Keep in mind that this will fully break the undo/redo behavior of the + * old editor. + * + * @param historyLimit The maximum size of the history stack. Defaults to 999. + */ +declare const editHistory: (historyLimit?: number) => EditHistory; +export { defaultCommands, setIgnoreTab, ignoreTab, editHistory }; diff --git a/pkg-r/inst/js/prism-code-editor/extensions/commands.js b/pkg-r/inst/js/prism-code-editor/extensions/commands.js new file mode 100644 index 00000000..d6684e37 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/extensions/commands.js @@ -0,0 +1,333 @@ +import { l as languageMap, p as preventDefault, i as isMac, b as addTextareaListener } from "../index-BltwYS88.js"; +import { getLanguage, insertText, getModifierCode, getLines, getLineBefore, regexEscape, prevSelection } from "../utils/index.js"; +import { a as getLineStart, g as getStyleValue, b as getLineEnd } from "../local-VpqO7_GV.js"; +let ignoreTab = false; +const clipboard = navigator.clipboard; +const mod = isMac ? 4 : 2; +const setIgnoreTab = (newState) => ignoreTab = newState; +const whitespaceEnd = (str) => str.search(/\S|$/); +const defaultCommands = (selfClosePairs = ['""', "''", "``", "()", "[]", "{}"], selfCloseRegex = /([^$\w'"`]["'`]|.[[({])[.,:;\])}>\s]|.[[({]`/s) => (editor, options) => { + let prevCopy; + const { keyCommandMap, inputCommandMap, getSelection, scrollContainer } = editor; + const getIndent = ({ insertSpaces = true, tabSize } = options) => [insertSpaces ? " " : " ", insertSpaces ? tabSize || 2 : 1]; + const scroll = () => !options.readOnly && !editor.extensions.cursor?.scrollIntoView(); + const selfClose = ([start, end], [open, close], value, wrapOnly) => (start < end || !wrapOnly && selfCloseRegex.test((value[end - 1] || " ") + open + (value[end] || " "))) && !insertText(editor, open + value.slice(start, end) + close, null, null, start + 1, end + 1); + const skipIfEqual = ([start, end], char, value) => start == end && value[end] == char && !editor.setSelection(start + 1); + const insertLines = (old, newL, start, end, selectionStart, selectionEnd) => { + let newLines = newL.join("\n"); + if (newLines != old.join("\n")) { + const last = old.length - 1; + const lastLine = newL[last]; + const oldLastLine = old[last]; + const lastDiff = oldLastLine.length - lastLine.length; + const firstDiff = newL[0].length - old[0].length; + const firstInsersion = start + whitespaceEnd((firstDiff < 0 ? newL : old)[0]); + const lastInsersion = end - oldLastLine.length + whitespaceEnd(lastDiff > 0 ? lastLine : oldLastLine); + const offset = start - end + newLines.length + lastDiff; + const newCursorStart = firstInsersion > selectionStart ? selectionStart : Math.max(firstInsersion, selectionStart + firstDiff); + const newCursorEnd = selectionEnd + start - end + newLines.length; + insertText( + editor, + newLines, + start, + end, + newCursorStart, + selectionEnd < lastInsersion ? newCursorEnd + lastDiff : Math.max(lastInsersion + offset, newCursorEnd) + ); + } + }; + const indent = (outdent, lines, start1, end1, start, end, indentChar, tabSize) => { + insertLines( + lines, + lines.map( + outdent ? (str) => str.slice(whitespaceEnd(str) ? tabSize - whitespaceEnd(str) % tabSize : 0) : (str) => str && indentChar.repeat(tabSize - whitespaceEnd(str) % tabSize) + str + ), + start1, + end1, + start, + end + ); + }; + inputCommandMap["<"] = (_e, selection, value) => selfClose(selection, "<>", value, true); + selfClosePairs.forEach(([open, close]) => { + const isQuote = open == close; + inputCommandMap[open] = (_e, selection, value) => (isQuote && skipIfEqual(selection, close, value) || selfClose(selection, open + close, value)) && scroll(); + if (!isQuote) + inputCommandMap[close] = (_e, selection, value) => skipIfEqual(selection, close, value) && scroll(); + }); + inputCommandMap[">"] = (e, selection, value) => { + const closingTag = languageMap[getLanguage(editor)]?.autoCloseTags?.(selection, value, editor); + if (closingTag) { + insertText(editor, ">" + closingTag, null, null, selection[0] + 1); + preventDefault(e); + } + }; + keyCommandMap.Tab = (e, [start, end], value) => { + if (ignoreTab || options.readOnly || getModifierCode(e) & 6) + return; + const [indentChar, tabSize] = getIndent(options); + const shiftKey = e.shiftKey; + const [lines, start1, end1] = getLines(value, start, end); + if (start < end || shiftKey) { + indent(shiftKey, lines, start1, end1, start, end, indentChar, tabSize); + } else + insertText(editor, indentChar.repeat(tabSize - (start - start1) % tabSize)); + return scroll(); + }; + keyCommandMap.Enter = (e, selection, value) => { + const code = getModifierCode(e) & 7; + if (!code || code == mod) { + if (code) + selection[0] = selection[1] = getLines(value, selection[1])[2]; + const [indentChar, tabSize] = getIndent(); + const [start, end] = selection; + const autoIndent = languageMap[getLanguage(editor)]?.autoIndent; + const indenationCount = Math.floor(whitespaceEnd(getLineBefore(value, start)) / tabSize) * tabSize; + const extraIndent = autoIndent?.[0]?.(selection, value, editor) ? tabSize : 0; + const extraLine = autoIndent?.[1]?.(selection, value, editor); + const newText = "\n" + indentChar.repeat(indenationCount + extraIndent) + (extraLine ? "\n" + indentChar.repeat(indenationCount) : ""); + if (newText[1] || value[end]) { + insertText(editor, newText, start, end, start + indenationCount + extraIndent + 1); + return scroll(); + } + } + }; + keyCommandMap.Backspace = (_e, [start, end], value) => { + if (start == end) { + const line = getLineBefore(value, start); + const tabSize = options.tabSize || 2; + const isPair = selfClosePairs.includes(value.slice(start - 1, start + 1)); + const indenationCount = /[^ ]/.test(line) ? 0 : (line.length - 1) % tabSize + 1; + if (isPair || indenationCount > 1) { + insertText(editor, "", start - (isPair ? 1 : indenationCount), start + isPair); + return scroll(); + } + } + }; + for (let i = 0; i < 2; i++) { + keyCommandMap[i ? "ArrowDown" : "ArrowUp"] = (e, [start, end], value) => { + const code = getModifierCode(e); + if (code == 1) { + const newStart = i ? start : getLineStart(value, start) - 1; + const newEnd = i ? value.indexOf("\n", end) + 1 : end; + if (newStart > -1 && newEnd > 0) { + const [lines, start1, end1] = getLines(value, newStart, newEnd); + const line = lines[i ? "pop" : "shift"](); + const offset = (line.length + 1) * (i ? 1 : -1); + lines[i ? "unshift" : "push"](line); + insertText(editor, lines.join("\n"), start1, end1, start + offset, end + offset); + } + return scroll(); + } else if (code == 9) { + const [lines, start1, end1] = getLines(value, start, end); + const str = lines.join("\n"); + const offset = i ? str.length + 1 : 0; + insertText(editor, str + "\n" + str, start1, end1, start + offset, end + offset); + return scroll(); + } else if (code == 2 && !isMac) { + scrollContainer.scrollBy(0, getStyleValue(scrollContainer, "lineHeight") * (i ? 1 : -1)); + return true; + } + }; + } + addTextareaListener(editor, "keydown", (e) => { + const code = getModifierCode(e); + const keyCode = e.keyCode; + const [start, end, dir] = getSelection(); + if (code == mod && (keyCode == 221 || keyCode == 219)) { + indent(keyCode == 219, ...getLines(editor.value, start, end), start, end, ...getIndent()); + scroll(); + preventDefault(e); + } else if (code == (isMac ? 10 : 2) && keyCode == 77) { + setIgnoreTab(!ignoreTab); + preventDefault(e); + } else if (keyCode == 191 && code == mod || keyCode == 65 && code == 9) { + const value = editor.value; + const isBlock = code == 9; + const position = isBlock ? start : getLineStart(value, start); + const language = languageMap[getLanguage(editor, position)] || {}; + const { line, block } = language.getComments?.(editor, position, value) || language.comments || {}; + const [lines, start1, end1] = getLines(value, start, end); + const last = lines.length - 1; + if (isBlock) { + if (block) { + const [open, close] = block; + const text = value.slice(start, end); + const pos = value.slice(0, start).search(regexEscape(open) + " ?$"); + const matches = RegExp("^ ?" + regexEscape(close)).test(value.slice(end)); + if (pos + 1 && matches) + insertText( + editor, + text, + pos, + end + +(value[end] == " ") + close.length, + pos, + pos + end - start + ); + else + insertText( + editor, + `${open} ${text} ${close}`, + start, + end, + start + open.length + 1, + end + open.length + 1 + ); + scroll(); + preventDefault(e); + } + } else { + if (line) { + const escaped = regexEscape(line); + const regex = RegExp(`^\\s*(${escaped} ?|$)`); + const regex2 = RegExp(escaped + " ?"); + const allWhiteSpace = !/\S/.test(value.slice(start1, end1)); + const newLines = lines.map( + lines.every((line2) => regex.test(line2)) && !allWhiteSpace ? (str) => str.replace(regex2, "") : (str) => allWhiteSpace || /\S/.test(str) ? str.replace(/^\s*/, `$&${line} `) : str + ); + insertLines(lines, newLines, start1, end1, start, end); + scroll(); + preventDefault(e); + } else if (block) { + const [open, close] = block; + const insertionPoint = whitespaceEnd(lines[0]); + const hasComment = lines[0].startsWith(open, insertionPoint) && lines[last].endsWith(close); + const newLines = lines.slice(); + newLines[0] = lines[0].replace( + hasComment ? RegExp(regexEscape(open) + " ?") : /(?=\S)|$/, + hasComment ? "" : open + " " + ); + let diff = newLines[0].length - lines[0].length; + newLines[last] = hasComment ? newLines[last].replace(RegExp(`( ?${regexEscape(close)})?$`), "") : newLines[last] + " " + close; + let newText = newLines.join("\n"); + let firstInsersion = insertionPoint + start1; + let newStart = firstInsersion > start ? start : Math.max(start + diff, firstInsersion); + let newEnd = firstInsersion > end - (start != end) ? end : Math.min(Math.max(firstInsersion, end + diff), start1 + newText.length); + insertText(editor, newText, start1, end1, newStart, Math.max(newStart, newEnd)); + scroll(); + preventDefault(e); + } + } + } else if (code == 8 + mod && keyCode == 75) { + const value = editor.value; + const [lines, start1, end1] = getLines(value, start, end); + const column = dir > "f" ? end - end1 + lines.pop().length : start - start1; + const newLineLen = getLineEnd(value, end1 + 1) - end1 - 1; + insertText( + editor, + "", + start1 - !!start1, + end1 + !start1, + start1 + Math.min(column, newLineLen) + ); + scroll(); + preventDefault(e); + } + }); + ["copy", "cut", "paste"].forEach( + (type) => addTextareaListener(editor, type, (e) => { + const [start, end] = getSelection(); + if (start == end && clipboard) { + const [[line], start1, end1] = getLines(editor.value, start, end); + if (type == "paste") { + if (e.clipboardData.getData("text/plain") == prevCopy) { + insertText(editor, prevCopy + "\n", start1, start1, start + prevCopy.length + 1); + scroll(); + preventDefault(e); + } + } else { + clipboard.writeText(prevCopy = line); + if (type == "cut") + insertText(editor, "", start1, end1 + 1), scroll(); + preventDefault(e); + } + } + }) + ); +}; +const editHistory = (historyLimit = 999) => { + let sp = 0; + let currentEditor; + let allowMerge; + let isTyping = false; + let prevInputType; + let prevData; + let prevTime; + let isMerge; + let textarea; + let getSelection; + const stack = []; + const update = (index) => { + if (index >= historyLimit) { + index--; + stack.shift(); + } + stack.splice(sp = index, historyLimit, [currentEditor.value, getSelection(), getSelection()]); + }; + const setEditorState = (index) => { + if (stack[index]) { + textarea.value = stack[index][0]; + textarea.setSelectionRange(...stack[index][index < sp ? 2 : 1]); + currentEditor.update(); + currentEditor.extensions.cursor?.scrollIntoView(); + sp = index; + allowMerge = false; + } + }; + const self = (editor, options) => { + editor.extensions.history = self; + currentEditor = editor; + getSelection = editor.getSelection; + textarea || update(0); + textarea = editor.textarea; + editor.addListener("selectionChange", () => { + allowMerge = isTyping; + isTyping = false; + }); + addTextareaListener(editor, "beforeinput", (e) => { + let data = e.data; + let inputType = e.inputType; + let time = e.timeStamp; + if (/history/.test(inputType)) { + setEditorState(sp + (inputType[7] == "U" ? -1 : 1)); + preventDefault(e); + } else if (!(isMerge = allowMerge && (prevInputType == inputType || time - prevTime < 99 && inputType.slice(-4) == "Drop") && !prevSelection && (data != " " || prevData == data))) { + stack[sp][2] = prevSelection || getSelection(); + } + isTyping = true; + prevData = data; + prevTime = time; + prevInputType = inputType; + }); + addTextareaListener(editor, "input", () => update(sp + !isMerge)); + addTextareaListener(editor, "keydown", (e) => { + if (!options.readOnly) { + const code = getModifierCode(e); + const keyCode = e.keyCode; + const isUndo = code == mod && keyCode == 90; + const isRedo = code == mod + 8 && keyCode == 90 || !isMac && code == mod && keyCode == 89; + if (isUndo) { + setEditorState(sp - 1); + preventDefault(e); + } else if (isRedo) { + setEditorState(sp + 1); + preventDefault(e); + } + } + }); + }; + self.clear = () => { + update(0); + allowMerge = false; + }; + self.has = (offset) => sp + offset in stack; + self.go = (offset) => setEditorState(sp + offset); + return self; +}; +export { + defaultCommands, + editHistory, + ignoreTab, + setIgnoreTab +}; +//# sourceMappingURL=commands.js.map diff --git a/pkg-r/inst/js/prism-code-editor/extensions/commands.js.map b/pkg-r/inst/js/prism-code-editor/extensions/commands.js.map new file mode 100644 index 00000000..fa8d1af2 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/extensions/commands.js.map @@ -0,0 +1 @@ +{"version":3,"file":"commands.js","sources":["../../src/extensions/commands.ts"],"sourcesContent":["/** @module commands */\r\n\r\nimport { InputSelection, BasicExtension, PrismEditor } from \"../index.js\"\r\nimport { isMac, preventDefault, languageMap, addTextareaListener } from \"../core.js\"\r\nimport {\r\n\tgetLanguage,\r\n\tinsertText,\r\n\tgetLineBefore,\r\n\tgetLines,\r\n\tregexEscape,\r\n\tgetModifierCode,\r\n\tprevSelection,\r\n} from \"../utils/index.js\"\r\nimport { getLineEnd, getLineStart, getStyleValue } from \"../utils/local.js\"\r\n\r\nlet ignoreTab = false\r\nconst clipboard = navigator.clipboard\r\nconst mod = isMac ? 4 : 2\r\n/**\r\n * Sets whether editors should ignore tab or use it for indentation.\r\n * Users can always toggle this using Ctrl+M / Ctrl+Shift+M (Mac).\r\n */\r\nconst setIgnoreTab = (newState: boolean) => (ignoreTab = newState)\r\nconst whitespaceEnd = (str: string) => str.search(/\\S|$/)\r\n\r\n/**\r\n * Extension that will add automatic indentation, closing of brackets,\r\n * quotes and tags along with the following commands:\r\n *\r\n * - Alt+ArrowUp/Down: Move line up/down\r\n * - Ctrl+ArrowUp/Down (Not on MacOS): Scroll up/down 1 line\r\n * - Shift+Alt+ArrowUp/Down: Copy line up/down\r\n * - Ctrl+Enter (Cmd+Enter on MacOS): Insert blank line\r\n * - Ctrl+[ (Cmd+[ on MacOS): Outdent line\r\n * - Ctrl+] (Cmd+] on MacOS): Indent line\r\n * - Shift+Ctrl+K (Shift+Cmd + K on MacOS): Delete line\r\n * - Ctrl+/ (Cmd+/ on MacOS): Toggle comment\r\n * - Shift+Alt+A: Toggle block comment\r\n * - Ctrl+M (Ctrl+Shift+M on MacOS): Toggle Tab capturing\r\n * \r\n * The shortcuts for the commands are not easily customizable. If you want to customize\r\n * them, you can copy the {@link https://github.com/FIameCaster/prism-code-editor/blob/main/package/src/extensions/commands.ts|source}\r\n * and change the conditions.\r\n *\r\n * @param selfClosePairs Pairs of self-closing brackets and quotes.\r\n * Must be an array of strings with 2 characters each.\r\n * Defaults to `['\"\"', \"''\", '``', '()', '[]', '{}']`.\r\n * @param selfCloseRegex Regex controlling whether or not a bracket/quote should\r\n * automatically close based on the character before and after the cursor.\r\n * Defaults to ``/([^$\\w'\"`][\"'`]|.[[({])[.,:;\\])}>\\s]|.[[({]`/s``.\r\n */\r\nconst defaultCommands =\r\n\t(\r\n\t\tselfClosePairs = ['\"\"', \"''\", \"``\", \"()\", \"[]\", \"{}\"],\r\n\t\tselfCloseRegex = /([^$\\w'\"`][\"'`]|.[[({])[.,:;\\])}>\\s]|.[[({]`/s,\r\n\t): BasicExtension =>\r\n\t(editor, options) => {\r\n\t\tlet prevCopy: string\r\n\t\tconst { keyCommandMap, inputCommandMap, getSelection, scrollContainer } = editor\r\n\r\n\t\tconst getIndent = ({ insertSpaces = true, tabSize } = options) =>\r\n\t\t\t[insertSpaces ? \" \" : \"\\t\", insertSpaces ? tabSize || 2 : 1] as const\r\n\r\n\t\tconst scroll = () => !options.readOnly && !editor.extensions.cursor?.scrollIntoView()\r\n\r\n\t\t/**\r\n\t\t * Automatically closes quotes and brackets if text is selected,\r\n\t\t * or if the character before and after the cursor matches a regex\r\n\t\t * @param wrapOnly If true, the character will only be closed if text is selected.\r\n\t\t */\r\n\t\tconst selfClose = (\r\n\t\t\t[start, end]: InputSelection,\r\n\t\t\t[open, close]: string,\r\n\t\t\tvalue: string,\r\n\t\t\twrapOnly?: boolean,\r\n\t\t) =>\r\n\t\t\t(start < end ||\r\n\t\t\t\t(!wrapOnly && selfCloseRegex.test((value[end - 1] || \" \") + open + (value[end] || \" \")))) &&\r\n\t\t\t!insertText(editor, open + value.slice(start, end) + close, null, null, start + 1, end + 1)!\r\n\r\n\t\tconst skipIfEqual = ([start, end]: InputSelection, char: string, value: string) =>\r\n\t\t\tstart == end && value[end] == char && !editor.setSelection(start + 1)!\r\n\r\n\t\t/**\r\n\t\t * Inserts slightly altered lines while keeping the same selection.\r\n\t\t * Used when toggling comments and indenting.\r\n\t\t */\r\n\t\tconst insertLines = (\r\n\t\t\told: string[],\r\n\t\t\tnewL: string[],\r\n\t\t\tstart: number,\r\n\t\t\tend: number,\r\n\t\t\tselectionStart: number,\r\n\t\t\tselectionEnd: number,\r\n\t\t) => {\r\n\t\t\tlet newLines = newL.join(\"\\n\")\r\n\t\t\tif (newLines != old.join(\"\\n\")) {\r\n\t\t\t\tconst last = old.length - 1\r\n\t\t\t\tconst lastLine = newL[last]\r\n\t\t\t\tconst oldLastLine = old[last]\r\n\t\t\t\tconst lastDiff = oldLastLine.length - lastLine.length\r\n\t\t\t\tconst firstDiff = newL[0].length - old[0].length\r\n\t\t\t\tconst firstInsersion = start + whitespaceEnd((firstDiff < 0 ? newL : old)[0])\r\n\t\t\t\tconst lastInsersion =\r\n\t\t\t\t\tend - oldLastLine.length + whitespaceEnd(lastDiff > 0 ? lastLine : oldLastLine)\r\n\t\t\t\tconst offset = start - end + newLines.length + lastDiff\r\n\t\t\t\tconst newCursorStart =\r\n\t\t\t\t\tfirstInsersion > selectionStart\r\n\t\t\t\t\t\t? selectionStart\r\n\t\t\t\t\t\t: Math.max(firstInsersion, selectionStart + firstDiff)\r\n\t\t\t\tconst newCursorEnd = selectionEnd + start - end + newLines.length\r\n\t\t\t\tinsertText(\r\n\t\t\t\t\teditor,\r\n\t\t\t\t\tnewLines,\r\n\t\t\t\t\tstart,\r\n\t\t\t\t\tend,\r\n\t\t\t\t\tnewCursorStart,\r\n\t\t\t\t\tselectionEnd < lastInsersion\r\n\t\t\t\t\t\t? newCursorEnd + lastDiff\r\n\t\t\t\t\t\t: Math.max(lastInsersion + offset, newCursorEnd),\r\n\t\t\t\t)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst indent = (\r\n\t\t\toutdent: boolean,\r\n\t\t\tlines: string[],\r\n\t\t\tstart1: number,\r\n\t\t\tend1: number,\r\n\t\t\tstart: number,\r\n\t\t\tend: number,\r\n\t\t\tindentChar: string,\r\n\t\t\ttabSize: number,\r\n\t\t) => {\r\n\t\t\tinsertLines(\r\n\t\t\t\tlines,\r\n\t\t\t\tlines.map(\r\n\t\t\t\t\toutdent\r\n\t\t\t\t\t\t? str => str.slice(whitespaceEnd(str) ? tabSize - (whitespaceEnd(str) % tabSize) : 0)\r\n\t\t\t\t\t\t: str => str && indentChar.repeat(tabSize - (whitespaceEnd(str) % tabSize)) + str,\r\n\t\t\t\t),\r\n\t\t\t\tstart1,\r\n\t\t\t\tend1,\r\n\t\t\t\tstart,\r\n\t\t\t\tend,\r\n\t\t\t)\r\n\t\t}\r\n\r\n\t\tinputCommandMap[\"<\"] = (_e, selection, value) => selfClose(selection, \"<>\", value, true)\r\n\r\n\t\tselfClosePairs.forEach(([open, close]) => {\r\n\t\t\tconst isQuote = open == close\r\n\t\t\tinputCommandMap[open] = (_e, selection, value) =>\r\n\t\t\t\t((isQuote && skipIfEqual(selection, close, value)) ||\r\n\t\t\t\t\tselfClose(selection, open + close, value)) &&\r\n\t\t\t\tscroll()\r\n\t\t\tif (!isQuote)\r\n\t\t\t\tinputCommandMap[close] = (_e, selection, value) =>\r\n\t\t\t\t\tskipIfEqual(selection, close, value) && scroll()\r\n\t\t})\r\n\r\n\t\tinputCommandMap[\">\"] = (e, selection, value) => {\r\n\t\t\tconst closingTag = languageMap[getLanguage(editor)]?.autoCloseTags?.(selection, value, editor)\r\n\t\t\tif (closingTag) {\r\n\t\t\t\tinsertText(editor, \">\" + closingTag, null, null, selection[0] + 1)\r\n\t\t\t\tpreventDefault(e)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tkeyCommandMap.Tab = (e, [start, end], value) => {\r\n\t\t\tif (ignoreTab || options.readOnly || getModifierCode(e) & 6) return\r\n\t\t\tconst [indentChar, tabSize] = getIndent(options)\r\n\t\t\tconst shiftKey = e.shiftKey\r\n\t\t\tconst [lines, start1, end1] = getLines(value, start, end)\r\n\t\t\tif (start < end || shiftKey) {\r\n\t\t\t\tindent(shiftKey, lines, start1, end1, start, end, indentChar, tabSize)\r\n\t\t\t} else insertText(editor, indentChar.repeat(tabSize - ((start - start1) % tabSize)))\r\n\t\t\treturn scroll()\r\n\t\t}\r\n\r\n\t\tkeyCommandMap.Enter = (e, selection, value) => {\r\n\t\t\tconst code = getModifierCode(e) & 7\r\n\t\t\tif (!code || code == mod) {\r\n\t\t\t\tif (code) selection[0] = selection[1] = getLines(value, selection[1])[2]\r\n\t\t\t\tconst [indentChar, tabSize] = getIndent()\r\n\t\t\t\tconst [start, end] = selection\r\n\t\t\t\tconst autoIndent = languageMap[getLanguage(editor)]?.autoIndent\r\n\t\t\t\tconst indenationCount =\r\n\t\t\t\t\tMath.floor(whitespaceEnd(getLineBefore(value, start)) / tabSize) * tabSize\r\n\t\t\t\tconst extraIndent = autoIndent?.[0]?.(selection, value, editor) ? tabSize : 0\r\n\t\t\t\tconst extraLine = autoIndent?.[1]?.(selection, value, editor)\r\n\t\t\t\tconst newText =\r\n\t\t\t\t\t\"\\n\" +\r\n\t\t\t\t\tindentChar.repeat(indenationCount + extraIndent) +\r\n\t\t\t\t\t(extraLine ? \"\\n\" + indentChar.repeat(indenationCount) : \"\")\r\n\r\n\t\t\t\tif (newText[1] || value[end]) {\r\n\t\t\t\t\tinsertText(editor, newText, start, end, start + indenationCount + extraIndent + 1)\r\n\t\t\t\t\treturn scroll()\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tkeyCommandMap.Backspace = (_e, [start, end], value) => {\r\n\t\t\tif (start == end) {\r\n\t\t\t\tconst line = getLineBefore(value, start)\r\n\t\t\t\tconst tabSize = options.tabSize || 2\r\n\t\t\t\tconst isPair = selfClosePairs.includes(value.slice(start - 1, start + 1))\r\n\t\t\t\tconst indenationCount = /[^ ]/.test(line) ? 0 : ((line.length - 1) % tabSize) + 1\r\n\r\n\t\t\t\tif (isPair || indenationCount > 1) {\r\n\t\t\t\t\tinsertText(editor, \"\", start - (isPair ? 1 : indenationCount), start + isPair)\r\n\t\t\t\t\treturn scroll()\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (let i = 0; i < 2; i++) {\r\n\t\t\tkeyCommandMap[i ? \"ArrowDown\" : \"ArrowUp\"] = (e, [start, end], value) => {\r\n\t\t\t\tconst code = getModifierCode(e)\r\n\r\n\t\t\t\tif (code == 1) {\r\n\t\t\t\t\t// Moving lines\r\n\t\t\t\t\tconst newStart = i ? start : getLineStart(value, start) - 1\r\n\t\t\t\t\tconst newEnd = i ? value.indexOf(\"\\n\", end) + 1 : end\r\n\t\t\t\t\tif (newStart > -1 && newEnd > 0) {\r\n\t\t\t\t\t\tconst [lines, start1, end1] = getLines(value, newStart, newEnd)\r\n\t\t\t\t\t\tconst line = lines[i ? \"pop\" : \"shift\"]()!\r\n\t\t\t\t\t\tconst offset = (line.length + 1) * (i ? 1 : -1)\r\n\r\n\t\t\t\t\t\tlines[i ? \"unshift\" : \"push\"](line)\r\n\t\t\t\t\t\tinsertText(editor, lines.join(\"\\n\"), start1, end1, start + offset, end + offset)\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn scroll()\r\n\t\t\t\t} else if (code == 9) {\r\n\t\t\t\t\t// Copying lines\r\n\t\t\t\t\tconst [lines, start1, end1] = getLines(value, start, end)\r\n\t\t\t\t\tconst str = lines.join(\"\\n\")\r\n\t\t\t\t\tconst offset = i ? str.length + 1 : 0\r\n\t\t\t\t\tinsertText(editor, str + \"\\n\" + str, start1, end1, start + offset, end + offset)\r\n\t\t\t\t\treturn scroll()\r\n\t\t\t\t} else if (code == 2 && !isMac) {\r\n\t\t\t\t\tscrollContainer.scrollBy(0, getStyleValue(scrollContainer, \"lineHeight\") * (i ? 1 : -1))\r\n\t\t\t\t\treturn true\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\taddTextareaListener(editor, \"keydown\", e => {\r\n\t\t\tconst code = getModifierCode(e)\r\n\t\t\tconst keyCode = e.keyCode\r\n\t\t\tconst [start, end, dir] = getSelection()\r\n\r\n\t\t\tif (code == mod && (keyCode == 221 || keyCode == 219)) {\r\n\t\t\t\tindent(keyCode == 219, ...getLines(editor.value, start, end), start, end, ...getIndent())\r\n\t\t\t\tscroll()\r\n\t\t\t\tpreventDefault(e)\r\n\t\t\t} else if (code == (isMac ? 0b1010 : 0b0010) && keyCode == 77) {\r\n\t\t\t\tsetIgnoreTab(!ignoreTab)\r\n\t\t\t\tpreventDefault(e)\r\n\t\t\t} else if ((keyCode == 191 && code == mod) || (keyCode == 65 && code == 9)) {\r\n\t\t\t\tconst value = editor.value\r\n\t\t\t\tconst isBlock = code == 9\r\n\t\t\t\tconst position = isBlock ? start : getLineStart(value, start)\r\n\t\t\t\tconst language = languageMap[getLanguage(editor, position)] || {}\r\n\t\t\t\tconst { line, block } =\r\n\t\t\t\t\tlanguage.getComments?.(editor, position, value) || language.comments || {}\r\n\t\t\t\tconst [lines, start1, end1] = getLines(value, start, end)\r\n\t\t\t\tconst last = lines.length - 1\r\n\r\n\t\t\t\tif (isBlock) {\r\n\t\t\t\t\tif (block) {\r\n\t\t\t\t\t\tconst [open, close] = block\r\n\t\t\t\t\t\tconst text = value.slice(start, end)\r\n\t\t\t\t\t\tconst pos = value.slice(0, start).search(regexEscape(open) + \" ?$\")\r\n\t\t\t\t\t\tconst matches = RegExp(\"^ ?\" + regexEscape(close)).test(value.slice(end))\r\n\r\n\t\t\t\t\t\tif (pos + 1 && matches)\r\n\t\t\t\t\t\t\tinsertText(\r\n\t\t\t\t\t\t\t\teditor,\r\n\t\t\t\t\t\t\t\ttext,\r\n\t\t\t\t\t\t\t\tpos,\r\n\t\t\t\t\t\t\t\tend + +(value[end] == \" \") + close.length,\r\n\t\t\t\t\t\t\t\tpos,\r\n\t\t\t\t\t\t\t\tpos + end - start,\r\n\t\t\t\t\t\t\t)\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\tinsertText(\r\n\t\t\t\t\t\t\t\teditor,\r\n\t\t\t\t\t\t\t\t`${open} ${text} ${close}`,\r\n\t\t\t\t\t\t\t\tstart,\r\n\t\t\t\t\t\t\t\tend,\r\n\t\t\t\t\t\t\t\tstart + open.length + 1,\r\n\t\t\t\t\t\t\t\tend + open.length + 1,\r\n\t\t\t\t\t\t\t)\r\n\t\t\t\t\t\tscroll()\r\n\t\t\t\t\t\tpreventDefault(e)\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (line) {\r\n\t\t\t\t\t\tconst escaped = regexEscape(line)\r\n\t\t\t\t\t\tconst regex = RegExp(`^\\\\s*(${escaped} ?|$)`)\r\n\t\t\t\t\t\tconst regex2 = RegExp(escaped + \" ?\")\r\n\t\t\t\t\t\tconst allWhiteSpace = !/\\S/.test(value.slice(start1, end1))\r\n\t\t\t\t\t\tconst newLines = lines.map(\r\n\t\t\t\t\t\t\tlines.every(line => regex.test(line)) && !allWhiteSpace\r\n\t\t\t\t\t\t\t\t? str => str.replace(regex2, \"\")\r\n\t\t\t\t\t\t\t\t: str =>\r\n\t\t\t\t\t\t\t\t\t\tallWhiteSpace || /\\S/.test(str) ? str.replace(/^\\s*/, `$&${line} `) : str,\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t\tinsertLines(lines, newLines, start1, end1, start, end)\r\n\t\t\t\t\t\tscroll()\r\n\t\t\t\t\t\tpreventDefault(e)\r\n\t\t\t\t\t} else if (block) {\r\n\t\t\t\t\t\tconst [open, close] = block\r\n\t\t\t\t\t\tconst insertionPoint = whitespaceEnd(lines[0])\r\n\t\t\t\t\t\tconst hasComment =\r\n\t\t\t\t\t\t\tlines[0].startsWith(open, insertionPoint) && lines[last].endsWith(close)\r\n\t\t\t\t\t\tconst newLines = lines.slice()\r\n\r\n\t\t\t\t\t\tnewLines[0] = lines[0].replace(\r\n\t\t\t\t\t\t\thasComment ? RegExp(regexEscape(open) + \" ?\") : /(?=\\S)|$/,\r\n\t\t\t\t\t\t\thasComment ? \"\" : open + \" \",\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t\tlet diff = newLines[0].length - lines[0].length\r\n\t\t\t\t\t\tnewLines[last] = hasComment\r\n\t\t\t\t\t\t\t? newLines[last].replace(RegExp(`( ?${regexEscape(close)})?$`), \"\")\r\n\t\t\t\t\t\t\t: newLines[last] + \" \" + close\r\n\r\n\t\t\t\t\t\tlet newText = newLines.join(\"\\n\")\r\n\t\t\t\t\t\tlet firstInsersion = insertionPoint + start1\r\n\t\t\t\t\t\tlet newStart = firstInsersion > start ? start : Math.max(start + diff, firstInsersion)\r\n\t\t\t\t\t\tlet newEnd =\r\n\t\t\t\t\t\t\tfirstInsersion > end - (start != end)\r\n\t\t\t\t\t\t\t\t? end\r\n\t\t\t\t\t\t\t\t: Math.min(Math.max(firstInsersion, end + diff), start1 + newText.length)\r\n\t\t\t\t\t\tinsertText(editor, newText, start1, end1, newStart, Math.max(newStart, newEnd))\r\n\t\t\t\t\t\tscroll()\r\n\t\t\t\t\t\tpreventDefault(e)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else if (code == 8 + mod && keyCode == 75) {\r\n\t\t\t\tconst value = editor.value\r\n\t\t\t\tconst [lines, start1, end1] = getLines(value, start, end)\r\n\t\t\t\tconst column = dir > \"f\" ? end - end1 + lines.pop()!.length : start - start1\r\n\t\t\t\tconst newLineLen = getLineEnd(value, end1 + 1) - end1 - 1\r\n\t\t\t\tinsertText(\r\n\t\t\t\t\teditor,\r\n\t\t\t\t\t\"\",\r\n\t\t\t\t\tstart1 - !!start1,\r\n\t\t\t\t\tend1 + !start1,\r\n\t\t\t\t\tstart1 + Math.min(column, newLineLen),\r\n\t\t\t\t)\r\n\t\t\t\tscroll()\r\n\t\t\t\tpreventDefault(e)\r\n\t\t\t}\r\n\t\t})\r\n\t\t;([\"copy\", \"cut\", \"paste\"] as const).forEach(type =>\r\n\t\t\taddTextareaListener(editor, type, e => {\r\n\t\t\t\tconst [start, end] = getSelection()\r\n\t\t\t\tif (start == end && clipboard) {\r\n\t\t\t\t\tconst [[line], start1, end1] = getLines(editor.value, start, end)\r\n\t\t\t\t\tif (type == \"paste\") {\r\n\t\t\t\t\t\tif (e.clipboardData!.getData(\"text/plain\") == prevCopy) {\r\n\t\t\t\t\t\t\tinsertText(editor, prevCopy + \"\\n\", start1, start1, start + prevCopy.length + 1)\r\n\t\t\t\t\t\t\tscroll()\r\n\t\t\t\t\t\t\tpreventDefault(e)\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tclipboard.writeText((prevCopy = line))\r\n\t\t\t\t\t\tif (type == \"cut\") insertText(editor, \"\", start1, end1 + 1), scroll()\r\n\t\t\t\t\t\tpreventDefault(e)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}),\r\n\t\t)\r\n\t}\r\n\r\nexport interface EditHistory extends BasicExtension {\r\n\t/** Clears the history stack. Probably necessary after changing the value of the editor. */\r\n\tclear(): void\r\n\t/**\r\n\t * Sets the active entry relative to the current entry.\r\n\t *\r\n\t * @param offset The position you want to move to relative to the current entry.\r\n\t *\r\n\t * `EditHistory.go(-1)` would be equivalent to an undo while `EditHistory.go(1)` would\r\n\t * be equivalent to a redo.\r\n\t *\r\n\t * If there's no entry at the specified offset, the call does nothing.\r\n\t */\r\n\tgo(offset: number): void\r\n\t/**\r\n\t * Returns whether or not there exists a history entry at the specified offset relative\r\n\t * to the current entry.\r\n\t *\r\n\t * This method can be used to determine whether a call to {@link EditHistory.go} with the\r\n\t * same offset will succeed or do nothing.\r\n\t */\r\n\thas(offset: number): boolean\r\n}\r\n\r\n/**\r\n * History extension that overrides the undo/redo behavior of the browser.\r\n *\r\n * Without this extension, the browser's native undo/redo is used, which can be sufficient\r\n * in some cases.\r\n *\r\n * It should be noted that the history stack is not automatically cleared when the editors\r\n * value is changed programmatically using `editor.setOptions` Instead you can clear the\r\n * stack any time using {@link EditHistory.clear}.\r\n *\r\n * Once added to an editor, this extension can be accessed from `editor.extensions.history`.\r\n *\r\n * If you want to create a new editor with different extensions while keeping the undo/redo\r\n * history of an old editor, you can! Just add the old editor's history extension instance\r\n * to the new editor. Keep in mind that this will fully break the undo/redo behavior of the\r\n * old editor.\r\n *\r\n * @param historyLimit The maximum size of the history stack. Defaults to 999.\r\n */\r\nconst editHistory = (historyLimit = 999) => {\r\n\tlet sp = 0\r\n\tlet currentEditor: PrismEditor\r\n\tlet allowMerge: boolean\r\n\tlet isTyping = false\r\n\tlet prevInputType: string\r\n\tlet prevData: string | null\r\n\tlet prevTime: number\r\n\tlet isMerge: boolean\r\n\tlet textarea: HTMLTextAreaElement\r\n\tlet getSelection: PrismEditor[\"getSelection\"]\r\n\r\n\tconst stack: [string, InputSelection, InputSelection][] = []\r\n\tconst update = (index: number) => {\r\n\t\tif (index >= historyLimit) {\r\n\t\t\tindex--\r\n\t\t\tstack.shift()\r\n\t\t}\r\n\t\tstack.splice((sp = index), historyLimit, [currentEditor.value, getSelection(), getSelection()])\r\n\t}\r\n\tconst setEditorState = (index: number) => {\r\n\t\tif (stack[index]) {\r\n\t\t\ttextarea.value = stack[index][0]\r\n\t\t\ttextarea.setSelectionRange(...stack[index][index < sp ? 2 : 1])\r\n\t\t\tcurrentEditor.update()\r\n\t\t\tcurrentEditor.extensions.cursor?.scrollIntoView()\r\n\t\t\tsp = index\r\n\t\t\tallowMerge = false\r\n\t\t}\r\n\t}\r\n\r\n\tconst self: EditHistory = (editor, options) => {\r\n\t\teditor.extensions.history = self\r\n\t\tcurrentEditor = editor\r\n\t\tgetSelection = editor.getSelection\r\n\t\ttextarea || update(0)\r\n\t\ttextarea = editor.textarea\r\n\r\n\t\teditor.addListener(\"selectionChange\", () => {\r\n\t\t\tallowMerge = isTyping\r\n\t\t\tisTyping = false\r\n\t\t})\r\n\r\n\t\taddTextareaListener(editor, \"beforeinput\", e => {\r\n\t\t\tlet data = e.data\r\n\t\t\tlet inputType = e.inputType\r\n\t\t\tlet time = e.timeStamp\r\n\r\n\t\t\tif (/history/.test(inputType)) {\r\n\t\t\t\tsetEditorState(sp + (inputType[7] == \"U\" ? -1 : 1))\r\n\t\t\t\tpreventDefault(e)\r\n\t\t\t} else if (\r\n\t\t\t\t!(isMerge =\r\n\t\t\t\t\tallowMerge &&\r\n\t\t\t\t\t(prevInputType == inputType || (time - prevTime < 99 && inputType.slice(-4) == \"Drop\")) &&\r\n\t\t\t\t\t!prevSelection &&\r\n\t\t\t\t\t(data != \" \" || prevData == data))\r\n\t\t\t) {\r\n\t\t\t\tstack[sp][2] = prevSelection || getSelection()\r\n\t\t\t}\r\n\t\t\tisTyping = true\r\n\t\t\tprevData = data\r\n\t\t\tprevTime = time\r\n\t\t\tprevInputType = inputType\r\n\t\t})\r\n\t\taddTextareaListener(editor, \"input\", () => update(sp + !isMerge))\r\n\t\taddTextareaListener(editor, \"keydown\", e => {\r\n\t\t\tif (!options.readOnly) {\r\n\t\t\t\tconst code = getModifierCode(e)\r\n\t\t\t\tconst keyCode = e.keyCode\r\n\t\t\t\tconst isUndo = code == mod && keyCode == 90\r\n\t\t\t\tconst isRedo =\r\n\t\t\t\t\t(code == mod + 8 && keyCode == 90) || (!isMac && code == mod && keyCode == 89)\r\n\t\t\t\tif (isUndo) {\r\n\t\t\t\t\tsetEditorState(sp - 1)\r\n\t\t\t\t\tpreventDefault(e)\r\n\t\t\t\t} else if (isRedo) {\r\n\t\t\t\t\tsetEditorState(sp + 1)\r\n\t\t\t\t\tpreventDefault(e)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n\r\n\tself.clear = () => {\r\n\t\tupdate(0)\r\n\t\tallowMerge = false\r\n\t}\r\n\r\n\tself.has = offset => sp + offset in stack\r\n\tself.go = offset => setEditorState(sp + offset)\r\n\r\n\treturn self\r\n}\r\n\r\nexport { defaultCommands, setIgnoreTab, ignoreTab, editHistory }\r\n"],"names":["line"],"mappings":"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"} \ No newline at end of file diff --git a/pkg-r/inst/js/prism-code-editor/extensions/copyButton/index.d.ts b/pkg-r/inst/js/prism-code-editor/extensions/copyButton/index.d.ts new file mode 100644 index 00000000..88e42dff --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/extensions/copyButton/index.d.ts @@ -0,0 +1,8 @@ +import { BasicExtension } from '../../types.js'; + +/** + * Extension that adds a copy button to the editor. + * Probably best used with a read-only editor. + * You must also import styles from `prism-code-editor/copy-button.css`. + */ +export declare const copyButton: () => BasicExtension; diff --git a/pkg-r/inst/js/prism-code-editor/extensions/copyButton/index.js b/pkg-r/inst/js/prism-code-editor/extensions/copyButton/index.js new file mode 100644 index 00000000..b0376ad8 --- /dev/null +++ b/pkg-r/inst/js/prism-code-editor/extensions/copyButton/index.js @@ -0,0 +1,24 @@ +import { a as createTemplate } from "../../index-BltwYS88.js"; +const template = createTemplate( + '