-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add error linting to the editor (#203)
* fix: autocomplete substitute the entire line with correct case sensitive * feat: add first error linting test version * fix: remove unused dependencies from grammar package.json * fix(grammar): install @codemirror/lint * fix: allow permutations of parameters in 'send' * refactor(grammar): move lint part in a separate file * fix: slangroom comment char * feat: add linting with similarity suggestions for all slangroom statements * feat: add also plugin name to complete statements for easier match on plugin name * fix: slangroom comment char * feat: add autocomplete based on plugin suggestion * feat: add scenario to allowed lines --------- Co-authored-by: matteo-cristino <[email protected]> Co-authored-by: Matteo Cristino <[email protected]>
- Loading branch information
1 parent
e93af98
commit fa74b29
Showing
7 changed files
with
226 additions
and
55 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// SPDX-FileCopyrightText: 2024 Dyne.org foundation | ||
// | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
||
import { linter, Diagnostic } from "@codemirror/lint"; | ||
import { fullStatementTemplates } from "./complete_statement"; | ||
import { distance } from "fastest-levenshtein" | ||
|
||
// Helper function to extract the content inside quotes | ||
function extractContent(line: string): string[] { | ||
return [...line.matchAll(/'([^']*)'/g)].map(match => match[1]); | ||
} | ||
|
||
// Helper function to insert the content into the template | ||
function insertContent(template: string, content: string[]): string { | ||
let contentIndex = 0; | ||
return template.replace(/''/g, () => `'${content[contentIndex++] || ""}'`); | ||
} | ||
|
||
// Normalize a line (case-insensitive, remove extra spaces) | ||
function normalizeLine(line: string): string { | ||
|
||
const regex = /'[^']*'|[^'\s]+/g; | ||
|
||
return line.match(regex)?.map((word) => { | ||
if (word.startsWith("'") && word.endsWith("'")) { | ||
return word; | ||
} | ||
if (word === 'I' || word === 'i') { | ||
return word; | ||
} | ||
return word.toLowerCase(); | ||
}).join(' ').trim() || ''; | ||
} | ||
|
||
// Helper function to generate permutations of "send" phrases | ||
function generateSendPermutations(statement: string): string[] { | ||
const sendParts = statement.match(/send [^']+ ''/g) || []; | ||
if (sendParts.length <= 1) return [statement]; | ||
|
||
const permute = (arr: string[], result: string[] = []) => { | ||
if (arr.length === 0) return [result.join(' and ')]; | ||
let permutations: string[] = []; | ||
for (let i = 0; i < arr.length; i++) { | ||
const current = arr.slice(); | ||
const next = current.splice(i, 1); | ||
permutations = permutations.concat(permute(current, result.concat(next))); | ||
} | ||
return permutations; | ||
}; | ||
|
||
const permutedSendParts = permute(sendParts); | ||
return permutedSendParts.map((perm) => | ||
statement.replace(/(send [^']+ '')( and send [^']+ '')*/g, perm), | ||
); | ||
} | ||
|
||
|
||
function capitalize(statement: string): string { | ||
return statement.charAt(0).toUpperCase() + statement.slice(1); | ||
} | ||
|
||
// Helper function to find the most similar correct statements | ||
function findMostSimilarStatements(wrongStatement: string, correctStatements: string[]): string[] { | ||
const scores = correctStatements.map(template => { | ||
const normalizedTemplate = normalizeLine(template); | ||
return { | ||
statement: template, | ||
distance: distance(normalizedTemplate, wrongStatement) | ||
}; | ||
}); | ||
|
||
// Sort by similarity (smallest Levenshtein distance) | ||
scores.sort((a, b) => a.distance - b.distance); | ||
|
||
// Return the top 3 most similar statements | ||
return scores.slice(0, 3).map(score => capitalize(score.statement)); | ||
} | ||
|
||
const correctStatements = fullStatementTemplates.flatMap(template => { | ||
const normalizedTemplate = normalizeLine(template.displayLabel); | ||
return generateSendPermutations(normalizedTemplate); | ||
}); | ||
|
||
|
||
export const customLinter = linter((view) => { | ||
let diagnostics: Diagnostic[] = []; | ||
const doc = view.state.doc; | ||
const cursorLine = view.state.selection.main.head; | ||
const lineCount = doc.lines; | ||
|
||
for (let i = 1; i <= lineCount; i++) { | ||
const line = doc.line(i); | ||
|
||
// Ignore the line where the cursor is | ||
if (line.from <= cursorLine && cursorLine <= line.to) { | ||
continue; | ||
} | ||
|
||
const lineText = line.text.trim(); | ||
|
||
// Ignore empty lines and comment lines | ||
if (lineText === '' || lineText.startsWith('#') || lineText.toLowerCase().startsWith('scenario')) { | ||
continue; | ||
} | ||
|
||
const content = extractContent(lineText); | ||
|
||
const normalizedLine = normalizeLine(lineText); | ||
const modifiedStatements = correctStatements.map(template => { | ||
const updatedTemplate = insertContent(template, content); | ||
return updatedTemplate; | ||
}); | ||
|
||
const matchesStatement = modifiedStatements.includes(normalizedLine); | ||
|
||
if (!matchesStatement) { | ||
const mostSimilarStatements = findMostSimilarStatements(normalizedLine, modifiedStatements); | ||
|
||
diagnostics.push({ | ||
from: line.from, | ||
to: line.to, | ||
severity: 'error', | ||
message: 'Invalid statement, do you mean:', | ||
actions: mostSimilarStatements.map((statement) => ({ | ||
name: statement, | ||
apply(view, from, to) { | ||
view.dispatch({ | ||
changes: { from, to, insert: statement }, | ||
}); | ||
}, | ||
})), | ||
}); | ||
} | ||
} | ||
|
||
return diagnostics; | ||
}); | ||
|
This file contains 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
Oops, something went wrong.