Skip to content

Commit

Permalink
Add inline CSS errors
Browse files Browse the repository at this point in the history
  • Loading branch information
sonnyp committed Oct 17, 2022
1 parent 1438569 commit 7557ec8
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 4 deletions.
62 changes: 62 additions & 0 deletions src/Editor.js
Expand Up @@ -3,10 +3,13 @@ import GLib from "gi://GLib";
import Adw from "gi://Adw";
import Gtk from "gi://Gtk";
import Source from "gi://GtkSource";
import Pango from "gi://Pango";

import Template from "./editor.blp" assert { type: "uri" };

import { promiseTask } from "../troll/src/util.js";
import HoverProvider from "./HoverProvider.js";
import { getItersAtRange } from "./util.js";

const language_manager = Source.LanguageManager.get_default();
const scheme_manager = Source.StyleSchemeManager.get_default();
Expand All @@ -17,6 +20,10 @@ class EditorWindow extends Gtk.Window {
constructor({ application, default_style, file, text, onChange }) {
super({ application });

const provider = new HoverProvider();
this.provider = provider;
prepareSourceView({ source_view: this._source_view, provider });

this._buffer.set_language(language);
this._buffer.text = text;

Expand All @@ -32,6 +39,9 @@ class EditorWindow extends Gtk.Window {
location: file,
});

setTimeout(() => {
onChange(this._buffer.text);
});
this._buffer.connect("changed", () => {
onChange(this._buffer.text);
});
Expand Down Expand Up @@ -76,6 +86,26 @@ class EditorWindow extends Gtk.Window {
reset = () => {
this._buffer.text = this.default_style;
};

onCssProvider = (css_provider) => {
this.provider.diagnostics = [];
this._buffer.remove_tag_by_name(
"error",
this._buffer.get_start_iter(),
this._buffer.get_end_iter()
);
css_provider.connect("parsing-error", this.onParsingError);
};

onParsingError = (css_parser, section, error) => {
const diagnostic = getDiagnostic(section, error);
this.provider.diagnostics.push(diagnostic);
const [start_iter, end_iter] = getItersAtRange(
this._buffer,
diagnostic.range
);
this._buffer.apply_tag_by_name("error", start_iter, end_iter);
};
}

export default GObject.registerClass(
Expand All @@ -86,3 +116,35 @@ export default GObject.registerClass(
},
EditorWindow
);

function prepareSourceView({ source_view, provider }) {
const tag_table = source_view.buffer.get_tag_table();
const tag = new Gtk.TextTag({
name: "error",
underline: Pango.Underline.ERROR,
});
tag_table.add(tag);

const hover = source_view.get_hover();
// hover.hover_delay = 25;
hover.add_provider(provider);
}

// Converts a Gtk.CssSection and Gtk.CssError to an LSP diagnostic object
function getDiagnostic(section, error) {
const start_location = section.get_start_location();
const end_location = section.get_end_location();

const range = {
start: {
line: start_location.lines,
character: start_location.line_chars,
},
end: {
line: end_location.lines,
character: end_location.line_chars,
},
};

return { range, message: error.message };
}
88 changes: 88 additions & 0 deletions src/HoverProvider.js
@@ -0,0 +1,88 @@
import GObject from "gi://GObject";
import Gtk from "gi://Gtk";
import Source from "gi://GtkSource";

import { rangeEquals } from "./util.js";

class RetroHoverProvider extends GObject.Object {
constructor() {
super();
this.diagnostics = [];
}

findDiagnostics(context) {
const iter = new Gtk.TextIter();
context.get_iter(iter);

const line = iter.get_line();
// Looks like line_offset starts at 0
// Blueprint starts at 1
const character = iter.get_line_offset() + 1;

return findDiagnostics(this.diagnostics, { line, character });
}

showDiagnostics(display, diagnostics) {
const container = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 4,
});
container.add_css_class("hoverdisplay");
container.add_css_class("osd");
container.add_css_class("frame");

for (const { message } of diagnostics) {
const label = new Gtk.Label({
halign: Gtk.Align.START,
label: `${message}`,
});
label.add_css_class("body");
container.append(label);
}

display.append(container);
}

vfunc_populate(context, display) {
try {
const diagnostics = this.findDiagnostics(context);
if (diagnostics.length < 1) return false;
this.showDiagnostics(display, diagnostics);
} catch (err) {
logError(err);
return false;
}

return true;
}
}

function findDiagnostics(diagnostics, position) {
return diagnostics.filter((diagnostic) => {
return isDiagnosticInRange(diagnostic, position);
});
}

export function isDiagnosticInRange(diagnostic, { line, character }) {
const { start, end } = diagnostic.range;

// The tag is applied on the whole line
// when diagnostic start and end ranges are equals
if (rangeEquals(start, end) && line === start.line) return true;

if (line < start.line) return false;
if (line > end.line) return false;

return (
(line >= start.line && character >= start.character - 1) ||
(line <= end.line && character <= end.character + 1)
);
}

export default GObject.registerClass(
{
GTypeName: "RetroHoverProvider",
Implements: [Source.HoverProvider],
},
RetroHoverProvider
);
7 changes: 3 additions & 4 deletions src/Window.js
Expand Up @@ -85,10 +85,9 @@ class RetroWindow extends Adw.ApplicationWindow {
}

const css_provider = new Gtk.CssProvider();
// css_provider.connect("parsing-error", (self, section, error) => {
// const diagnostic = getDiagnostic(section, error);
// panel_style.handleDiagnostic(diagnostic);
// });
if (window_editor) {
window_editor?.onCssProvider(css_provider);
}
if (text) {
css_provider.load_from_data(text);
}
Expand Down
26 changes: 26 additions & 0 deletions src/util.js
@@ -0,0 +1,26 @@
export function rangeEquals(start, end) {
return start.line === end.line && start.character === end.character;
}

export function getItersAtRange(buffer, { start, end }) {
let start_iter;
let end_iter;

// Apply the tag on the whole line
// if diagnostic start and end are equals such as
// Blueprint-Error 13:12 to 13:12 Could not determine what kind of syntax is meant here
if (rangeEquals(start, end)) {
[, start_iter] = buffer.get_iter_at_line(start.line);
[, end_iter] = buffer.get_iter_at_line(end.line);
end_iter.forward_to_line_end();
start_iter.forward_find_char((char) => char !== "", end_iter);
} else {
[, start_iter] = buffer.get_iter_at_line_offset(
start.line,
start.character
);
[, end_iter] = buffer.get_iter_at_line_offset(end.line, end.character);
}

return [start_iter, end_iter];
}

0 comments on commit 7557ec8

Please sign in to comment.