mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 02:39:28 +00:00
online_editor: Make document outline highlight where the cursor is
Make the document outline set the "active" class on the element the cursor is currently in. Include some css to make the browser show what is active.
This commit is contained in:
parent
1b58ab93f1
commit
fe38ae9762
4 changed files with 180 additions and 11 deletions
|
@ -10,7 +10,12 @@ import {
|
|||
DefinitionPosition,
|
||||
} from "./lsp_integration";
|
||||
import { FilterProxyReader } from "./proxy";
|
||||
import { TextPosition, TextRange } from "./text";
|
||||
import {
|
||||
TextPosition,
|
||||
TextRange,
|
||||
PositionChangeCallback,
|
||||
DocumentAndTextPosition,
|
||||
} from "./text";
|
||||
|
||||
import { BoxLayout, TabBar, Title, Widget } from "@lumino/widgets";
|
||||
import { Message as LuminoMessage } from "@lumino/messaging";
|
||||
|
@ -118,6 +123,12 @@ class EditorPaneWidget extends Widget {
|
|||
#disposables: monaco.IDisposable[] = [];
|
||||
#current_properties = "";
|
||||
|
||||
onPositionChangeCallback: PositionChangeCallback = (
|
||||
_uri: string,
|
||||
_pos: TextPosition,
|
||||
) => {
|
||||
return;
|
||||
};
|
||||
onNewPropertyData: PropertyDataNotifier | null = null;
|
||||
|
||||
readonly editor_ready: Promise<void>;
|
||||
|
@ -249,6 +260,16 @@ class EditorPaneWidget extends Widget {
|
|||
}
|
||||
}
|
||||
|
||||
get position(): DocumentAndTextPosition {
|
||||
const uri = this.#editor?.getModel()?.uri.toString() ?? "";
|
||||
const position = this.#editor?.getPosition() ?? {
|
||||
lineNumber: 1,
|
||||
column: 1,
|
||||
};
|
||||
|
||||
return { uri: uri, position: position };
|
||||
}
|
||||
|
||||
private add_model_listener(model: monaco.editor.ITextModel) {
|
||||
const uri = model.uri;
|
||||
model.onDidChangeContent(() => {
|
||||
|
@ -392,6 +413,12 @@ class EditorPaneWidget extends Widget {
|
|||
(pos: monaco.editor.ICursorPositionChangedEvent) => {
|
||||
const model = editor.getModel();
|
||||
if (model != null) {
|
||||
this.onPositionChangeCallback({
|
||||
uri: model.uri.toString(),
|
||||
position: pos.position,
|
||||
});
|
||||
|
||||
// TODO: Move this into an onPositionChangeCallback?
|
||||
const offset =
|
||||
model.getOffsetAt(pos.position) -
|
||||
model.getOffsetAt({
|
||||
|
@ -742,6 +769,14 @@ export class EditorWidget extends Widget {
|
|||
textAt(handle: unknown, start: number, end: number): string {
|
||||
return this.#editor.textAt(handle, start, end);
|
||||
}
|
||||
|
||||
set onPositionChange(cb: PositionChangeCallback) {
|
||||
this.#editor.onPositionChangeCallback = cb;
|
||||
}
|
||||
|
||||
get position(): DocumentAndTextPosition {
|
||||
return this.#editor.position;
|
||||
}
|
||||
}
|
||||
|
||||
class ModelBindingTextProvider implements BindingTextProvider {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { OutlineWidget } from "./outline_widget";
|
|||
import { PropertiesWidget } from "./properties_widget";
|
||||
import { WelcomeWidget } from "./welcome_widget";
|
||||
|
||||
import { TextPosition, TextRange } from "./text";
|
||||
import { DocumentAndTextPosition, TextPosition, TextRange } from "./text";
|
||||
|
||||
const commands = new CommandRegistry();
|
||||
|
||||
|
@ -312,10 +312,14 @@ function main() {
|
|||
],
|
||||
[
|
||||
() => {
|
||||
const outline = new OutlineWidget(() => {
|
||||
const outline = new OutlineWidget(editor.position, () => {
|
||||
return [editor.language_client, editor.current_text_document_uri];
|
||||
});
|
||||
|
||||
editor.onPositionChange = (position: DocumentAndTextPosition) => {
|
||||
outline.position_changed(position);
|
||||
};
|
||||
|
||||
outline.on_goto_position = (
|
||||
uri: string,
|
||||
pos: TextPosition | TextRange,
|
||||
|
|
|
@ -6,8 +6,14 @@
|
|||
import { Message } from "@lumino/messaging";
|
||||
import { Widget } from "@lumino/widgets";
|
||||
|
||||
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import { MonacoLanguageClient } from "monaco-languageclient";
|
||||
import { GotoPositionCallback, TextPosition, TextRange } from "./text";
|
||||
import {
|
||||
DocumentAndTextPosition,
|
||||
GotoPositionCallback,
|
||||
TextPosition,
|
||||
TextRange,
|
||||
} from "./text";
|
||||
|
||||
import {
|
||||
DocumentSymbolRequest,
|
||||
|
@ -47,12 +53,26 @@ const SYMBOL_KIND_MAP = new Map<SymbolKind, string>([
|
|||
[SymbolKind.TypeParameter, "kind-type-parameter"],
|
||||
]);
|
||||
|
||||
const ACTIVE_ELEMENT_CLASS = "active";
|
||||
|
||||
interface PositionData {
|
||||
range: TextRange;
|
||||
element: HTMLElement;
|
||||
children: PositionData[];
|
||||
}
|
||||
|
||||
interface OutlineData {
|
||||
uri: string;
|
||||
data: PositionData[];
|
||||
}
|
||||
|
||||
function set_data(
|
||||
data: DocumentSymbol[],
|
||||
parent: HTMLUListElement,
|
||||
uri: string,
|
||||
goto_position: GotoPositionCallback,
|
||||
) {
|
||||
): PositionData[] {
|
||||
const pos_data = [];
|
||||
for (const d of data) {
|
||||
const row = document.createElement("li");
|
||||
row.className = "outline-element";
|
||||
|
@ -68,6 +88,18 @@ function set_data(
|
|||
row.classList.add(SYMBOL_KIND_MAP.get(d.kind) ?? "kind-unknown");
|
||||
|
||||
const span = document.createElement("span");
|
||||
|
||||
const current_pos_data = {
|
||||
range: {
|
||||
startLineNumber: d.range.start.line + 1,
|
||||
startColumn: d.range.start.character + 1,
|
||||
endLineNumber: d.range.end.line + 1,
|
||||
endColumn: d.range.end.character + 1,
|
||||
},
|
||||
element: span,
|
||||
children: [],
|
||||
} as PositionData;
|
||||
|
||||
span.innerText = d.name;
|
||||
span.addEventListener("click", () =>
|
||||
goto_position(uri, {
|
||||
|
@ -82,12 +114,38 @@ function set_data(
|
|||
|
||||
if (d.children != null) {
|
||||
const children_parent = document.createElement("ul");
|
||||
set_data(d.children, children_parent, uri, goto_position);
|
||||
current_pos_data.children = set_data(
|
||||
d.children,
|
||||
children_parent,
|
||||
uri,
|
||||
goto_position,
|
||||
);
|
||||
row.appendChild(children_parent);
|
||||
}
|
||||
|
||||
pos_data.push(current_pos_data);
|
||||
parent.appendChild(row);
|
||||
}
|
||||
|
||||
return pos_data;
|
||||
}
|
||||
|
||||
function deactivate_elements_and_find_to_activate(
|
||||
data: PositionData[],
|
||||
position: TextPosition,
|
||||
): HTMLElement | null {
|
||||
let to_activate = null;
|
||||
for (const d of data) {
|
||||
d.element.classList.remove(ACTIVE_ELEMENT_CLASS);
|
||||
const r = monaco.Range.lift(d.range);
|
||||
if (r.containsPosition(position)) {
|
||||
to_activate = d.element;
|
||||
}
|
||||
to_activate =
|
||||
deactivate_elements_and_find_to_activate(d.children, position) ??
|
||||
to_activate;
|
||||
}
|
||||
return to_activate;
|
||||
}
|
||||
|
||||
export class OutlineWidget extends Widget {
|
||||
|
@ -97,6 +155,9 @@ export class OutlineWidget extends Widget {
|
|||
console.log("Goto Position ignored:", uri, position);
|
||||
};
|
||||
|
||||
#outline: OutlineData | null = null;
|
||||
#cursor_position: DocumentAndTextPosition;
|
||||
|
||||
static createNode(): HTMLElement {
|
||||
const node = document.createElement("div");
|
||||
const content = document.createElement("div");
|
||||
|
@ -105,6 +166,7 @@ export class OutlineWidget extends Widget {
|
|||
}
|
||||
|
||||
constructor(
|
||||
cursor_position: DocumentAndTextPosition,
|
||||
callback: () => [MonacoLanguageClient | undefined, string | undefined],
|
||||
) {
|
||||
super({ node: OutlineWidget.createNode() });
|
||||
|
@ -135,12 +197,29 @@ export class OutlineWidget extends Widget {
|
|||
}
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
this.#cursor_position = cursor_position;
|
||||
}
|
||||
|
||||
set on_goto_position(callback: GotoPositionCallback) {
|
||||
this.#onGotoPosition = callback;
|
||||
}
|
||||
|
||||
position_changed(position: DocumentAndTextPosition) {
|
||||
if (this.#outline) {
|
||||
if (position.uri != this.#outline.uri) {
|
||||
// Document has changed, and we have no new data yet!
|
||||
this.clear_data();
|
||||
} else {
|
||||
deactivate_elements_and_find_to_activate(
|
||||
this.#outline.data,
|
||||
position.position,
|
||||
)?.classList.add(ACTIVE_ELEMENT_CLASS);
|
||||
}
|
||||
}
|
||||
this.#cursor_position = position;
|
||||
}
|
||||
|
||||
protected get contentNode(): HTMLDivElement {
|
||||
return this.node.getElementsByTagName("div")[0] as HTMLDivElement;
|
||||
}
|
||||
|
@ -161,17 +240,36 @@ export class OutlineWidget extends Widget {
|
|||
const content = document.createElement("ul");
|
||||
content.className = "outline-tree";
|
||||
|
||||
set_data(data as DocumentSymbol[], content, uri, this.#onGotoPosition);
|
||||
const pos_data = set_data(
|
||||
data as DocumentSymbol[],
|
||||
content,
|
||||
uri,
|
||||
this.#onGotoPosition,
|
||||
);
|
||||
|
||||
if (
|
||||
uri == this.#outline?.uri &&
|
||||
JSON.stringify(pos_data) == JSON.stringify(this.#outline?.data)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clear_data();
|
||||
|
||||
this.#outline = { uri: uri, data: pos_data };
|
||||
this.position_changed(this.#cursor_position); // re-highlight the expected element:-)
|
||||
|
||||
this.contentNode.appendChild(content);
|
||||
}
|
||||
|
||||
protected clear_data() {
|
||||
this.contentNode.innerText = "";
|
||||
|
||||
this.#outline = null;
|
||||
}
|
||||
|
||||
protected set_error(message: string) {
|
||||
this.clear_data();
|
||||
this.contentNode.innerHTML = '<div class="error">' + message + "</div>";
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,42 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.outline ul {
|
||||
padding-left: 1em;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.outline .outline-tree {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.outline .outline-tree {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.outline .outline-element {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.outline .outline-element > span {
|
||||
display: block;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.outline .kind-class.outline-element span:before {
|
||||
font-family: "FontAwesome";
|
||||
content: "\f1b2 ";
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.outline .outline-element .active {
|
||||
background: #3333ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.properties-editor .element-header {
|
||||
background: #3333ff;
|
||||
color: #ffffff;
|
||||
|
@ -104,10 +140,6 @@
|
|||
background-color: #b0b0b0;
|
||||
}
|
||||
|
||||
.help > div {
|
||||
background: #f1c4f1;
|
||||
}
|
||||
|
||||
#help-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue