Skip to content

Commit

Permalink
inline shader errors (#66)
Browse files Browse the repository at this point in the history
adds shader errors underneath the line they occurred in
  • Loading branch information
garrisonhh authored Jan 17, 2025
1 parent 40ac8dd commit a53bcb7
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 1 deletion.
82 changes: 82 additions & 0 deletions src/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { StateEffect, StateField, RangeSet } from '@codemirror/state';
import { EditorView, Decoration, WidgetType } from '@codemirror/view';

export class InlineErrorMessage {
constructor(lineno, text) {
this.lineno = lineno;
this.text = text;
}
}

class ErrorWidget extends WidgetType {
constructor(docId, msg) {
super();
this.docId = docId;
this.msg = msg;
}

eq(other) {
return other.msg.lineno == this.msg.lineno && other.msg.text == this.msg.text;
}

toDOM() {
const msg = document.createElement('div');
msg.classList.add('error-inline');
msg.innerText = this.msg.text;
return msg;
}

ignoreEvent() {
return false;
}
}

const addError = StateEffect.define();
const clearErrors = StateEffect.define();

const errorField = StateField.define({
create() {
return Decoration.none;
},
update(errors, tr) {
errors = errors.map(tr.changes);
for (let e of tr.effects) {
if (e.is(addError)) {
const doc = tr.state.doc;
const afterLineIndex = doc.line(e.value.msg.lineno).to + 1;
const lastIndex = doc.length;
const rangeFrom = Math.min(afterLineIndex, lastIndex);

const deco = Decoration.widget({
widget: e.value,
block: true,
}).range(rangeFrom);

errors = errors.update({
add: [deco],
});
} else if (e.is(clearErrors)) {
errors = RangeSet.empty;
}
}
return errors;
},
provide: (f) => EditorView.decorations.from(f),
});

export function displayInlineErrors(docId, msg) {
const view = window.editorViews.get(docId);
const effects = [];
effects.push(addError.of(new ErrorWidget(docId, msg)));

if (!view.state.field(errorField, false)) {
effects.push(StateEffect.appendConfig.of(errorField));
}

view.dispatch({ effects });
}

export function clearInlineErrors(docId) {
const view = window.editorViews.get(docId);
view.dispatch({ effects: [clearErrors.of()] });
}
8 changes: 8 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ const setError = (message, docId) => {
// todo: where to show global errors?
return;
}

// messages will either be string or InlineErrorMessage
if (typeof message != 'string') {
displayInlineErrors(docId, message);
return;
}

const slot = document.querySelector(`#slot-${docId}`);
let errorEl = document.querySelector(`#slot-${docId} #error-${docId}`);

Expand All @@ -85,6 +92,7 @@ const setError = (message, docId) => {
};
const clearError = (docId) => {
document.querySelector(`#slot-${docId} #error-${docId}`)?.remove();
clearInlineErrors(docId);
};
// clear local error when new eval comes in
session.on('eval', (msg) => clearError(msg.docId));
Expand Down
18 changes: 17 additions & 1 deletion src/shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
Copyright (C) 2025 nudel contributors
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { InlineErrorMessage } from './error';

// The standard fullscreen vertex shader.
const vertexShader = `#version 300 es
precision highp float;
Expand Down Expand Up @@ -268,6 +271,19 @@ function reloadShaderInstanceCode(instance, code) {
instance.update();
}

const errorRegex = /ERROR:\s+\d+:(\d+):\s+(.+)/;
function parseError(text) {
try {
const m = errorRegex.exec(text);
const linesInTemplateBeforeUserText = 8;
const lineno = parseInt(m[1]) - linesInTemplateBeforeUserText;
return new InlineErrorMessage(lineno, m[2]);
} catch (e) {
console.error(e);
return text;
}
}

export class ShaderSession {
constructor({ onError, canvas }) {
this.onError = onError;
Expand Down Expand Up @@ -295,7 +311,7 @@ export class ShaderSession {
}
console.log('Shader updated!');
} catch (err) {
this.onError(`${err}`, msg.docId);
this.onError(parseError(err), msg.docId);
}
}
}
9 changes: 9 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ body {
position: sticky;
bottom: 0;
}

.error-inline {
background-color: black;
font-family: monospace;
margin: 0;
border-top: 2px dotted tomato;
color: tomato;
position: relative;
}
}

:root {
Expand Down

0 comments on commit a53bcb7

Please sign in to comment.