Skip to content

Commit ab575d3

Browse files
authored
Merge pull request #56 from ETH-PEACH-Lab/18-parse-error-when-changing-a-number-in-an-example
fix: Update newline matching in lexer to support both LF and CRLF
2 parents a793676 + 6f25de7 commit ab575d3

6 files changed

Lines changed: 289 additions & 53 deletions

File tree

src/compiler/compiler.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2070,7 +2070,7 @@ export default function convertParsedDSLtoMermaid(parsedDSLOriginal) {
20702070
function preCheck(parsedDSL) {
20712071
if (!parsedDSL || !parsedDSL.defs || !parsedDSL.cmds ||
20722072
(Array.isArray(parsedDSL.defs) && Array.isArray(parsedDSL.cmds) &&
2073-
parsedDSL.defs.length === 0 && parsedDSL.cmds.length === 0)) {
2073+
parsedDSL.defs.length !== 0 && parsedDSL.cmds.length === 0)) {
20742074
throw new Error("Nothing to show\n\nPlease define an object and a page.\nThen show it using the 'show' command");
20752075
}
20762076

src/components/customLang.js

Lines changed: 259 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,39 @@ import {
1212
} from './languageConfig.js';
1313

1414
export 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*(page|show)\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

Comments
 (0)