Add Elixir language support to the editor (#13)

This commit is contained in:
Jonatan Kłosko 2021-01-22 23:27:25 +01:00 committed by GitHub
parent b8df31e1ae
commit 8e4b4af60c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 171 additions and 1 deletions

View file

@ -0,0 +1,37 @@
/**
* Defines Elixir traits to enable various editor features,
* like automatic bracket insertion and indentation.
*/
const ElixirLanguageConfiguration = {
comments: {
lineComment: "#",
},
brackets: [
["{", "}"],
["[", "]"],
["(", ")"],
],
surroundingPairs: [
["{", "}"],
["[", "]"],
["(", ")"],
["'", "'"],
['"', '"'],
],
autoClosingPairs: [
{ open: "'", close: "'", notIn: ["string", "comment"] },
{ open: '"', close: '"', notIn: ["comment"] },
{ open: '"""', close: '"""' },
{ open: "`", close: "`", notIn: ["string", "comment"] },
{ open: "(", close: ")" },
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "<<", close: ">>" },
],
indentationRules: {
increaseIndentPattern: /^\s*(after|else|catch|rescue|fn|[^#]*(do|<\-|\->|\{|\[|\=))\s*$/,
decreaseIndentPattern: /^\s*((\}|\])\s*$|(after|else|catch|rescue|end)\b)/,
},
};
export default ElixirLanguageConfiguration;

View file

@ -0,0 +1,118 @@
/**
* Defines custom auto-formatting behavior for Elixir.
*
* The provider is triggered when the user makes edits
* and it may instruct the editor to apply some additional changes.
*/
const ElixirOnTypeFormattingEditProvider = {
autoFormatTriggerCharacters: ["\n"],
provideOnTypeFormattingEdits(model, position, char, options, token) {
if (char === "\n") {
return closingEndTextEdits(model, position);
}
return [];
},
};
function closingEndTextEdits(model, position) {
const lines = model.getLinesContent();
const lineIndex = position.lineNumber - 1;
const line = lines[lineIndex];
const prevLine = lines[lineIndex - 1];
const prevIndentation = indentation(prevLine);
if (shouldInsertClosingEnd(lines, lineIndex)) {
// If this is the last line or the line is not empty,
// we have to insert a newline at the current position.
// Otherwise we prefer to explicitly insert the closing end
// in the next line, as it preserves current cursor position.
const shouldInsertInNextLine =
position.lineNumber < lines.length && isBlank(line);
const textEdit = insertClosingEndTextEdit(
position,
prevIndentation,
shouldInsertInNextLine
);
return [textEdit];
}
return [];
}
function shouldInsertClosingEnd(lines, lineIndex) {
const prevLine = lines[lineIndex - 1];
const prevIndentation = indentation(prevLine);
const prevTokens = tokens(prevLine);
if (
last(prevTokens) === "do" ||
(prevTokens.includes("fn") && last(prevTokens) === "->")
) {
const nextLineWithSameIndentation = lines
.slice(lineIndex + 1)
.filter(line => !isBlank(line))
.find((line) => indentation(line) === prevIndentation);
if (nextLineWithSameIndentation) {
const [firstToken] = tokens(nextLineWithSameIndentation);
if (["after", "else", "catch", "rescue", "end"].includes(firstToken)) {
return false;
}
}
return true;
}
return false;
}
function insertClosingEndTextEdit(
position,
indentation,
shouldInsertInNextLine
) {
if (shouldInsertInNextLine) {
return {
range: new monaco.Range(
position.lineNumber + 1,
1,
position.lineNumber + 1,
1
),
text: `${indentation}end\n`,
};
} else {
return {
range: new monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column
),
text: `\n${indentation}end`,
};
}
}
function indentation(line) {
const [indentation] = line.match(/^\s*/);
return indentation;
}
function tokens(line) {
return line.replace(/#.*/, "").match(/->|[\w:]+/g) || [];
}
function last(list) {
return list[list.length - 1];
}
function isBlank(string) {
return string.trim() === "";
}
export default ElixirOnTypeFormattingEditProvider;

View file

@ -1,5 +1,20 @@
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
import ElixirLanguageConfiguration from "./elixir/language_configuration";
import ElixirOnTypeFormattingEditProvider from "./elixir/on_type_formatting_edit_provider";
// TODO: add Elixir language definition
// Register the Elixir language and add relevant configuration
monaco.languages.register({ id: "elixir" });
monaco.languages.setLanguageConfiguration(
"elixir",
ElixirLanguageConfiguration
);
monaco.languages.registerOnTypeFormattingEditProvider(
"elixir",
ElixirOnTypeFormattingEditProvider
);
// TODO: add Monarch tokenizer for syntax highlighting
export default monaco;