@@ -12,6 +12,39 @@ import {
1212} from './languageConfig.js' ;
1313
1414export function registerCustomLanguage ( monaco ) {
15+ // Helper: check if the entire model already contains any 'page' or 'show' at line start
16+ function modelHasPageOrShow ( model ) {
17+ if ( ! model ) return false ;
18+ const text = model . getValue ( ) ;
19+ // Check per line to ensure we only match commands starting a line (ignoring leading spaces)
20+ return text . split ( '\n' ) . some ( line => / ^ \s * ( p a g e | s h o w ) \b / . test ( line ) ) ;
21+ }
22+
23+ // Helper: build insert text with optional page/show trailer based on typeDocumentation.insertTextName
24+ function buildInsertTextWithPageShow ( baseInsertText , componentType , model ) {
25+ try {
26+ if ( ! model || modelHasPageOrShow ( model ) ) {
27+ return baseInsertText ;
28+ }
29+ const nameHint = typeDocumentation ?. [ componentType ] ?. insertTextName || 'item' ;
30+
31+ // Determine if baseInsertText ends with a closing brace at end of string (possibly with whitespace)
32+ const trimmed = baseInsertText . trimEnd ( ) ;
33+ const endsWithBrace = / \} \s * $ / . test ( trimmed ) ;
34+
35+ // Ensure a single trailing newline after the definition, then add page and show lines
36+ const newline = '\n' ;
37+ const suffix = `${ newline } ${ newline } page${ newline } show ${ nameHint } ` ;
38+
39+ if ( endsWithBrace ) {
40+ // If base already places cursor markers like $0 after brace, preserve them by appending after
41+ return `${ baseInsertText } ${ suffix } ` ;
42+ }
43+ return `${ baseInsertText } ${ suffix } ` ;
44+ } catch ( _ ) {
45+ return baseInsertText ;
46+ }
47+ }
1548 // Prevent multiple registrations
1649 if ( window . customLangRegistered ) {
1750 return ;
@@ -1307,7 +1340,11 @@ export function registerCustomLanguage(monaco) {
13071340 suggestions . push ( {
13081341 label : component ,
13091342 kind : monaco . languages . CompletionItemKind . Class ,
1310- insertText : componentDocumentation . insertText || `${ component } \${1:variableName} = {\n\t\${2:}\n}` ,
1343+ insertText : buildInsertTextWithPageShow (
1344+ componentDocumentation . insertText || `${ component } \${1:variableName} = {\n\t\${2:}\n}` ,
1345+ component ,
1346+ model
1347+ ) ,
13111348 insertTextRules : monaco . languages . CompletionItemInsertTextRule . InsertAsSnippet ,
13121349 detail : 'Data structure type' ,
13131350 documentation : componentDocumentation . description || `${ component } data structure` ,
@@ -1967,48 +2004,25 @@ export function registerCustomLanguage(monaco) {
19672004
19682005 // For other fixes, we need a word
19692006 if ( ! word ) return { actions, dispose : ( ) => { } } ;
2007+ // Aggregate quick fixes
2008+ actions . push ( ...getAttributeQuickFixes ( line , word , range , model ) ) ;
2009+ actions . push ( ...getMethodQuickFixes ( line , word , range , model ) ) ;
2010+ actions . push ( ...getComponentQuickFixes ( line , word , range , model ) ) ;
2011+ actions . push ( ...getSyntaxQuickFixes ( line , word , range , model ) ) ;
2012+ actions . push ( ...getArrayMethodQuickFixes ( line , word , range , model ) ) ;
19702013
1971- // Quick fix for misspelled attributes
1972- const attributeFixes = getAttributeQuickFixes ( line , word , range , model ) ;
1973- actions . push ( ...attributeFixes ) ;
1974-
1975- // Quick fix for misspelled methods
1976- const methodFixes = getMethodQuickFixes ( line , word , range , model ) ;
1977- actions . push ( ...methodFixes ) ;
1978-
1979- // Quick fix for misspelled components
1980- const componentFixes = getComponentQuickFixes ( line , word , range , model ) ;
1981- actions . push ( ...componentFixes ) ;
1982-
1983- // Quick fix for common syntax errors
1984- const syntaxFixes = getSyntaxQuickFixes ( line , word , range , model ) ;
1985- actions . push ( ...syntaxFixes ) ;
1986-
1987- // Quick fix for array method misuse
1988- const arrayMethodFixes = getArrayMethodQuickFixes ( line , word , range , model ) ;
1989- actions . push ( ...arrayMethodFixes ) ;
2014+ return { actions, dispose : ( ) => { } } ;
19902015
1991- return {
1992- actions : actions ,
1993- dispose : ( ) => { }
1994- } ;
2016+ // Leverage helper functions defined below
19952017 }
19962018 } ) ;
19972019
1998- // Helper function to calculate Levenshtein distance
2020+ // Levenshtein distance helper
19992021 function levenshteinDistance ( a , b ) {
2000- if ( a . length === 0 ) return b . length ;
2001- if ( b . length === 0 ) return a . length ;
2002-
2003- const matrix = [ ] ;
2004- for ( let i = 0 ; i <= b . length ; i ++ ) {
2005- matrix [ i ] = [ i ] ;
2006- }
2007- for ( let j = 0 ; j <= a . length ; j ++ ) {
2008- matrix [ 0 ] [ j ] = j ;
2009- }
2010-
2022+ const matrix = Array . from ( { length : b . length + 1 } , ( ) => new Array ( a . length + 1 ) . fill ( 0 ) ) ;
2023+ for ( let j = 0 ; j <= a . length ; j ++ ) matrix [ 0 ] [ j ] = j ;
20112024 for ( let i = 1 ; i <= b . length ; i ++ ) {
2025+ matrix [ i ] [ 0 ] = i ;
20122026 for ( let j = 1 ; j <= a . length ; j ++ ) {
20132027 if ( b . charAt ( i - 1 ) === a . charAt ( j - 1 ) ) {
20142028 matrix [ i ] [ j ] = matrix [ i - 1 ] [ j - 1 ] ;
@@ -2040,12 +2054,12 @@ export function registerCustomLanguage(monaco) {
20402054 // Quick fixes for misspelled attributes
20412055 function getAttributeQuickFixes ( line , word , range , model ) {
20422056 const actions = [ ] ;
2043- const currentWord = word . word ;
2044-
2057+ const currentWord = word ?. word ;
2058+ if ( ! currentWord ) return actions ;
2059+
20452060 // Check if this looks like an attribute (followed by colon)
20462061 if ( line . includes ( `${ currentWord } :` ) ) {
20472062 const suggestions = findClosestMatches ( currentWord , languageConfig . attributes ) ;
2048-
20492063 suggestions . forEach ( suggestion => {
20502064 actions . push ( {
20512065 title : `Change "${ currentWord } " to "${ suggestion } "` ,
@@ -2054,20 +2068,223 @@ export function registerCustomLanguage(monaco) {
20542068 edit : {
20552069 edits : [ {
20562070 resource : model . uri ,
2057- versionId : model . getVersionId ( ) ,
2058- textEdits : [ {
2071+ edit : {
20592072 range : new monaco . Range (
20602073 range . startLineNumber , word . startColumn ,
20612074 range . startLineNumber , word . endColumn
20622075 ) ,
20632076 text : suggestion
2077+ }
2078+ } ]
2079+ }
2080+ } ) ;
2081+ } ) ;
2082+ }
2083+
2084+ return actions ;
2085+ }
2086+
2087+ // Quick fixes for misspelled methods
2088+ function getMethodQuickFixes ( line , word , range , model ) {
2089+ const actions = [ ] ;
2090+ const currentWord = word . word ;
2091+ // Check if this looks like a method call (preceded by dot)
2092+ const beforeWord = line . substring ( 0 , word . startColumn - 1 ) ;
2093+ const methodCallMatch = beforeWord . match ( / ( \w + ) \. $ / ) ;
2094+ if ( methodCallMatch ) {
2095+ const variableName = methodCallMatch [ 1 ] ;
2096+ const { variableTypes } = parseCache . getCachedData ( model , {
2097+ lineNumber : range . startLineNumber ,
2098+ column : word . startColumn
2099+ } ) ;
2100+ const varType = variableTypes [ variableName ] ;
2101+ if ( varType ) {
2102+ const availableMethods = getMethodsForType ( varType ) ;
2103+ const suggestions = findClosestMatches ( currentWord , availableMethods ) ;
2104+ suggestions . forEach ( suggestion => {
2105+ actions . push ( {
2106+ title : `Change "${ currentWord } " to "${ suggestion } "` ,
2107+ kind : 'quickfix' ,
2108+ diagnostics : [ ] ,
2109+ edit : {
2110+ edits : [ {
2111+ resource : model . uri ,
2112+ textEdit : {
2113+ range : new monaco . Range (
2114+ range . startLineNumber , word . startColumn ,
2115+ range . startLineNumber , word . endColumn
2116+ ) ,
2117+ text : suggestion
2118+ }
20642119 } ]
2120+ }
2121+ } ) ;
2122+ } ) ;
2123+ }
2124+ }
2125+ return actions ;
2126+ }
2127+
2128+ // Quick fixes for misspelled components
2129+ function getComponentQuickFixes ( line , word , range , model ) {
2130+ const actions = [ ] ;
2131+ const currentWord = word . word ;
2132+ // Check if this looks like a component declaration or an unknown type token
2133+ const looksLikeDeclaration = line . match ( / ^ \s * \w + \s + \w + \s * = \s * \{ / ) ;
2134+ const unknownTypeToken = line . includes ( `${ currentWord } ` ) && ! languageConfig . components . includes ( currentWord ) ;
2135+ if ( looksLikeDeclaration || unknownTypeToken ) {
2136+ const suggestions = findClosestMatches ( currentWord , languageConfig . components ) ;
2137+ suggestions . forEach ( suggestion => {
2138+ actions . push ( {
2139+ title : `Change "${ currentWord } " to "${ suggestion } "` ,
2140+ kind : 'quickfix' ,
2141+ diagnostics : [ ] ,
2142+ edit : {
2143+ edits : [ {
2144+ resource : model . uri ,
2145+ edit : {
2146+ range : new monaco . Range (
2147+ range . startLineNumber , word . startColumn ,
2148+ range . startLineNumber , word . endColumn
2149+ ) ,
2150+ text : suggestion
2151+ }
2152+ } ]
2153+ }
2154+ } ) ;
2155+ } ) ;
2156+ }
2157+ return actions ;
2158+ }
2159+
2160+ // Quick fixes for common syntax errors
2161+ function getSyntaxQuickFixes ( line , word , range , model ) {
2162+ const actions = [ ] ;
2163+ const trimmedLine = line . trim ( ) ;
2164+ // Fix missing colon after attribute
2165+ if ( trimmedLine . match ( / ^ \s * \w + \s + [ ^ : = ] / ) && languageConfig . attributes . includes ( word . word ) ) {
2166+ actions . push ( {
2167+ title : `Add colon after "${ word . word } "` ,
2168+ kind : 'quickfix' ,
2169+ diagnostics : [ ] ,
2170+ edit : {
2171+ edits : [ {
2172+ resource : model . uri ,
2173+ edit : {
2174+ range : new monaco . Range (
2175+ range . startLineNumber , word . endColumn ,
2176+ range . startLineNumber , word . endColumn
2177+ ) ,
2178+ text : ':'
2179+ }
2180+ } ]
2181+ }
2182+ } ) ;
2183+ }
2184+
2185+ // Fix missing equals sign in component declaration
2186+ if ( trimmedLine . match ( / ^ \s * \w + \s + \w + \s * \{ / ) && languageConfig . components . includes ( word . word ) ) {
2187+ const braceIndex = line . indexOf ( '{' ) ;
2188+ if ( braceIndex > 0 ) {
2189+ actions . push ( {
2190+ title : `Add "= " before "{"` ,
2191+ kind : 'quickfix' ,
2192+ diagnostics : [ ] ,
2193+ edit : {
2194+ edits : [ {
2195+ resource : model . uri ,
2196+ edit : {
2197+ range : new monaco . Range (
2198+ range . startLineNumber , braceIndex ,
2199+ range . startLineNumber , braceIndex
2200+ ) ,
2201+ text : '= '
2202+ }
2203+ } ]
2204+ }
2205+ } ) ;
2206+ }
2207+ }
2208+
2209+ // Fix missing parentheses in method calls
2210+ if ( line . includes ( `${ word . word } .` ) || line . includes ( `.${ word . word } ` ) ) {
2211+ const all = staticCache . getAllMethods ( ) ;
2212+ if ( all . includes ( word . word ) && ! line . includes ( `${ word . word } (` ) ) {
2213+ actions . push ( {
2214+ title : `Add parentheses to "${ word . word } " method call` ,
2215+ kind : 'quickfix' ,
2216+ diagnostics : [ ] ,
2217+ edit : {
2218+ edits : [ {
2219+ resource : model . uri ,
2220+ edit : {
2221+ range : new monaco . Range (
2222+ range . startLineNumber , word . endColumn ,
2223+ range . startLineNumber , word . endColumn
2224+ ) ,
2225+ text : '()'
2226+ }
2227+ } ]
2228+ }
2229+ } ) ;
2230+ }
2231+ }
2232+
2233+ // Fix common typos in keywords
2234+ const keywordSuggestions = findClosestMatches ( word . word , languageConfig . keywords ) ;
2235+ if ( keywordSuggestions . length > 0 && ! languageConfig . keywords . includes ( word . word ) ) {
2236+ keywordSuggestions . forEach ( suggestion => {
2237+ actions . push ( {
2238+ title : `Change "${ word . word } " to "${ suggestion } "` ,
2239+ kind : 'quickfix' ,
2240+ diagnostics : [ ] ,
2241+ edit : {
2242+ edits : [ {
2243+ resource : model . uri ,
2244+ edit : {
2245+ range : new monaco . Range (
2246+ range . startLineNumber , word . startColumn ,
2247+ range . startLineNumber , word . endColumn
2248+ ) ,
2249+ text : suggestion
2250+ }
2251+ } ]
2252+ }
2253+ } ) ;
2254+ } ) ;
2255+ }
2256+ return actions ;
2257+ }
2258+
2259+ // NEW: Quick fixes for array method misuse
2260+ function getArrayMethodQuickFixes ( line , word , range , model ) {
2261+ const actions = [ ] ;
2262+ const currentWord = word . word ;
2263+ // Check if this is a method call with parameters
2264+ const methodCallMatch = line . match ( new RegExp ( `(\\w+)\\.(${ currentWord } )\\s*\\(([^)]*)\\)` ) ) ;
2265+ if ( methodCallMatch ) {
2266+ const [ , , methodName , params ] = methodCallMatch ;
2267+ const suggestions = getArrayMethodSuggestions ( methodName , params ) ;
2268+ suggestions . forEach ( suggestion => {
2269+ actions . push ( {
2270+ title : suggestion . title ,
2271+ kind : 'quickfix' ,
2272+ diagnostics : [ ] ,
2273+ edit : {
2274+ edits : [ {
2275+ resource : model . uri ,
2276+ edit : {
2277+ range : new monaco . Range (
2278+ range . startLineNumber , word . startColumn ,
2279+ range . startLineNumber , word . endColumn
2280+ ) ,
2281+ text : suggestion . replacement
2282+ }
20652283 } ]
20662284 }
20672285 } ) ;
20682286 } ) ;
20692287 }
2070-
20712288 return actions ;
20722289 }
20732290
0 commit comments