From 4d54e692146aa605b290c3bc1d1b9ab6188046bf Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 14 Oct 2025 23:39:40 -0400 Subject: [PATCH 01/32] chore: initial plan --- btw.md | 688 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 688 insertions(+) create mode 100644 btw.md diff --git a/btw.md b/btw.md new file mode 100644 index 00000000..292809ed --- /dev/null +++ b/btw.md @@ -0,0 +1,688 @@ +--- +client: aws_bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0 +tools: + - docs + # - env + - files + # - ide + # - search + # - session + - web +--- + +Follow these important style rules when writing R code: + +* Prefer solutions that use {tidyverse} +* Always use `<-` for assignment +* Always use the native base-R pipe `|>` for piped expressions + +--- + +# Code Editor Component Implementation Plan + +## Overview + +We are adding a lightweight, language-agnostic code editor component to the querychat R package, with initial focus on SQL. The editor will use [Prism Code Editor](https://prism-code-editor.netlify.app/), a minimal alternative to Monaco/CodeMirror, suitable for displaying code in documentation, forms, and interactive applications. + +**Key Requirements:** +- Language-agnostic with SQL as priority +- Bidirectional R ↔ JavaScript communication (update from R, send changes to R) +- Flexible syntax highlighting with separate light/dark themes +- Automatic theme switching based on Bootstrap 5 `data-bs-theme` attribute +- Copy-to-clipboard button +- Update triggers: blur or Ctrl/Cmd+Enter +- All options updatable from server +- Autocomplete deferred to Phase 2 + +## Component Naming & API + +**Input Component:** `input_code_editor(id, code = "", language = "sql", ...)` +**Update Function:** `update_code_editor(session, id, code, language, theme_light, theme_dark, ...)` + +This naming: +- Follows Shiny convention (`input_*`, `update_*`) +- Makes language-agnostic nature explicit +- Leaves room for future `output_code_editor()` if needed for read-only display + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ R Package │ +│ │ +│ input_code_editor(id, code, language, theme_light/dark) │ +│ ↓ │ +│ Creates
with data attributes │ +│ Attaches html_dependency_code_editor() │ +│ ↓ │ +└──────────────┼───────────────────────────────────────────────┘ + │ + ↓ Sent to browser + +┌─────────────────────────────────────────────────────────────┐ +│ Browser (JavaScript) │ +│ │ +│ Shiny Input Binding detects
│ +│ ↓ │ +│ Creates PrismEditor instance with: │ +│ - Prism language grammar (sql/javascript/python/etc.) │ +│ - Copy button extension │ +│ - Default commands (undo, redo, indent) │ +│ - Theme CSS based on current data-bs-theme │ +│ ↓ │ +│ Listens to: │ +│ - blur event → triggers input value update to R │ +│ - Ctrl/Cmd+Enter → triggers input value update to R │ +│ - data-bs-theme mutations → switches theme CSS │ +│ ↓ │ +│ Sends current code value to Shiny input system │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Phase 1: Dependency Management & Bundling + +### 1.1 Set Up npm Workflow + +**Create:** `package.json` + +Configure npm to install `prism-code-editor` and use `cpy-cli` to copy necessary files into `pkg-r/inst/js/prism-code-editor/`. + +**Required files from prism-code-editor:** +- Core: `prism-code-editor.js`, `layout.css` +- Language grammars: `languages/sql.js`, `languages/javascript.js`, `languages/python.js`, `languages/r.js` +- Extensions: `copy-button.js`, `copy-button.css`, `commands.js` +- Themes: All themes from `themes/` directory (we'll load them dynamically) + +**npm scripts needed:** +- `install-deps`: Install prism-code-editor +- `copy-deps`: Use cpy-cli to copy files from `node_modules/prism-code-editor/` to `pkg-r/inst/js/prism-code-editor/` +- `update-deps`: Combined install and copy + +**Directory structure after bundling:** +``` +pkg-r/inst/js/prism-code-editor/ +├── prism-code-editor.js +├── layout.css +├── languages/ +│ ├── sql.js +│ ├── javascript.js +│ ├── python.js +│ └── r.js +├── extensions/ +│ ├── copy-button.js +│ ├── copy-button.css +│ └── commands.js +└── themes/ + ├── github-light.css + ├── github-dark.css + ├── vs-code-light.css + ├── vs-code-dark.css + └── ... (all other themes) +``` + +### 1.2 Create JavaScript Input Binding + +**Create:** `pkg-r/inst/js/code-editor-binding.js` + +This file implements the Shiny input binding for the code editor using the standard Shiny input binding pattern. + +**Binding Structure:** + +The binding should follow Shiny's `InputBinding` interface with these key methods: + +**`find(scope)`**: Return all elements with class `.code-editor-input` within scope. + +**`getValue(el)`**: Return the current code content from the PrismEditor instance stored on the element. If the editor hasn't been initialized yet, return the value from the `data-initial-code` attribute. + +**`setValue(el, value)`**: Update the editor's content without triggering reactivity. Used for bookmark restoration. Should call `editor.setOptions({ value })` if editor exists. + +**`receiveMessage(el, data)`**: Handle messages from `session$sendInputMessage()`. The `data` object may contain: +- `code`: New code content +- `language`: New language (requires re-initialization or language switching) +- `theme_light`: New light theme name +- `theme_dark`: New dark theme name +- `read_only`: Boolean for read-only mode +- `line_numbers`: Boolean for line numbers display +- `word_wrap`: Boolean for word wrap +- `tab_size`: Number for tab size (default: 2) +- `indentation`: Enum for using `spaces` vs `tabs` (default: spaces) + +When `receiveMessage()` updates values, it should call `editor.setOptions()` with the new configuration. If the update should trigger reactivity (rare), dispatch a change event. + +**`subscribe(el, callback)`**: Set up event listeners for when the input value should be sent to R. Listen to: +1. Custom `codeEditorUpdate` event on the element +2. Should use `callback(true)` to enable rate policy (debouncing) + +**`getRatePolicy()`**: Return `{ policy: 'debounce', delay: 300 }` to avoid excessive updates during typing. + +**`unsubscribe(el)`**: Clean up event listeners using namespace to avoid memory leaks. + +References (consult as needed): + +* https://shiny.rstudio.com/articles/building-inputs.html +* https://github.com/gadenbuie/js4shiny/blob/main/inst/snippets/javascript.snippets#L16 + +**Initialization Logic:** + +The binding needs an initialization function (called on first `find()` or lazily on first interaction) that: + +1. Reads configuration from `data-*` attributes on the element +2. Dynamically imports the required language grammar file if not already loaded +3. Creates the PrismEditor instance using `createEditor()` from the Prism Code Editor library +4. Adds extensions: `copyButton()` and `defaultCommands()` +5. Stores the editor instance on the element (e.g., `el.prismEditor`) for later access +6. Sets up event listeners: + - Blur event on editor's textarea → dispatch `codeEditorUpdate` event + - Ctrl/Cmd+Enter keyboard shortcut → dispatch `codeEditorUpdate` event +7. Returns the created editor + +**Theme Management:** + +Create a separate initialization function that sets up Bootstrap theme watching: + +1. Create a `MutationObserver` on the `` element watching for `data-bs-theme` attribute changes +2. When the attribute changes, call a function to load the appropriate theme: + - If `data-bs-theme="light"` or empty/missing → load `theme_light` + - If `data-bs-theme="dark"` → load `theme_dark` +3. Theme loading should: + - Create or update a `` element in document head with id `code-editor-theme-{inputId}` + - Set `href` to the theme CSS file path from the htmldep folder + - Remove the old theme link (if exists) after new one loads to prevent flash +4. Call this function once on initialization to set the initial theme + +**Language Grammar Loading:** + +Maintain a global Set of loaded language names. Before creating an editor: + +1. Check if language is in the loaded set +2. If not, dynamically import using: `import('/path/to/languages/{language}.js')` +3. Wait for the import promise to resolve +4. Add language to the loaded set +5. Proceed with editor creation + +**Key Technical Notes:** + +- The editor instance must be stored on the DOM element for access by other binding methods +- Update triggers should dispatch a custom `codeEditorUpdate` event rather than direct callback invocation +- The `subscribe()` method listens for this custom event and calls the Shiny callback +- When language changes in `receiveMessage()`, may need to recreate the editor or call `editor.setOptions({ language })` and manually retokenize +- Theme switching should not trigger reactivity +- Use jQuery's namespaced events (`.codeEditorBinding`) for easy cleanup + +### 1.3 Base Styling + +**Create:** `pkg-r/inst/js/code-editor.css` + +Provide minimal CSS for Bootstrap 5 integration: + +- Container border and border-radius to match Bootstrap form controls +- Focus styling (border color + box-shadow) matching Bootstrap input focus +- Proper `display: grid` on container (required by Prism layout) +- Responsive height handling +- Light/dark mode border color adjustments using `[data-bs-theme]` selector +- Z-index management so copy button appears above code but below modals + +**Do not override Prism's theme CSS** - those provide syntax highlighting colors. Our CSS only handles the "chrome" around the editor. + +## Phase 2: R Package Integration + +### 2.1 HTML Dependency Function + +**Create:** `pkg-r/R/input_code_editor.R` + +**Function:** `html_dependency_code_editor()` + +Returns an `htmltools::htmlDependency()` object that bundles: +- `prism-code-editor.js` (core library) +- `layout.css` (required layout styles) +- `copy-button.js`, `copy-button.css` +- `commands.js` +- `code-editor-binding.js` (our custom binding) +- `code-editor.css` (our integration styles) + +**Important:** Do NOT include language files or theme files in the base dependency. These will be loaded dynamically by the JavaScript binding based on the `language` and `theme_*` options. + +**Versioning:** Use the version from `prism-code-editor` package.json. + +### 2.2 Input UI Function + +**Create:** `pkg-r/R/input_code_editor.R` + +**Function signature:** +```r +input_code_editor( + id, + code = "", + language = "sql", + height = "300px", + width = "100%", + theme_light = "github-light", + theme_dark = "github-dark", + placeholder = NULL, + read_only = FALSE, + line_numbers = TRUE, + word_wrap = FALSE, + tab_size = 2, + indentation = c("space", "tab") +) +``` + +**Returns:** An `htmltools::tagList()` containing: +1. `html_dependency_code_editor()` dependency +2. A `
` with: + - `id`: Namespaced ID + - `class`: `"code-editor-input"` + - `style`: Height, width, `display: grid` + - `data-language`: The programming language + - `data-initial-code`: The initial code content (HTML-escaped) + - `data-theme-light`: Light theme name + - `data-theme-dark`: Dark theme name + - `data-read-only`: Boolean as string + - `data-line-numbers`: Boolean as string + - `data-word-wrap`: Boolean as string + - `data-tab-size`: Number as string + - `data-insert-spaces`: Boolean as string + - `data-placeholder`: Placeholder text (if provided) + +**Module support:** If `id` contains a namespace (from `shiny::NS()`), it's preserved. The binding must use the full namespaced ID. + +**Validation:** Check that `language` is one of supported languages. Initially support: `"sql"`, `"javascript"`, `"python"`, `"r"`. Return informative error if unsupported language provided. + +### 2.3 Update Function + +**Create:** Same file as 2.2 + +**Function signature:** +```r +update_code_editor( + id, + code = NULL, + ..., # ignored, included to require named arguments + 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() +) +``` + +**Behavior:** +- Sends custom message to JavaScript using `session$sendInputMessage()` +- Message payload: Named list with only non-NULL values +- JavaScript input binding receives this and updates editor options + +**Validation:** If `language` is provided, validate it's supported. + +## Phase 3: Theme Management + +### 3.1 Theme System Design + +Prism Code Editor ships with 14 built-in themes. We need a system that: + +1. **Discovers available themes** at package build time +2. **Validates theme names** when user specifies them +3. **Dynamically loads theme CSS** in the browser based on `data-bs-theme` + +**Approach:** + +**R side:** +- Helper function `code_editor_themes()` that lists available themes by reading the `themes/` directory in the bundled htmldep folder +- Validation function used by `input_code_editor()` and `update_code_editor()` to check theme names + +**JavaScript side:** +- Theme CSS is loaded as `` elements in document head +- Use `importmap` or dynamic imports to load theme files from the htmldep folder +- Theme file path pattern: `{htmldep_path}/themes/{theme_name}.css` + +### 3.2 Automatic Theme Switching + +**Implementation in code-editor-binding.js:** + +On initialization: +1. Create a `MutationObserver` that watches the `` element's attributes +2. When `data-bs-theme` attribute changes: + - If new value is `"light"`, load `theme_light` CSS + - If new value is `"dark"`, load `theme_dark` CSS + - If value is removed or empty, default to light +3. On first load, check current `data-bs-theme` and load appropriate theme + +**Theme loading mechanism:** +- Each theme gets a `` +- When switching themes, remove old link element and add new one +- Use link's `onload` event to prevent FOUC (Flash of Unstyled Content) + +### 3.3 Theme Defaults + +**Default theme selection:** +- `theme_light = "github-light"` (clean, readable, GitHub-style) +- `theme_dark = "github-dark"` (consistent with light theme) + +**Alternative recommendations:** +- For VS Code feel: `"vs-code-light"` / `"vs-code-dark"` +- For syntax-heavy: `"one-light"` / `"one-dark"` + +Users can override per-editor or globally via a package option: +```r +options( + querychat.code_editor_theme_light = "vs-code-light", + querychat.code_editor_theme_dark = "night-owl" +) +``` + +## Phase 4: Language Support + +### 4.1 Language Grammar Loading + +**Challenge:** Prism Code Editor requires language grammar files to be imported before they can be used. We need to support multiple languages without loading all of them upfront. + +**Solution:** Dynamic imports in JavaScript + +When the binding initializes an editor: +1. Check if language grammar is already loaded (track in a global Set) +2. If not loaded, dynamically import it: `import('/path/to/languages/{language}.js')` +3. Wait for import to complete before creating editor +4. Cache that language is loaded + +**Supported languages (initial):** +- `sql`: SQL queries +- `javascript`: JavaScript code +- `python`: Python code +- `r`: R code +- `markup`: HTML/XML +- `css`: CSS styles +- `json`: JSON data + +**Future expansion:** Easy to add more languages by: +1. Copying additional language files from prism-code-editor +2. Adding to supported language list in validation +3. No JavaScript changes needed (dynamic import handles it) + +### 4.2 Language-Specific Defaults + +Different languages may benefit from different defaults: + +**SQL:** +- `tab_size = 2` +- `indentation = "space"` +- `word_wrap = FALSE` +- `placeholder = "-- Enter SQL query"` + +**Python:** +- `tab_size = 4` +- `indentation = "tab"` +- `placeholder = "# Enter Python code"` + +**R:** +- `tab_size = 2` +- `indentation = "space"` +- `placeholder = "# Enter R code"` + +Implement via internal helper function that provides language-specific defaults, which can be overridden by user arguments. + +## Phase 5: Keyboard Shortcuts & UX + +### 5.1 Editor Shortcuts + +**Built-in from Prism Code Editor:** +- Ctrl/Cmd+Z: Undo +- Ctrl/Cmd+Shift+Z: Redo +- Tab: Indent selection +- Shift+Tab: Dedent selection +- Ctrl/Cmd+/: Toggle line comment (requires language-specific behavior) + +**Custom shortcuts to add:** +- **Ctrl/Cmd+Enter: Submit code to R** + - Trigger input value update + - Visual feedback (brief highlight or border flash) + - Prevent default behavior + +Implementation: Add event listener to editor's textarea for keydown events, check for Ctrl/Cmd+Enter combination. + +### 5.2 Copy Button + +Prism Code Editor's `copyButton()` extension provides a floating copy button. Configure: +- Position: Top-right of editor +- Icon: Use clipboard icon (browser default or custom SVG) +- Tooltip: "Copy code" +- Success feedback: Brief checkmark icon or "Copied!" tooltip + +### 5.3 Placeholder Text + +When editor is empty, show placeholder text (like HTML ``). + +Implementation: +- Use `::before` pseudo-element on wrapper when editor value is empty +- Style with lower opacity and italic font +- Remove when editor gains focus or has content + +## Phase 6: Testing Strategy + +### 6.1 Example Shiny App + +**Create:** `pkg-r/inst/examples-shiny/code-editor/app.R` + +Demonstrate: +- Multiple editors with different languages on one page +- Theme switcher (buttons to change `data-bs-theme`) +- Live output showing current editor value +- Update editor from R using `update_code_editor()` +- Read-only mode toggle +- Language switching demo + +### 6.2 Unit Tests + +**Create:** `pkg-r/tests/testthat/test-code-editor.R` + +Test coverage: +- `html_dependency_code_editor()` returns valid htmlDependency object +- `input_code_editor()` generates correct HTML structure with all data attributes +- `update_code_editor()` validation (theme names, language names) +- Language defaults are applied correctly +- Namespaced IDs work properly +- Invalid language/theme names raise errors + + +### 6.3 Integration Tests + +**Create:** `pkg-r/tests/testthat/test-code-editor-app.R` + +Use `shinytest2` to test: +- Editor initialization in browser +- Value updates from editor to R +- Updates from R to editor +- Theme switching when Bootstrap theme changes +- Keyboard shortcuts trigger updates + + +### 6.4 Manual Testing Checklist + +Document in `pkg-r/examples/code-editor-manual-tests.md`: +- [ ] Copy button copies correct content +- [ ] Ctrl/Cmd+Enter triggers update +- [ ] Blur triggers update +- [ ] Theme switches automatically with Bootstrap theme +- [ ] Multiple editors on page work independently +- [ ] Syntax highlighting correct for each language +- [ ] Read-only mode prevents editing +- [ ] Undo/redo work correctly +- [ ] Line numbers toggle works +- [ ] Word wrap toggle works +- [ ] Tab size changes affect indentation +- [ ] Works in RStudio Viewer, browser, and Shiny Server + +## Phase 7: Documentation + +### 7.1 Function Documentation + +Each should include: +- Clear description of purpose +- All parameter descriptions with types and defaults +- Return value description +- At least 3 examples of increasing complexity +- Link to related functions +- Use roxygen2 comments for documentation +- Update documentation by calling `devtools::document()` + +## Phase 8: Integration with querychat + +### 8.1 Add Editor to querychat UI (Optional) + +The code editor is designed as a standalone component, but can be integrated into the querychat interface if desired. + +**Potential integration points:** + +**Option A: Show SQL in chat messages** +- When LLM uses `tool_update_dashboard`, display the SQL in a read-only code editor within the chat message +- Users can copy SQL easily +- Provides better syntax highlighting than plain text + +**Option B: Live SQL editor panel** +- Add `show_sql_editor = FALSE` parameter to `querychat_ui()` +- When TRUE, shows `input_code_editor()` below chat in a card +- Bidirectional sync: chat updates editor, editor updates can re-run query +- Requires adding "Run Query" button or Ctrl+Enter handler + +**Option C: Separate but reactive** +- User adds `input_code_editor()` themselves in their UI layout +- Use `update_code_editor()` to populate it when LLM generates SQL +- User's app handles the connection between editor and query execution + +**Recommendation:** Start with Option C (most flexible), document Options A & B as patterns users can implement. If there's demand, implement Option B in a future version. + +### 8.2 Documentation for querychat Integration + +**Add to vignette:** Section on "Using Code Editor with querychat" + +Show example of: +```r +ui <- page_sidebar( + sidebar = querychat_sidebar("chat"), + card( + card_header("SQL Query"), + input_code_editor("sql", language = "sql", height = "200px") + ), + # ... output displays +) + +server <- function(input, output, session) { + qc <- querychat_server("chat", config) + + # Update editor when query changes + observe({ + update_code_editor(session, "sql", code = qc$sql()) + }) + + # Run query when editor updates + observe({ + # input$sql available as reactive value + }) +} +``` + +## Phase 9: Future Enhancements (Deferred) + +### 9.1 Autocomplete (Phase 2) + +**Scope:** Add autocomplete for SQL keywords, table names, and column names. + +**Approach:** +- Add `autocomplete.js` and `autocomplete.css` to dependencies +- Create SQL-specific completion source +- Accept `schema` parameter in `input_code_editor()` to provide table/column info +- Convert R schema object to JavaScript format +- Register completions for SQL language + +**Design decision needed:** How to pass schema info (data attribute vs. separate Shiny message)? + +### 9.2 SQL Formatting + +**Scope:** Add "Format SQL" button that prettifies code. + +**Approach:** +- Bundle lightweight SQL formatter (e.g., `sql-formatter`) +- Add format button to editor UI +- Format current editor content on click +- Update editor value with formatted result + +### 9.3 Multi-cursor Support + +Prism Code Editor supports multiple cursors. Document how to enable and use them. + +### 9.4 Diff View + +For showing before/after SQL changes, could add split-pane diff view using two read-only editors. + +### 9.5 Additional Languages + +As needed, add more language grammars: +- `bash`: Shell scripts +- `yaml`: Configuration files +- `markdown`: Documentation +- `java`, `c`, `cpp`, `go`, etc. + +Simply requires copying language files and adding to validation list. + +## Phase 10: Python Package Port + +### 10.1 Port to Shiny for Python + +**Create:** `pkg-py/src/querychat/code_editor.py` + +**Implementation notes:** +- JavaScript files can be shared (symlink or copy from R package) +- Use `htmltools` Python package for HTML dependency management +- Function signatures mirror R version but use Python conventions (snake_case, type hints) +- Schema object format should work with pandas DataFrames and SQLAlchemy connections +- Testing with pytest and Playwright + +**Functions to implement:** +- `input_code_editor(id, code="", language="sql", ...)` +- `update_code_editor(id, code=None, language=None, ...)` +- `code_editor_themes()` - list available themes + +**Integration with existing Python querychat:** +- Follow same pattern as R version +- Example app in `pkg-py/examples/code-editor-demo.py` + +--- + +## Technical Dependencies + +**R Packages:** +- `htmltools`: HTML dependency management +- `shiny`: Input binding framework +- `bslib`: Bootstrap 5 integration (for theme detection) +- `jsonlite`: For serializing data to JavaScript (if passing schema) + +**JavaScript Packages (via npm):** +- `prism-code-editor`: ^3.0.0 (or latest) +- `cpy-cli`: For copying files from node_modules + +**Browser Requirements:** +- Modern browsers with ES6+ support +- Chrome/Edge 90+, Firefox 88+, Safari 14+ +- No IE11 support (consistent with Bootstrap 5) + +--- + +## Success Criteria + +The code editor component is complete when: + +1. ✅ User can add `input_code_editor()` to any Shiny app +2. ✅ Code changes in editor are sent to R as reactive input +3. ✅ `update_code_editor()` can change editor content from server +4. ✅ Themes switch automatically with Bootstrap 5 theme +5. ✅ Copy button works reliably +6. ✅ Syntax highlighting works for SQL, JavaScript, Python, R +7. ✅ Ctrl/Cmd+Enter triggers update to R +8. ✅ Blur event triggers update to R +9. ✅ All options are updatable from server +10. ✅ Component works in RStudio, browser, and Shiny Server +11. ✅ Documentation is complete with examples +12. ✅ Tests pass on all supported platforms +13. ✅ Example app demonstrates all features From 29f35278874e03429e84d5da55a6f8f35ac4f2c2 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 14 Oct 2025 23:42:03 -0400 Subject: [PATCH 02/32] chore: remove future work from plan --- btw.md | 132 +-------------------------------------------------------- 1 file changed, 1 insertion(+), 131 deletions(-) diff --git a/btw.md b/btw.md index 292809ed..ac365cb2 100644 --- a/btw.md +++ b/btw.md @@ -485,18 +485,9 @@ Test coverage: - Namespaced IDs work properly - Invalid language/theme names raise errors - ### 6.3 Integration Tests -**Create:** `pkg-r/tests/testthat/test-code-editor-app.R` - -Use `shinytest2` to test: -- Editor initialization in browser -- Value updates from editor to R -- Updates from R to editor -- Theme switching when Bootstrap theme changes -- Keyboard shortcuts trigger updates - +Do not implement automated browser tests. This will be done manually later. ### 6.4 Manual Testing Checklist @@ -527,127 +518,6 @@ Each should include: - Use roxygen2 comments for documentation - Update documentation by calling `devtools::document()` -## Phase 8: Integration with querychat - -### 8.1 Add Editor to querychat UI (Optional) - -The code editor is designed as a standalone component, but can be integrated into the querychat interface if desired. - -**Potential integration points:** - -**Option A: Show SQL in chat messages** -- When LLM uses `tool_update_dashboard`, display the SQL in a read-only code editor within the chat message -- Users can copy SQL easily -- Provides better syntax highlighting than plain text - -**Option B: Live SQL editor panel** -- Add `show_sql_editor = FALSE` parameter to `querychat_ui()` -- When TRUE, shows `input_code_editor()` below chat in a card -- Bidirectional sync: chat updates editor, editor updates can re-run query -- Requires adding "Run Query" button or Ctrl+Enter handler - -**Option C: Separate but reactive** -- User adds `input_code_editor()` themselves in their UI layout -- Use `update_code_editor()` to populate it when LLM generates SQL -- User's app handles the connection between editor and query execution - -**Recommendation:** Start with Option C (most flexible), document Options A & B as patterns users can implement. If there's demand, implement Option B in a future version. - -### 8.2 Documentation for querychat Integration - -**Add to vignette:** Section on "Using Code Editor with querychat" - -Show example of: -```r -ui <- page_sidebar( - sidebar = querychat_sidebar("chat"), - card( - card_header("SQL Query"), - input_code_editor("sql", language = "sql", height = "200px") - ), - # ... output displays -) - -server <- function(input, output, session) { - qc <- querychat_server("chat", config) - - # Update editor when query changes - observe({ - update_code_editor(session, "sql", code = qc$sql()) - }) - - # Run query when editor updates - observe({ - # input$sql available as reactive value - }) -} -``` - -## Phase 9: Future Enhancements (Deferred) - -### 9.1 Autocomplete (Phase 2) - -**Scope:** Add autocomplete for SQL keywords, table names, and column names. - -**Approach:** -- Add `autocomplete.js` and `autocomplete.css` to dependencies -- Create SQL-specific completion source -- Accept `schema` parameter in `input_code_editor()` to provide table/column info -- Convert R schema object to JavaScript format -- Register completions for SQL language - -**Design decision needed:** How to pass schema info (data attribute vs. separate Shiny message)? - -### 9.2 SQL Formatting - -**Scope:** Add "Format SQL" button that prettifies code. - -**Approach:** -- Bundle lightweight SQL formatter (e.g., `sql-formatter`) -- Add format button to editor UI -- Format current editor content on click -- Update editor value with formatted result - -### 9.3 Multi-cursor Support - -Prism Code Editor supports multiple cursors. Document how to enable and use them. - -### 9.4 Diff View - -For showing before/after SQL changes, could add split-pane diff view using two read-only editors. - -### 9.5 Additional Languages - -As needed, add more language grammars: -- `bash`: Shell scripts -- `yaml`: Configuration files -- `markdown`: Documentation -- `java`, `c`, `cpp`, `go`, etc. - -Simply requires copying language files and adding to validation list. - -## Phase 10: Python Package Port - -### 10.1 Port to Shiny for Python - -**Create:** `pkg-py/src/querychat/code_editor.py` - -**Implementation notes:** -- JavaScript files can be shared (symlink or copy from R package) -- Use `htmltools` Python package for HTML dependency management -- Function signatures mirror R version but use Python conventions (snake_case, type hints) -- Schema object format should work with pandas DataFrames and SQLAlchemy connections -- Testing with pytest and Playwright - -**Functions to implement:** -- `input_code_editor(id, code="", language="sql", ...)` -- `update_code_editor(id, code=None, language=None, ...)` -- `code_editor_themes()` - list available themes - -**Integration with existing Python querychat:** -- Follow same pattern as R version -- Example app in `pkg-py/examples/code-editor-demo.py` - --- ## Technical Dependencies From c39c6a9281a752c5814aee97e69c2a6ddee5f8e9 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 14 Oct 2025 23:48:05 -0400 Subject: [PATCH 03/32] chore: add dev advice to plan --- btw.md | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/btw.md b/btw.md index ac365cb2..b151eecd 100644 --- a/btw.md +++ b/btw.md @@ -2,22 +2,10 @@ client: aws_bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0 tools: - docs - # - env - files - # - ide - # - search - # - session - web --- -Follow these important style rules when writing R code: - -* Prefer solutions that use {tidyverse} -* Always use `<-` for assignment -* Always use the native base-R pipe `|>` for piped expressions - ---- - # Code Editor Component Implementation Plan ## Overview @@ -537,6 +525,37 @@ Each should include: - Chrome/Edge 90+, Firefox 88+, Safari 14+ - No IE11 support (consistent with Bootstrap 5) +## Key development commands + +General advice: +* When running R from the console, always run it with `--quiet --vanilla` +* Always run `air format .` before committing code +* The R package is in `pkg-r/`, always keep track of your working directory. + +### Testing + +- Tests for `pkg-r/R/{name}.R` go in `pkg-r/tests/testthat/test-{name}.R`. +- Use `devtools::test("pkg-r", reporter = "check")` to run all tests +- Use `devtools::test("pkg-r", filter = "name", reporter = "check")` to run tests for `pkg-r/R/{name}.R` +- DO NOT USE `devtools::test_active_file()` +- All testing functions automatically load code; you don't need to. + +- All new code should have an accompanying test. +- If there are existing tests, place new tests next to similar existing tests. + +### Documentation + +- Run `devtools::document("pkg-r")` after changing any roxygen2 docs. +- Every user facing function should be exported and have roxygen2 documentation. +- Whenever you add a new documentation file, make sure to also add the topic name to `_pkgdown.yml`. +- Use sentence case for all headings + +### Code style + +- Use newspaper style/high-level first function organization. Main logic at the top and helper functions should come below. +- Don't define functions inside of functions unless they are very brief. +- Error messages should use `cli::cli_abort()` and follow the tidyverse style guide (https://style.tidyverse.org/errors.html) + --- ## Success Criteria From 1193f40da4f6c31c45834712272e4b274cdfbea2 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 15 Oct 2025 09:05:13 -0400 Subject: [PATCH 04/32] feat: set up npm workflow for prism-code-editor dependencies - Add package.json with scripts to install and copy prism-code-editor files - Install prism-code-editor ^3.0.0 from npm - Copy core library files (index.js, layout.css, copy.css) to inst/js - Copy all language grammar files to inst/js/prism-code-editor/languages/ - Copy extension files (copyButton, commands) to inst/js/prism-code-editor/extensions/ - Copy all theme CSS files to inst/js/prism-code-editor/themes/ - Add node_modules and package-lock.json to .gitignore This completes Phase 1.1 of the code editor implementation plan. The npm workflow allows us to easily update dependencies in the future with a single 'npm run update-deps' command. --- .gitignore | 2 + package.json | 19 + pkg-r/inst/js/prism-code-editor/copy.css | 1 + .../prism-code-editor/extensions/commands.js | 333 ++++++++++++++++++ .../extensions/copyButton/index.js | 24 ++ pkg-r/inst/js/prism-code-editor/index.js | 11 + .../js/prism-code-editor/languages/abap.js | 7 + .../js/prism-code-editor/languages/abnf.js | 4 + .../languages/actionscript.js | 4 + .../js/prism-code-editor/languages/ada.js | 4 + .../js/prism-code-editor/languages/agda.js | 7 + .../inst/js/prism-code-editor/languages/al.js | 4 + .../js/prism-code-editor/languages/antlr4.js | 4 + .../prism-code-editor/languages/apacheconf.js | 16 + .../js/prism-code-editor/languages/apex.js | 4 + .../js/prism-code-editor/languages/apl.js | 4 + .../languages/applescript.js | 8 + .../js/prism-code-editor/languages/aql.js | 4 + .../js/prism-code-editor/languages/arduino.js | 4 + .../js/prism-code-editor/languages/arff.js | 7 + .../js/prism-code-editor/languages/arturo.js | 7 + .../prism-code-editor/languages/asciidoc.js | 7 + .../js/prism-code-editor/languages/asm.js | 3 + .../js/prism-code-editor/languages/aspnet.js | 4 + .../prism-code-editor/languages/autohotkey.js | 7 + .../js/prism-code-editor/languages/autoit.js | 8 + .../prism-code-editor/languages/avisynth.js | 7 + .../prism-code-editor/languages/avro-idl.js | 4 + .../js/prism-code-editor/languages/awk.js | 6 + .../js/prism-code-editor/languages/bash.js | 4 + .../js/prism-code-editor/languages/basic.js | 7 + .../js/prism-code-editor/languages/batch.js | 7 + .../js/prism-code-editor/languages/bbj.js | 7 + .../js/prism-code-editor/languages/bicep.js | 4 + .../js/prism-code-editor/languages/birb.js | 4 + .../js/prism-code-editor/languages/bison.js | 4 + .../js/prism-code-editor/languages/bqn.js | 4 + .../languages/brightscript.js | 5 + .../js/prism-code-editor/languages/bro.js | 6 + .../js/prism-code-editor/languages/bsl.js | 6 + .../prism-code-editor/languages/cfscript.js | 4 + .../prism-code-editor/languages/chaiscript.js | 4 + .../js/prism-code-editor/languages/cil.js | 6 + .../js/prism-code-editor/languages/cilk.js | 4 + .../js/prism-code-editor/languages/clike.js | 4 + .../js/prism-code-editor/languages/clojure.js | 6 + .../js/prism-code-editor/languages/cmake.js | 4 + .../js/prism-code-editor/languages/cobol.js | 7 + .../languages/coffeescript.js | 7 + .../js/prism-code-editor/languages/common.js | 27 ++ .../prism-code-editor/languages/concurnas.js | 4 + .../prism-code-editor/languages/cooklang.js | 8 + .../js/prism-code-editor/languages/coq.js | 4 + .../js/prism-code-editor/languages/cshtml.js | 8 + .../js/prism-code-editor/languages/css.js | 11 + .../js/prism-code-editor/languages/cue.js | 4 + .../js/prism-code-editor/languages/cypher.js | 4 + .../prism-code-editor/languages/dataweave.js | 4 + .../js/prism-code-editor/languages/dax.js | 4 + .../js/prism-code-editor/languages/dhall.js | 7 + .../js/prism-code-editor/languages/django.js | 6 + .../languages/dns-zone-file.js | 7 + .../js/prism-code-editor/languages/docker.js | 7 + .../js/prism-code-editor/languages/dot.js | 4 + .../js/prism-code-editor/languages/ebnf.js | 6 + .../languages/editorconfig.js | 7 + .../js/prism-code-editor/languages/eiffel.js | 6 + .../js/prism-code-editor/languages/ejs.js | 4 + .../js/prism-code-editor/languages/elixir.js | 7 + .../js/prism-code-editor/languages/elm.js | 8 + .../js/prism-code-editor/languages/erb.js | 4 + .../js/prism-code-editor/languages/erlang.js | 7 + .../js/prism-code-editor/languages/etlua.js | 4 + .../languages/excel-formula.js | 6 + .../js/prism-code-editor/languages/factor.js | 7 + .../js/prism-code-editor/languages/false.js | 7 + .../languages/firestore-security-rules.js | 6 + .../js/prism-code-editor/languages/fortran.js | 7 + .../js/prism-code-editor/languages/fsharp.js | 4 + .../js/prism-code-editor/languages/ftl.js | 5 + .../js/prism-code-editor/languages/gap.js | 7 + .../js/prism-code-editor/languages/gcode.js | 7 + .../prism-code-editor/languages/gdscript.js | 6 + .../js/prism-code-editor/languages/gettext.js | 7 + .../js/prism-code-editor/languages/gherkin.js | 7 + .../js/prism-code-editor/languages/git.js | 7 + .../js/prism-code-editor/languages/glsl.js | 4 + .../js/prism-code-editor/languages/gml.js | 4 + .../inst/js/prism-code-editor/languages/gn.js | 6 + .../prism-code-editor/languages/go-module.js | 6 + .../js/prism-code-editor/languages/gradle.js | 4 + .../js/prism-code-editor/languages/graphql.js | 4 + .../js/prism-code-editor/languages/groovy.js | 4 + .../js/prism-code-editor/languages/haml.js | 4 + .../prism-code-editor/languages/handlebars.js | 6 + .../js/prism-code-editor/languages/haskell.js | 7 + .../js/prism-code-editor/languages/hcl.js | 7 + .../js/prism-code-editor/languages/hoon.js | 7 + .../js/prism-code-editor/languages/html.js | 4 + .../prism-code-editor/languages/ichigojam.js | 6 + .../js/prism-code-editor/languages/icon.js | 6 + .../js/prism-code-editor/languages/iecst.js | 7 + .../js/prism-code-editor/languages/ignore.js | 7 + .../js/prism-code-editor/languages/index.js | 235 ++++++++++++ .../js/prism-code-editor/languages/inform7.js | 6 + .../js/prism-code-editor/languages/ini.js | 7 + .../inst/js/prism-code-editor/languages/io.js | 4 + .../inst/js/prism-code-editor/languages/j.js | 7 + .../js/prism-code-editor/languages/jolie.js | 4 + .../inst/js/prism-code-editor/languages/jq.js | 6 + .../js/prism-code-editor/languages/json.js | 4 + .../js/prism-code-editor/languages/jsx.js | 43 +++ .../js/prism-code-editor/languages/julia.js | 7 + .../prism-code-editor/languages/keepalived.js | 6 + .../js/prism-code-editor/languages/keyman.js | 7 + .../js/prism-code-editor/languages/kotlin.js | 4 + .../js/prism-code-editor/languages/kumir.js | 6 + .../js/prism-code-editor/languages/kusto.js | 6 + .../js/prism-code-editor/languages/latex.js | 4 + .../js/prism-code-editor/languages/latte.js | 5 + .../prism-code-editor/languages/lilypond.js | 7 + .../languages/linker-script.js | 6 + .../js/prism-code-editor/languages/liquid.js | 5 + .../js/prism-code-editor/languages/lisp.js | 4 + .../prism-code-editor/languages/livescript.js | 7 + .../js/prism-code-editor/languages/llvm.js | 6 + .../js/prism-code-editor/languages/lolcode.js | 8 + .../js/prism-code-editor/languages/lua.js | 4 + .../js/prism-code-editor/languages/magma.js | 4 + .../prism-code-editor/languages/makefile.js | 7 + .../js/prism-code-editor/languages/mata.js | 4 + .../js/prism-code-editor/languages/matlab.js | 7 + .../prism-code-editor/languages/maxscript.js | 7 + .../js/prism-code-editor/languages/mel.js | 4 + .../js/prism-code-editor/languages/mermaid.js | 6 + .../prism-code-editor/languages/metafont.js | 6 + .../js/prism-code-editor/languages/mizar.js | 6 + .../js/prism-code-editor/languages/mongodb.js | 4 + .../js/prism-code-editor/languages/monkey.js | 6 + .../prism-code-editor/languages/moonscript.js | 6 + .../js/prism-code-editor/languages/n1ql.js | 7 + .../js/prism-code-editor/languages/n4js.js | 4 + .../languages/nand2tetris-hdl.js | 4 + .../prism-code-editor/languages/naniscript.js | 6 + .../js/prism-code-editor/languages/neon.js | 6 + .../js/prism-code-editor/languages/nevod.js | 4 + .../js/prism-code-editor/languages/nginx.js | 6 + .../js/prism-code-editor/languages/nim.js | 6 + .../js/prism-code-editor/languages/nix.js | 7 + .../js/prism-code-editor/languages/nsis.js | 7 + .../prism-code-editor/languages/objectivec.js | 4 + .../js/prism-code-editor/languages/ocaml.js | 6 + .../js/prism-code-editor/languages/odin.js | 4 + .../js/prism-code-editor/languages/opencl.js | 4 + .../prism-code-editor/languages/openqasm.js | 4 + .../inst/js/prism-code-editor/languages/oz.js | 7 + .../js/prism-code-editor/languages/parigp.js | 7 + .../js/prism-code-editor/languages/parser.js | 11 + .../js/prism-code-editor/languages/pascal.js | 7 + .../prism-code-editor/languages/peoplecode.js | 7 + .../js/prism-code-editor/languages/perl.js | 4 + .../js/prism-code-editor/languages/php.js | 16 + .../prism-code-editor/languages/plant-uml.js | 7 + .../prism-code-editor/languages/powerquery.js | 4 + .../prism-code-editor/languages/powershell.js | 7 + .../prism-code-editor/languages/processing.js | 4 + .../js/prism-code-editor/languages/prolog.js | 7 + .../js/prism-code-editor/languages/promql.js | 6 + .../prism-code-editor/languages/properties.js | 7 + .../prism-code-editor/languages/protobuf.js | 4 + .../js/prism-code-editor/languages/psl.js | 6 + .../js/prism-code-editor/languages/pug.js | 6 + .../js/prism-code-editor/languages/puppet.js | 7 + .../js/prism-code-editor/languages/pure.js | 4 + .../prism-code-editor/languages/purebasic.js | 6 + .../js/prism-code-editor/languages/python.js | 4 + .../inst/js/prism-code-editor/languages/q.js | 6 + .../js/prism-code-editor/languages/qml.js | 4 + .../js/prism-code-editor/languages/qore.js | 4 + .../js/prism-code-editor/languages/qsharp.js | 6 + .../inst/js/prism-code-editor/languages/r.js | 6 + .../js/prism-code-editor/languages/reason.js | 4 + .../js/prism-code-editor/languages/rego.js | 6 + .../prism-code-editor/languages/rescript.js | 4 + .../js/prism-code-editor/languages/rest.js | 7 + .../js/prism-code-editor/languages/rip.js | 6 + .../prism-code-editor/languages/roboconf.js | 6 + .../languages/robotframework.js | 7 + .../js/prism-code-editor/languages/ruby.js | 4 + .../js/prism-code-editor/languages/rust.js | 4 + .../js/prism-code-editor/languages/sas.js | 6 + .../js/prism-code-editor/languages/scala.js | 4 + .../js/prism-code-editor/languages/scheme.js | 7 + .../js/prism-code-editor/languages/smali.js | 7 + .../prism-code-editor/languages/smalltalk.js | 6 + .../js/prism-code-editor/languages/smarty.js | 5 + .../js/prism-code-editor/languages/sml.js | 6 + .../prism-code-editor/languages/solidity.js | 4 + .../languages/solution-file.js | 7 + .../js/prism-code-editor/languages/soy.js | 3 + .../prism-code-editor/languages/splunk-spl.js | 6 + .../js/prism-code-editor/languages/sqf.js | 4 + .../js/prism-code-editor/languages/sql.js | 7 + .../prism-code-editor/languages/squirrel.js | 4 + .../js/prism-code-editor/languages/stan.js | 4 + .../js/prism-code-editor/languages/stata.js | 4 + .../js/prism-code-editor/languages/stylus.js | 4 + .../languages/supercollider.js | 4 + .../js/prism-code-editor/languages/swift.js | 4 + .../js/prism-code-editor/languages/systemd.js | 7 + .../js/prism-code-editor/languages/tcl.js | 6 + .../js/prism-code-editor/languages/textile.js | 4 + .../js/prism-code-editor/languages/toml.js | 6 + .../js/prism-code-editor/languages/tremor.js | 7 + .../js/prism-code-editor/languages/tt2.js | 9 + .../js/prism-code-editor/languages/turtle.js | 4 + .../js/prism-code-editor/languages/twig.js | 5 + .../prism-code-editor/languages/typoscript.js | 4 + .../languages/unrealscript.js | 4 + .../js/prism-code-editor/languages/uorazor.js | 7 + .../inst/js/prism-code-editor/languages/v.js | 4 + .../js/prism-code-editor/languages/vala.js | 4 + .../js/prism-code-editor/languages/vbnet.js | 7 + .../prism-code-editor/languages/velocity.js | 11 + .../js/prism-code-editor/languages/verilog.js | 4 + .../js/prism-code-editor/languages/vhdl.js | 6 + .../js/prism-code-editor/languages/vim.js | 6 + .../languages/visual-basic.js | 6 + .../prism-code-editor/languages/warpscript.js | 4 + .../js/prism-code-editor/languages/wasm.js | 8 + .../js/prism-code-editor/languages/web-idl.js | 4 + .../js/prism-code-editor/languages/wgsl.js | 4 + .../js/prism-code-editor/languages/wiki.js | 10 + .../js/prism-code-editor/languages/wolfram.js | 4 + .../js/prism-code-editor/languages/wren.js | 4 + .../js/prism-code-editor/languages/xeora.js | 4 + .../js/prism-code-editor/languages/xml.js | 4 + .../js/prism-code-editor/languages/xojo.js | 7 + .../js/prism-code-editor/languages/xquery.js | 12 + .../js/prism-code-editor/languages/yaml.js | 4 + .../js/prism-code-editor/languages/yang.js | 4 + .../js/prism-code-editor/languages/zig.js | 6 + pkg-r/inst/js/prism-code-editor/layout.css | 1 + .../themes/atom-one-dark.css | 1 + .../js/prism-code-editor/themes/dracula.css | 1 + .../themes/github-dark-dimmed.css | 1 + .../prism-code-editor/themes/github-dark.css | 1 + .../prism-code-editor/themes/github-light.css | 1 + .../js/prism-code-editor/themes/night-owl.css | 1 + .../themes/prism-okaidia.css | 1 + .../themes/prism-solarized-light.css | 1 + .../themes/prism-tomorrow.css | 1 + .../themes/prism-twilight.css | 1 + .../js/prism-code-editor/themes/prism.css | 1 + .../prism-code-editor/themes/vs-code-dark.css | 1 + .../themes/vs-code-light.css | 1 + 256 files changed, 2032 insertions(+) create mode 100644 package.json create mode 100644 pkg-r/inst/js/prism-code-editor/copy.css create mode 100644 pkg-r/inst/js/prism-code-editor/extensions/commands.js create mode 100644 pkg-r/inst/js/prism-code-editor/extensions/copyButton/index.js create mode 100644 pkg-r/inst/js/prism-code-editor/index.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/abap.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/abnf.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/actionscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/ada.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/agda.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/al.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/antlr4.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/apacheconf.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/apex.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/apl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/applescript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/aql.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/arduino.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/arff.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/arturo.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/asciidoc.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/asm.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/aspnet.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/autohotkey.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/autoit.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/avisynth.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/avro-idl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/awk.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/bash.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/basic.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/batch.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/bbj.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/bicep.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/birb.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/bison.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/bqn.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/brightscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/bro.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/bsl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/cfscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/chaiscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/cil.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/cilk.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/clike.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/clojure.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/cmake.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/cobol.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/coffeescript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/common.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/concurnas.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/cooklang.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/coq.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/cshtml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/css.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/cue.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/cypher.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/dataweave.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/dax.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/dhall.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/django.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/dns-zone-file.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/docker.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/dot.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/ebnf.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/editorconfig.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/eiffel.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/ejs.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/elixir.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/elm.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/erb.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/erlang.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/etlua.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/excel-formula.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/factor.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/false.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/firestore-security-rules.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/fortran.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/fsharp.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/ftl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/gap.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/gcode.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/gdscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/gettext.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/gherkin.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/git.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/glsl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/gml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/gn.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/go-module.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/gradle.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/graphql.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/groovy.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/haml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/handlebars.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/haskell.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/hcl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/hoon.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/html.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/ichigojam.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/icon.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/iecst.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/ignore.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/index.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/inform7.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/ini.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/io.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/j.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/jolie.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/jq.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/json.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/jsx.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/julia.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/keepalived.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/keyman.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/kotlin.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/kumir.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/kusto.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/latex.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/latte.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/lilypond.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/linker-script.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/liquid.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/lisp.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/livescript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/llvm.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/lolcode.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/lua.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/magma.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/makefile.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/mata.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/matlab.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/maxscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/mel.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/mermaid.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/metafont.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/mizar.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/mongodb.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/monkey.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/moonscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/n1ql.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/n4js.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/nand2tetris-hdl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/naniscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/neon.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/nevod.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/nginx.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/nim.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/nix.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/nsis.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/objectivec.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/ocaml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/odin.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/opencl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/openqasm.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/oz.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/parigp.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/parser.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/pascal.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/peoplecode.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/perl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/php.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/plant-uml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/powerquery.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/powershell.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/processing.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/prolog.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/promql.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/properties.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/protobuf.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/psl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/pug.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/puppet.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/pure.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/purebasic.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/python.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/q.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/qml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/qore.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/qsharp.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/r.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/reason.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/rego.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/rescript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/rest.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/rip.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/roboconf.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/robotframework.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/ruby.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/rust.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/sas.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/scala.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/scheme.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/smali.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/smalltalk.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/smarty.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/sml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/solidity.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/solution-file.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/soy.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/splunk-spl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/sqf.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/sql.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/squirrel.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/stan.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/stata.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/stylus.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/supercollider.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/swift.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/systemd.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/tcl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/textile.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/toml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/tremor.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/tt2.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/turtle.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/twig.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/typoscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/unrealscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/uorazor.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/v.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/vala.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/vbnet.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/velocity.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/verilog.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/vhdl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/vim.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/visual-basic.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/warpscript.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/wasm.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/web-idl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/wgsl.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/wiki.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/wolfram.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/wren.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/xeora.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/xml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/xojo.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/xquery.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/yaml.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/yang.js create mode 100644 pkg-r/inst/js/prism-code-editor/languages/zig.js create mode 100644 pkg-r/inst/js/prism-code-editor/layout.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/atom-one-dark.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/dracula.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/github-dark-dimmed.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/github-dark.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/github-light.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/night-owl.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/prism-okaidia.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/prism-solarized-light.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/prism-tomorrow.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/prism-twilight.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/prism.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/vs-code-dark.css create mode 100644 pkg-r/inst/js/prism-code-editor/themes/vs-code-light.css 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..a1720284 --- /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": "cp node_modules/prism-code-editor/dist/index.js node_modules/prism-code-editor/dist/layout.css node_modules/prism-code-editor/dist/copy.css pkg-r/inst/js/prism-code-editor/", + "copy-languages": "cp node_modules/prism-code-editor/dist/languages/*.js pkg-r/inst/js/prism-code-editor/languages/", + "copy-extensions": "cp node_modules/prism-code-editor/dist/extensions/copyButton/index.js pkg-r/inst/js/prism-code-editor/extensions/copyButton/ && cp node_modules/prism-code-editor/dist/extensions/commands.js pkg-r/inst/js/prism-code-editor/extensions/", + "copy-themes": "cp 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": "^5.0.0" + } +} 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/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/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( + '