Skip to content

Commit 0d8cdf0

Browse files
enhance(playground): カーソル位置を表示するように
Fix #21
1 parent 5090726 commit 0d8cdf0

File tree

1 file changed

+78
-13
lines changed

1 file changed

+78
-13
lines changed

.vitepress/pages/Playground.vue

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,24 @@
2424
</div>
2525

2626
<div :class="$style.playgroundEditorRoot">
27-
<div :class="$style.playgroundEditorScroller" :inert="editorLoading">
28-
<div :class="[$style.highlight, $style.playgroundEditorHighlight]" v-html="editorHtml"></div>
29-
<textarea
30-
ref="inputEl"
31-
v-model="code"
32-
@input="onInput"
33-
@keydown="onKeydown"
34-
autocomplete="off"
35-
wrap="off"
36-
spellcheck="false"
37-
:class="$style.playgroundEditorTextarea"
38-
></textarea>
27+
<div :class="$style.playgroundEditorScRoot">
28+
<div :class="$style.playgroundEditorScroller" :inert="editorLoading">
29+
<div :class="[$style.highlight, $style.playgroundEditorHighlight]" v-html="editorHtml"></div>
30+
<textarea
31+
ref="inputEl"
32+
v-model="code"
33+
@input="onInput"
34+
@keydown="onKeydown"
35+
@selectionchange.passive="setEditorCursorPosition"
36+
autocomplete="off"
37+
wrap="off"
38+
spellcheck="false"
39+
:class="$style.playgroundEditorTextarea"
40+
></textarea>
41+
</div>
42+
</div>
43+
<div :class="$style.playgroundInfoFooter">
44+
<div>Row: {{ currentCursor.row + 1 }}, Column: {{ currentCursor.column + 1 }}</div>
3945
</div>
4046
<div v-if="editorLoading" :class="$style.playgroundEditorLoading">
4147
<div>Loading...</div>
@@ -92,7 +98,7 @@
9298
import { inBrowser } from 'vitepress';
9399
import { ref, computed, useTemplateRef, nextTick, onMounted, watch, onUnmounted } from 'vue';
94100
import { createHighlighterCore } from 'shiki/core';
95-
import type { HighlighterCore, LanguageRegistration } from 'shiki/core';
101+
import type { HighlighterCore, LanguageRegistration, ShikiTransformerContext } from 'shiki/core';
96102
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
97103
import lzString from 'lz-string';
98104
import { useThrottle } from '../scripts/throttle';
@@ -116,6 +122,13 @@ const editorLoading = ref(true);
116122
const inputEl = useTemplateRef('inputEl');
117123
const code = ref(fizzbuzz);
118124
const editorHtml = ref('');
125+
const currentCursor = ref<{
126+
row: number;
127+
column: number;
128+
}>({
129+
row: 0,
130+
column: 0,
131+
});
119132
120133
let highlighter: HighlighterCore | null = null;
121134
@@ -135,6 +148,20 @@ async function init() {
135148
});
136149
}
137150
151+
function setEditorCursorPosition() {
152+
if (inputEl.value == null) return;
153+
154+
const pos = inputEl.value.selectionDirection === 'backward'
155+
? inputEl.value.selectionStart
156+
: inputEl.value.selectionEnd;
157+
const lines = code.value.slice(0, pos).split('\n');
158+
159+
currentCursor.value = {
160+
row: lines.length - 1,
161+
column: lines[lines.length - 1].length,
162+
};
163+
}
164+
138165
function onInput(ev: Event) {
139166
code.value = (ev.target as HTMLTextAreaElement)?.value ?? code.value;
140167
}
@@ -189,6 +216,7 @@ const logs = ref<{
189216
const logEl = useTemplateRef('logEl');
190217
191218
const isSyntaxError = ref(false);
219+
const errorLine = ref<number | null>(null);
192220
193221
const ast = ref<unknown>(null);
194222
const astHtml = ref('');
@@ -198,6 +226,7 @@ const metadataHtml = ref('');
198226
199227
function parse() {
200228
isSyntaxError.value = false;
229+
errorLine.value = null;
201230
202231
if (runner.value == null) {
203232
ast.value = null;
@@ -214,6 +243,13 @@ function parse() {
214243
type: 'error',
215244
}];
216245
isSyntaxError.value = true;
246+
247+
const lineRes = /\(Line\s*(:\s*)?(\d+)/.exec(err.message);
248+
if (lineRes != null) {
249+
errorLine.value = parseInt(lineRes[2], 10); // Convert to zero-based index
250+
} else {
251+
errorLine.value = null;
252+
}
217253
}
218254
ast.value = null;
219255
metadata.value = null;
@@ -356,6 +392,13 @@ onMounted(async () => {
356392
dark: 'github-dark',
357393
},
358394
defaultColor: false,
395+
transformers: [{
396+
line(node, lineIndex) {
397+
if (isSyntaxError.value && errorLine.value === lineIndex) {
398+
this.addClassToHast(node, 'error');
399+
}
400+
},
401+
}],
359402
});
360403
}
361404
}, { immediate: true });
@@ -451,7 +494,24 @@ onUnmounted(() => {
451494
452495
.playgroundEditorRoot {
453496
position: relative;
497+
min-height: 0;
498+
min-width: 0;
499+
display: flex;
500+
flex-direction: column;
501+
}
502+
503+
.playgroundEditorScRoot {
454504
overflow: scroll;
505+
flex-grow: 1;
506+
}
507+
508+
.playgroundInfoFooter {
509+
border-top: 1px solid var(--vp-c-divider);
510+
flex-shrink: 0;
511+
padding: 0 24px;
512+
font-size: 12px;
513+
line-height: 24px;
514+
color: var(--vp-c-text-2);
455515
}
456516
457517
.playgroundEditorLoading {
@@ -518,6 +578,11 @@ onUnmounted(() => {
518578
.highlight span:global(.line) {
519579
display: inline-block;
520580
min-height: 1em;
581+
min-width: 100%;
582+
}
583+
584+
.highlight span:global(.line.error) {
585+
background-color: var(--vp-c-danger-soft);
521586
}
522587
523588
:global(html.dark) .highlight span {

0 commit comments

Comments
 (0)