Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Indent-based folds for YAML editor #21966

Merged
merged 4 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/components/ha-code-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,14 @@ export class HaCodeEditor extends ReactiveElement {
}
const transactions: TransactionSpec[] = [];
if (changedProps.has("mode")) {
// TODO: not sure how to handle things here
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left this as a note so I wouldn't forget. I probably need some guidance here. Not sure if/how to handle mode changes
can mode changes even occur at all? I assume we need to check if mode changed to/from yaml, and disable/enable the added extensions accordingly, but not sure how to go about it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would need to make it a compartment, so you can reconfigure it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. will take a look at that, thanks

transactions.push({
effects: this._loadedCodeMirror!.langCompartment!.reconfigure(
this._mode
),
effects: [
this._loadedCodeMirror!.langCompartment!.reconfigure(this._mode),
this._loadedCodeMirror!.foldingCompartment.reconfigure(
this._getFoldingExtensions()
),
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reconfiguring folding compartment on mode change looks good, but consider removing the TODO comment.

I see that you have implemented the suggestion to reconfigure the folding compartment alongside the language compartment when the mode changes. This looks good to me.

However, the TODO comment is still present, indicating that you might still be unsure about the handling of mode changes. If you are satisfied with the current implementation, consider removing the TODO comment to avoid confusion.

If you need further guidance on handling mode changes, please let me know, and I'll be happy to assist you.

});
}
if (changedProps.has("readOnly")) {
Expand Down Expand Up @@ -194,6 +198,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 +318,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 };
});
Loading