-
Notifications
You must be signed in to change notification settings - Fork 20
feat: Add code editor for SQL query #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gadenbuie
wants to merge
32
commits into
main
Choose a base branch
from
feat/code-editor
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- 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.
Phase 1.2: JavaScript Input Binding (code-editor-binding.js) - Implement Shiny InputBinding interface with all required methods - find(): Locate .code-editor-input elements - getValue/setValue(): Get/set editor content - subscribe/unsubscribe(): Handle value change events - receiveMessage(): Process updates from R via update_code_editor() - getRatePolicy(): Debounce updates with 300ms delay Dynamic module loading: - Lazy-load language grammars on demand using ES6 imports - Track loaded languages globally to avoid duplicate loads - Load prism-code-editor core, copyButton, and commands extensions Theme management: - setupThemeWatcher(): Monitor data-bs-theme attribute changes - loadTheme(): Dynamically switch theme CSS files - Support separate light/dark themes per editor instance - Prevent FOUC with sequential link element replacement Editor initialization: - initializeEditor(): Create editor with all configuration options - Parse data attributes for initial state - Set up blur and Ctrl/Cmd+Enter event listeners - Dispatch custom 'codeEditorUpdate' events for reactivity Update handling: - Support updating code, language, themes, and all editor options - Handle language changes by loading grammar and retokenizing - Re-evaluate theme when theme_light/theme_dark updated Phase 1.3: Base CSS Styling (code-editor.css) - Bootstrap 5 form control integration - Match border, border-radius, colors to Bootstrap theme - Focus state with primary color and box-shadow - Dark mode support using [data-bs-theme="dark"] selectors - Submit flash animation for Ctrl/Cmd+Enter feedback - Placeholder text styling when editor is empty - Read-only state styling with muted background - Proper z-index management for copy button - Monospace font family using Bootstrap CSS variables This completes Phase 1 of the implementation plan.
Phase 2: R Package Integration (input_code_editor.R)
Implements three main functions and supporting utilities:
1. html_dependency_code_editor() [internal]:
- Creates htmlDependency bundling all JS/CSS files
- Includes: index.js, code-editor-binding.js, layout.css, copy.css, code-editor.css
- Uses version 3.0.0 matching prism-code-editor package
- Sets all_files = FALSE for selective file inclusion
2. code_editor_themes() [exported]:
- Returns character vector of available theme names
- Dynamically discovers themes from inst/js/prism-code-editor/themes/
- Used for validation and user discovery of theme options
3. input_code_editor() [exported]:
- Main UI function following Shiny input conventions
- Parameters: id, code, language, height, width, themes, placeholder,
read_only, line_numbers, word_wrap, tab_size, indentation
- Creates div.code-editor-input with data attributes for JS binding
- Validates language and theme inputs
- Attaches htmlDependency automatically
- Default language: sql, Default themes: github-light/github-dark
- Supports all features planned: keyboard shortcuts, theme switching,
copy button, line numbers, word wrap, configurable tabs
4. update_code_editor() [exported]:
- Server-side update function
- Uses session$sendInputMessage() to communicate with JS binding
- Supports partial updates (only non-NULL params sent)
- Can update: code, language, themes, read_only, line_numbers,
word_wrap, tab_size, indentation
- Validates inputs before sending to JavaScript
- Uses rlang::check_dots_empty() to prevent typos
Supporting utilities:
- validate_theme(): Check theme exists in available themes
- validate_language(): Check language is supported
- Supported languages: sql, python, r, javascript, html, css, json,
bash, markdown, yaml, xml
Documentation:
- Full roxygen2 documentation with @param, @return, @examples, @Seealso
- Detailed description of keyboard shortcuts and update triggers
- Integration notes for Bootstrap 5 theme switching
- Cross-references between related functions
Generated documentation files (man/*.Rd) and NAMESPACE exports.
Note: Phases 3-5 (theme management, language support, keyboard shortcuts)
were already integrated into Phases 1-2 as they are core to the binding
and UI implementation, not separate implementation phases.
Phase 6: Example App and Tests Example Shiny App (inst/examples-shiny/code-editor/app.R): - Comprehensive demo showcasing all code editor features - Interactive controls for all editor options: * Language selector (sql, python, r, javascript, html, css, json) * Theme selectors for light and dark modes * Toggles for read-only, line numbers, word wrap * Sliders for tab size, radio for indentation type - Sample code loader for each language - Live output display showing current code and editor info - Bootstrap theme toggle to demonstrate automatic theme switching - Features documentation card with keyboard shortcuts and capabilities - Uses bslib::page_sidebar() for modern layout - Demonstrates update_code_editor() for all update scenarios README.md for example: - Installation and running instructions - Feature list with what to try - Code structure explanation Unit Tests (tests/testthat/test-input-code-editor.R): 79 passing tests covering: 1. HTML Dependency Tests: - Valid htmlDependency object creation - Correct name, version, scripts, and stylesheets 2. Theme Management Tests: - code_editor_themes() returns character vector - Includes expected themes (github-light/dark, vs-code-light/dark) - validate_theme() accepts valid themes - validate_theme() rejects invalid themes with helpful errors 3. Language Validation Tests: - validate_language() accepts all supported languages - validate_language() rejects unsupported languages with helpful errors 4. input_code_editor() Tests: - Generates correct HTML structure with proper class and ID - All data attributes present with correct values - Handles all parameters correctly (height, width, themes, options) - Uses correct defaults for all parameters - Validates theme and language names before rendering - Handles empty code, special characters, NULL placeholder - Indentation parameter converts to insert_spaces correctly - Creates unique IDs for multiple editors - Attaches htmlDependency once per editor - Works with all supported languages 5. update_code_editor() Tests: - Validates all input parameters (language, themes, indentation) - Rejects invalid languages and themes with helpful errors - Validates indentation must be 'space' or 'tab' Test organization: - Uses testthat framework matching existing package style - Clear test names describing what is being tested - Appropriate assertions (expect_error, expect_match, expect_silent, etc.) - Mock sessions where needed to avoid NULL session errors - Comments explaining test purpose All 79 tests pass successfully. Phase 7 note: Documentation was already completed in Phase 2 via comprehensive roxygen2 comments, so no additional documentation phase is needed. The .Rd files were generated by devtools::document().
Changes to JavaScript binding (code-editor-binding.js): - Add getPrismCodeEditorBasePath() helper function that: * Discovers base path by finding script[src*="prism-code-editor"][src$="index.js"] * Memoizes the result for performance (only searches DOM once) * Returns empty string with error if script element not found - Rename all 'basePath' variables to 'prismCodeEditorBasePath' for clarity - Update all functions to use the new naming: * loadLanguage(language, prismCodeEditorBasePath) * loadTheme(inputId, themeName, prismCodeEditorBasePath) * setupThemeWatcher(el, themeLight, themeDark, prismCodeEditorBasePath) - Remove dependency on data-base-path attribute from DOM elements - Use getPrismCodeEditorBasePath() in: * initializeEditor() - for initial setup * receiveMessage() - for language and theme updates Changes to R code (input_code_editor.R): - Remove data-base-path attribute from editor div - Simplify editor creation by removing base path logic - Base path is now discovered automatically in JavaScript Changes to tests (test-input-code-editor.R): - Update html_dependency_code_editor test to expect tagList - Check for both 'prism-code-editor' and 'shiny-input-code-editor' dependencies - Update HTML structure test to check dependencies via findDependencies() Benefits: - More robust: Automatically finds correct path regardless of htmlDep structure - Cleaner separation: R side doesn't need to know about JS file paths - Better performance: Memoization ensures DOM search happens only once - More maintainable: Single source of truth for base path discovery All 79 tests pass.
Updated loadLanguage() to import from prism/languages/ instead of languages/ to properly load Prism's auto-registering grammar modules. The prism-code-editor documentation specifies that grammars should be imported from prism-code-editor/prism/languages/* as these versions register themselves through side effects when imported. Also added documentation explaining that JavaScript is included in the clike grammar which is loaded by default.
Changed the identifying CSS class from .code-editor-input to .shiny-input-code-editor for consistency with Shiny naming conventions. Changes: - Updated JavaScript binding to find .shiny-input-code-editor elements - Updated R function to generate elements with .shiny-input-code-editor class - Updated CSS selectors throughout code-editor.css - Updated tests to check for the new class name
…eholder`, set `height = "auto"` by default
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Adds
input_code_editor()with associatedupdate_code_editor()that is automatically wired up withquerychat_ui_code().The code editor is a light-weight input using prism-code-editor.
This is the R package implementation, Python version will be forthcoming.
For #70