From 9b61ddc462f4b1187332e2ec0035f40446d71637 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 22 Sep 2025 23:36:03 +0900 Subject: [PATCH 1/2] feat: add autocomplete --- .../website/src/scripts/playground/view.js | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/website/src/scripts/playground/view.js b/packages/website/src/scripts/playground/view.js index e525f4d2..0272e203 100644 --- a/packages/website/src/scripts/playground/view.js +++ b/packages/website/src/scripts/playground/view.js @@ -6,6 +6,9 @@ import "codemirror/lib/codemirror.css"; import "codemirror/mode/htmlmixed/htmlmixed.js"; import "codemirror/mode/javascript/javascript.js"; +import "codemirror/addon/hint/show-hint.js"; +import "codemirror/addon/hint/show-hint.css"; +import rules from "@html-eslint/eslint-plugin/lib/rules"; import CodeMirror from "codemirror"; import { toMarker, @@ -15,6 +18,12 @@ import { html } from "@html-kit/html"; +// HTML ESLint rules for autocomplete +const HTML_ESLINT_RULES = Object.keys(rules).map(rule => `@html-eslint/${rule}`); + +const HTML_REG = /"@html-eslint\//; +const RULES_REG = /"rules"[\s\S]*?"@html-eslint\//; + export class View { constructor() { /** @@ -38,14 +47,71 @@ export class View { document.getElementById("config-editor"), { mode: "application/json", - lineNumbers: false + lineNumbers: false, + extraKeys: { + "Ctrl-Space": "autocomplete" + }, + hintOptions: { + hint: this.getHtmlEslintHints.bind(this) + } } ); + this.configEditor.on('inputRead', (cm, change) => { + if (change.text[0].match(/[\w$.]/)) { + this.configEditor.showHint({ + completeSingle: false + }); + } + }); this.$languageTabs = document.getElementById("language-tabs"); this.$errors = document.getElementById("errors"); } + /** + * Custom hint function for HTML ESLint rules + * @param {CodeMirror.Editor} cm + * @returns {Object|null} + */ + getHtmlEslintHints(cm) { + const cur = cm.getCursor(); + const token = cm.getTokenAt(cur); + const start = token.start; + const end = cur.ch; + const line = cm.getLine(cur.line); + + // Get the text before cursor + const textBefore = line.substring(0, cur.ch); + + // Check if we're in a context where rule names should be suggested + const ruleContext = HTML_REG.test(textBefore) || + RULES_REG.test(cm.getValue().substring(0, cm.indexFromPos(cur))); + if (!ruleContext) { + return null; + } + + + // Extract the current word being typed + const word = token.string.replace(/^["']|["']$/g, ''); + // Filter rules based on what's being typed + const suggestions = HTML_ESLINT_RULES + .filter(rule => rule.includes(word.toLowerCase())) + .map(rule => ({ + text: `"${rule}"`, + displayText: rule + })); + + if (suggestions.length === 0) { + return null; + } + + return { + list: suggestions, + from: CodeMirror.Pos(cur.line, start), + to: CodeMirror.Pos(cur.line, end) + }; + } + /** * @param {string} content */ From add1fa51ee916ff7d965b4a11e1effb105ad9a49 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 22 Sep 2025 23:41:07 +0900 Subject: [PATCH 2/2] Update view.js --- .../website/src/scripts/playground/view.js | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/packages/website/src/scripts/playground/view.js b/packages/website/src/scripts/playground/view.js index 0272e203..2749c745 100644 --- a/packages/website/src/scripts/playground/view.js +++ b/packages/website/src/scripts/playground/view.js @@ -24,6 +24,10 @@ const HTML_ESLINT_RULES = Object.keys(rules).map(rule => `@html-eslint/${rule}`) const HTML_REG = /"@html-eslint\//; const RULES_REG = /"rules"[\s\S]*?"@html-eslint\//; +// ESLint severity values for autocomplete +const ESLINT_SEVERITIES = ["error", "warn", "off"]; +const RULES_VALUE_REG = /"@html-eslint\/[^"]*":\s*/; + export class View { constructor() { /** @@ -84,22 +88,38 @@ export class View { const textBefore = line.substring(0, cur.ch); // Check if we're in a context where rule names should be suggested - const ruleContext = HTML_REG.test(textBefore) || - RULES_REG.test(cm.getValue().substring(0, cm.indexFromPos(cur))); - if (!ruleContext) { + const ruleNameContext = HTML_REG.test(textBefore) || + RULES_REG.test(cm.getValue().substring(0, cm.indexFromPos(cur))); + + const ruleValueContext = RULES_VALUE_REG.test(textBefore); + + if (!ruleNameContext && !ruleValueContext) { return null; } - - // Extract the current word being typed + let suggestions = []; const word = token.string.replace(/^["']|["']$/g, ''); - // Filter rules based on what's being typed - const suggestions = HTML_ESLINT_RULES - .filter(rule => rule.includes(word.toLowerCase())) - .map(rule => ({ - text: `"${rule}"`, - displayText: rule - })); + + if (ruleValueContext) { + // Provide ESLint severity suggestions + suggestions = ESLINT_SEVERITIES + .filter(severity => { + const severityStr = severity.toString(); + return severityStr.toLowerCase().includes(word.toLowerCase()); + }) + .map(severity => ({ + text: typeof severity === 'string' ? `"${severity}"` : severity.toString(), + displayText: severity.toString() + })); + } else if (ruleNameContext) { + // Provide rule name suggestions + suggestions = HTML_ESLINT_RULES + .filter(rule => rule.includes(word.toLowerCase())) + .map(rule => ({ + text: `"${rule}"`, + displayText: rule + })); + } if (suggestions.length === 0) { return null;