Skip to content

Commit

Permalink
Indent-based folds for YAML editor (#21966)
Browse files Browse the repository at this point in the history
* Indent-based folds for YAML editor

* adding compartment

* code review
  • Loading branch information
naps62 authored Sep 25, 2024
1 parent 4bd27e5 commit e687dda
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 4 deletions.
23 changes: 20 additions & 3 deletions src/components/ha-code-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,12 @@ export class HaCodeEditor extends ReactiveElement {
const transactions: TransactionSpec[] = [];
if (changedProps.has("mode")) {
transactions.push({
effects: this._loadedCodeMirror!.langCompartment!.reconfigure(
this._mode
),
effects: [
this._loadedCodeMirror!.langCompartment!.reconfigure(this._mode),
this._loadedCodeMirror!.foldingCompartment.reconfigure(
this._getFoldingExtensions()
),
],
});
}
if (changedProps.has("readOnly")) {
Expand Down Expand Up @@ -194,6 +197,9 @@ export class HaCodeEditor extends ReactiveElement {
this.linewrap ? this._loadedCodeMirror.EditorView.lineWrapping : []
),
this._loadedCodeMirror.EditorView.updateListener.of(this._onUpdate),
this._loadedCodeMirror.foldingCompartment.of(
this._getFoldingExtensions()
),
];

if (!this.readOnly) {
Expand Down Expand Up @@ -311,6 +317,17 @@ export class HaCodeEditor extends ReactiveElement {
fireEvent(this, "value-changed", { value: this._value });
};

private _getFoldingExtensions = (): Extension => {
if (this.mode === "yaml") {
return [
this._loadedCodeMirror!.foldGutter(),
this._loadedCodeMirror!.foldingOnIndent,
];
}

return [];
};

static get styles(): CSSResultGroup {
return css`
:host(.error-state) .cm-gutters {
Expand Down
43 changes: 42 additions & 1 deletion src/resources/codemirror.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { indentLess, indentMore } from "@codemirror/commands";
import {
foldService,
HighlightStyle,
StreamLanguage,
syntaxHighlighting,
Expand All @@ -12,7 +13,7 @@ import { tags } from "@lezer/highlight";

export { autocompletion } from "@codemirror/autocomplete";
export { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
export { highlightingFor } from "@codemirror/language";
export { highlightingFor, foldGutter } from "@codemirror/language";
export { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
export { EditorState } from "@codemirror/state";
export {
Expand All @@ -34,6 +35,7 @@ export const langs = {
export const langCompartment = new Compartment();
export const readonlyCompartment = new Compartment();
export const linewrapCompartment = new Compartment();
export const foldingCompartment = new Compartment();

export const tabKeyBindings: KeyBinding[] = [
{ key: "Tab", run: indentMore },
Expand Down Expand Up @@ -270,3 +272,42 @@ const haHighlightStyle = HighlightStyle.define([
]);

export const haSyntaxHighlighting = syntaxHighlighting(haHighlightStyle);

// A folding service for indent-based languages such as YAML.
export const foldingOnIndent = foldService.of((state, from, to) => {
const line = state.doc.lineAt(from);
const lineCount = state.doc.lines;
const indent = line.text.search(/\S|$/); // Indent level of the first line
let foldStart = from; // Start of the fold
let foldEnd = to; // End of the fold

// Check if the next line is on a deeper indent level
// If so, continue subsequent lines
// If not, go on with the foldEnd
let nextLine = line;
while (nextLine.number < lineCount) {
nextLine = state.doc.line(nextLine.number + 1); // Next line
const nextIndent = nextLine.text.search(/\S|$/); // Indent level of the next line

// If the next line is on a deeper indent level, add it to the fold
if (nextIndent > indent) {
// include this line in the fold and continue
foldEnd = nextLine.to;
} else {
// If the next line is not on a deeper indent level, we found the end of the region
break;
}
}

// Don't create fold if it's a single line
if (state.doc.lineAt(foldStart).number === state.doc.lineAt(foldEnd).number) {
return null;
}

// Set the fold start to the end of the first line
// With this, the fold will not include the first line
foldStart = line.to;

// Return a fold that covers the entire indent level
return { from: foldStart, to: foldEnd };
});

0 comments on commit e687dda

Please sign in to comment.