mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-11-11 06:32:23 +08:00
Add Elixir language support to the editor (#13)
This commit is contained in:
parent
b8df31e1ae
commit
8e4b4af60c
3 changed files with 171 additions and 1 deletions
37
assets/js/editor/elixir/language_configuration.js
Normal file
37
assets/js/editor/elixir/language_configuration.js
Normal 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;
|
||||||
118
assets/js/editor/elixir/on_type_formatting_edit_provider.js
Normal file
118
assets/js/editor/elixir/on_type_formatting_edit_provider.js
Normal 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;
|
||||||
|
|
@ -1,5 +1,20 @@
|
||||||
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
|
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;
|
export default monaco;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue