diff --git a/content/tutorial/common/src/__client.js b/content/tutorial/common/src/__client.js index a6765fb35..4f4ffee9b 100644 --- a/content/tutorial/common/src/__client.js +++ b/content/tutorial/common/src/__client.js @@ -118,4 +118,14 @@ if (import.meta.hot) { '*' ); }); + + import.meta.hot.on('svelte:warnings', (data) => { + parent.postMessage( + { + type: 'warnings', + data + }, + '*' + ); + }); } diff --git a/content/tutorial/common/svelte.config.js b/content/tutorial/common/svelte.config.js index 29ce35b4b..66ff56f30 100644 --- a/content/tutorial/common/svelte.config.js +++ b/content/tutorial/common/svelte.config.js @@ -5,6 +5,12 @@ const config = { // Don't do this in your own apps unless you know what you're doing! // See https://kit.svelte.dev/docs/configuration#csrf for more info. csrf: false + }, + + vitePlugin: { + experimental: { + sendWarningsToBrowser: true + } } }; diff --git a/package.json b/package.json index ea2158932..4d3bc7fdc 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@codemirror/lang-html": "^6.4.2", "@codemirror/lang-javascript": "^6.1.4", "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.2.0", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.9.2", "@fontsource/roboto-mono": "^4.5.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60bac2584..7b5b7c8b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ specifiers: '@codemirror/lang-html': ^6.4.2 '@codemirror/lang-javascript': ^6.1.4 '@codemirror/language': ^6.6.0 + '@codemirror/lint': ^6.2.0 '@codemirror/state': ^6.2.0 '@codemirror/view': ^6.9.2 '@fontsource/roboto-mono': ^4.5.10 @@ -51,6 +52,7 @@ dependencies: '@codemirror/lang-html': 6.4.2 '@codemirror/lang-javascript': 6.1.4 '@codemirror/language': 6.6.0 + '@codemirror/lint': 6.2.0 '@codemirror/state': 6.2.0 '@codemirror/view': 6.9.2 '@fontsource/roboto-mono': 4.5.10 diff --git a/src/routes/tutorial/[slug]/Editor.svelte b/src/routes/tutorial/[slug]/Editor.svelte index ff8589197..b4ce4390e 100644 --- a/src/routes/tutorial/[slug]/Editor.svelte +++ b/src/routes/tutorial/[slug]/Editor.svelte @@ -6,6 +6,7 @@ import { EditorState } from '@codemirror/state'; import { indentWithTab } from '@codemirror/commands'; import { indentUnit } from '@codemirror/language'; + import { setDiagnostics } from '@codemirror/lint'; import { javascript } from '@codemirror/lang-javascript'; import { html } from '@codemirror/lang-html'; import { svelte } from '@replit/codemirror-lang-svelte'; @@ -13,7 +14,7 @@ import { HighlightStyle } from '@codemirror/language'; import { syntaxHighlighting } from '@codemirror/language'; import { afterNavigate, beforeNavigate } from '$app/navigation'; - import { files, selected_file, selected_name, update_file } from './state.js'; + import { files, selected_file, selected_name, update_file, warnings } from './state.js'; import './codemirror.css'; // TODO add more styles (selection ranges, etc) @@ -49,7 +50,31 @@ theme ]; - $: if (editor_view) select_state($selected_name); + $: if (editor_view) { + select_state($selected_name); + + if ($selected_name) { + const current_warnings = $warnings[$selected_name]; + + if (current_warnings) { + const diagnostics = current_warnings.map((warning) => { + /** @type {import('@codemirror/lint').Diagnostic} */ + const diagnostic = { + from: warning.start.character, + to: warning.end.character, + severity: 'warning', + message: warning.message + }; + + return diagnostic; + }); + + const transaction = setDiagnostics(editor_view.state, diagnostics); + + editor_view.dispatch(transaction); + } + } + } $: reset($files); @@ -178,6 +203,9 @@ reset($files); select_state($selected_name); + + // clear warnings + warnings.set({}); }); diff --git a/src/routes/tutorial/[slug]/Output.svelte b/src/routes/tutorial/[slug]/Output.svelte index 59f907648..a84a295fe 100644 --- a/src/routes/tutorial/[slug]/Output.svelte +++ b/src/routes/tutorial/[slug]/Output.svelte @@ -5,6 +5,7 @@ import Chrome from './Chrome.svelte'; import Loading from './Loading.svelte'; import { base, error, logs, progress, subscribe } from './adapter'; + import { warnings } from './state'; /** @type {import('$lib/types').Exercise} */ export let exercise; @@ -61,6 +62,11 @@ }, 1000); } else if (e.data.type === 'ping-pause') { clearTimeout(timeout); + } else if (e.data.type === 'warnings') { + warnings.update(($warnings) => ({ + ...$warnings, + [e.data.data.normalizedFilename]: e.data.data.allWarnings + })); } } diff --git a/src/routes/tutorial/[slug]/codemirror.css b/src/routes/tutorial/[slug]/codemirror.css index 63d9a3360..416da525a 100644 --- a/src/routes/tutorial/[slug]/codemirror.css +++ b/src/routes/tutorial/[slug]/codemirror.css @@ -49,6 +49,28 @@ color: var(--sk-text-2); } +.cm-editor .cm-tooltip { + border: none; + border-radius: 2px; + overflow: hidden; + margin: 0.4rem 0; + filter: drop-shadow(1px 2px 5px rgba(0,0,0,0.1)); +} +.cm-editor .cm-tooltip-hover {} +.cm-editor .cm-tooltip-below {} +.cm-editor .cm-tooltip-lint {} +.cm-editor .cm-tooltip-section {} +.cm-editor .cm-diagnostic { + border: none; + padding: 0.2rem 0.8rem; +} +.cm-editor .cm-diagnostic-warning { + /* background: hsl(36, 100%, 32%); */ + background: hsl(39, 100%, 10%); +} +.cm-editor .cm-diagnosticText {} + + @media (prefers-color-scheme: dark) { .cm-editor .cm-activeLineGutter { background-color: var(--sk-back-3); @@ -57,4 +79,8 @@ .cm-editor .cm-activeLine { background-color: var(--sk-back-2); } + + .cm-editor .cm-diagnostic-warning { + background: hsl(39, 100%, 20%); + } } \ No newline at end of file diff --git a/src/routes/tutorial/[slug]/state.js b/src/routes/tutorial/[slug]/state.js index 9932b214c..b9290b678 100644 --- a/src/routes/tutorial/[slug]/state.js +++ b/src/routes/tutorial/[slug]/state.js @@ -1,13 +1,34 @@ import { derived, writable } from 'svelte/store'; import * as adapter from './adapter.js'; -/** @type {import('svelte/store').Writable} */ +/** + * @template T + * @typedef {import('svelte/store').Writable} Writable + */ + +// TODO would be nice if svelte exported this type (maybe it does already?) +/** + * @typedef {{ + * code: string; + * start: { line: number, column: number, character: number }; + * end: { line: number, column: number, character: number }; + * pos: number; + * filename: string; + * frame: string; + * message: string; + * }} CompilerWarning + */ + +/** @type {Writable} */ export const files = writable([]); -/** @type {import('svelte/store').Writable>} */ +/** @type {Writable>} */ export const solution = writable({}); -/** @type {import('svelte/store').Writable} */ +/** @type {Writable>} */ +export const warnings = writable({}); + +/** @type {Writable} */ export const selected_name = writable(null); export const selected_file = derived([files, selected_name], ([$files, $selected_name]) => {