Skip to content

Commit 0c5bdaa

Browse files
committed
feat: code viewer
1 parent 31c99e3 commit 0c5bdaa

File tree

21 files changed

+670
-93
lines changed

21 files changed

+670
-93
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"vite": "catalog:build",
5454
"vite-plugin-inspect": "catalog:devtools",
5555
"vitest": "catalog:testing",
56-
"vue": "catalog:",
56+
"vue": "catalog:frontend",
5757
"vue-tsc": "catalog:devtools"
5858
},
5959
"resolutions": {

packages/devtools/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,22 @@
5555
"ws": "catalog:deps"
5656
},
5757
"devDependencies": {
58+
"@types/codemirror": "catalog:types",
5859
"@types/stream-json": "catalog:types",
5960
"@unocss/nuxt": "catalog:build",
6061
"@vueuse/nuxt": "catalog:build",
62+
"codemirror": "catalog:frontend",
63+
"codemirror-theme-vars": "catalog:frontend",
64+
"comlink": "catalog:frontend",
6165
"d3": "catalog:frontend",
6266
"d3-hierarchy": "catalog:frontend",
6367
"d3-shape": "catalog:frontend",
68+
"diff-match-patch-es": "catalog:frontend",
6469
"floating-vue": "catalog:frontend",
6570
"fuse.js": "catalog:frontend",
6671
"idb-keyval": "catalog:frontend",
6772
"nanovis": "catalog:frontend",
73+
"splitpanes": "catalog:frontend",
6874
"theme-vitesse": "catalog:frontend",
6975
"vite-hot-client": "catalog:frontend"
7076
}

packages/devtools/src/app/app.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { backend } from './state/backend'
66
import { fetchData } from './state/data'
77
88
import 'floating-vue/dist/style.css'
9+
import './styles/cm.css'
10+
import './styles/splitpanes.css'
911
import './styles/global.css'
1012
import './composables/dark'
1113
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<script setup lang="ts">
2+
import type CodeMirror from 'codemirror'
3+
import { nextTick, onMounted, toRefs, useTemplateRef, watchEffect } from 'vue'
4+
import { guessCodemirrowMode, syncEditorScrolls, syncScrollListeners, useCodeMirror } from '../../composables/codemirror'
5+
import { settings } from '../../state/settings'
6+
import { calculateDiffWithWorker } from '../../worker/diff'
7+
8+
const props = defineProps<{
9+
from: string
10+
to: string
11+
oneColumn: boolean
12+
diff: boolean
13+
}>()
14+
15+
const { from, to } = toRefs(props)
16+
17+
const fromEl = useTemplateRef('fromEl')
18+
const toEl = useTemplateRef('toEl')
19+
20+
let cm1: CodeMirror.Editor
21+
let cm2: CodeMirror.Editor
22+
23+
onMounted(() => {
24+
cm1 = useCodeMirror(
25+
fromEl,
26+
from,
27+
{
28+
mode: 'javascript',
29+
readOnly: true,
30+
lineNumbers: true,
31+
},
32+
)
33+
34+
cm2 = useCodeMirror(
35+
toEl,
36+
to,
37+
{
38+
mode: 'javascript',
39+
readOnly: true,
40+
lineNumbers: true,
41+
},
42+
)
43+
44+
syncScrollListeners(cm1, cm2)
45+
46+
watchEffect(() => {
47+
cm1.setOption('lineWrapping', settings.value.codeviewerLineWrap)
48+
cm2.setOption('lineWrapping', settings.value.codeviewerLineWrap)
49+
})
50+
51+
watchEffect(async () => {
52+
cm1.getWrapperElement().style.display = props.oneColumn ? 'none' : ''
53+
if (!props.oneColumn) {
54+
await nextTick()
55+
// Force sync to current scroll
56+
cm1.refresh()
57+
syncEditorScrolls(cm2, cm1)
58+
}
59+
})
60+
61+
watchEffect(async () => {
62+
const l = from.value
63+
const r = to.value
64+
const diffEnabled = props.diff
65+
66+
cm1.setOption('mode', guessCodemirrowMode(l))
67+
cm2.setOption('mode', guessCodemirrowMode(r))
68+
69+
await nextTick()
70+
71+
cm1.startOperation()
72+
cm2.startOperation()
73+
74+
// clean up marks
75+
cm1.getAllMarks().forEach(i => i.clear())
76+
cm2.getAllMarks().forEach(i => i.clear())
77+
for (let i = 0; i < cm1.lineCount() + 2; i++)
78+
cm1.removeLineClass(i, 'background', 'diff-removed')
79+
for (let i = 0; i < cm2.lineCount() + 2; i++)
80+
cm2.removeLineClass(i, 'background', 'diff-added')
81+
82+
if (diffEnabled && from.value) {
83+
const changes = await calculateDiffWithWorker(l, r)
84+
85+
const addedLines = new Set()
86+
const removedLines = new Set()
87+
88+
let indexL = 0
89+
let indexR = 0
90+
changes.forEach(([type, change]) => {
91+
if (type === 1) {
92+
const start = cm2.posFromIndex(indexR)
93+
indexR += change.length
94+
const end = cm2.posFromIndex(indexR)
95+
cm2.markText(start, end, { className: 'diff-added-inline' })
96+
for (let i = start.line; i <= end.line; i++) addedLines.add(i)
97+
}
98+
else if (type === -1) {
99+
const start = cm1.posFromIndex(indexL)
100+
indexL += change.length
101+
const end = cm1.posFromIndex(indexL)
102+
cm1.markText(start, end, { className: 'diff-removed-inline' })
103+
for (let i = start.line; i <= end.line; i++) removedLines.add(i)
104+
}
105+
else {
106+
indexL += change.length
107+
indexR += change.length
108+
}
109+
})
110+
111+
Array.from(removedLines).forEach(i =>
112+
cm1.addLineClass(i, 'background', 'diff-removed'),
113+
)
114+
Array.from(addedLines).forEach(i =>
115+
cm2.addLineClass(i, 'background', 'diff-added'),
116+
)
117+
}
118+
119+
cm1.endOperation()
120+
cm2.endOperation()
121+
})
122+
})
123+
124+
function _onUpdate(size: number) {
125+
// Refresh sizes
126+
cm1?.refresh()
127+
cm2?.refresh()
128+
if (props.oneColumn)
129+
return
130+
settings.value.codeviewerDiffPanelSize = size
131+
}
132+
</script>
133+
134+
<template>
135+
<div h-full w-full :class="oneColumn ? 'flex' : 'grid grid-cols-2'">
136+
<div v-show="!oneColumn" ref="fromEl" class="h-inherit" />
137+
<div ref="toEl" class="h-inherit" />
138+
</div>
139+
</template>
140+
141+
<style lang="postcss">
142+
.diff-added {
143+
--at-apply: bg-green-400/15;
144+
}
145+
.diff-removed {
146+
--at-apply: bg-red-400/15;
147+
}
148+
.diff-added-inline {
149+
--at-apply: bg-green-400/30;
150+
}
151+
.diff-removed-inline {
152+
--at-apply: bg-red-400/30;
153+
}
154+
</style>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
4+
const props = withDefaults(
5+
defineProps<{
6+
duration: number | undefined
7+
factor?: number
8+
color?: boolean
9+
}>(),
10+
{
11+
factor: 1,
12+
color: true,
13+
},
14+
)
15+
16+
function getDurationColor(duration: number | undefined) {
17+
if (!props.color)
18+
return ''
19+
if (duration == null)
20+
return ''
21+
duration = duration * props.factor
22+
if (duration < 1)
23+
return ''
24+
if (duration > 10000)
25+
return 'color-scale-critical'
26+
if (duration > 1000)
27+
return 'color-scale-high'
28+
if (duration > 500)
29+
return 'color-scale-medium'
30+
if (duration > 200)
31+
return 'color-scale-low'
32+
return 'color-scale-neutral'
33+
}
34+
35+
const units = computed(() => {
36+
if (!props.duration)
37+
return ['', '-']
38+
if (props.duration < 1)
39+
return ['<1', 'ms']
40+
if (props.duration < 1000)
41+
return [props.duration.toFixed(0), 'ms']
42+
if (props.duration < 1000 * 60)
43+
return [(props.duration / 1000).toFixed(1), 's']
44+
return [(props.duration / 1000 / 60).toFixed(1), 'min']
45+
})
46+
</script>
47+
48+
<template>
49+
<DisplayNumberWithUnit :class="getDurationColor(duration)" :number="units[0]" :unit="units[1]" />
50+
</template>

packages/devtools/src/app/components/display/DurationBadge.vue

Lines changed: 0 additions & 73 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
number: number | string
4+
unit: string
5+
}>()
6+
</script>
7+
8+
<template>
9+
<span block>
10+
<span>{{ number }}</span><span ml-0.4 text-xs op75>{{ unit }}</span>
11+
</span>
12+
</template>

packages/devtools/src/app/components/display/PluginName.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { h } from 'vue'
3-
import { getHashColorFromString } from '../../utils/color'
3+
import { getPluginColor } from '../../utils/color'
44
55
const props = defineProps<{
66
name: string
@@ -40,7 +40,7 @@ function render() {
4040
const parts = props.name.split(':')
4141
if (parts.length > 1) {
4242
return h('span', [
43-
h('span', { style: { color: getHashColorFromString(parts[0]) } }, `${parts[0]}:`),
43+
h('span', { style: { color: getPluginColor(parts[0]) } }, `${parts[0]}:`),
4444
h('span', parts.slice(1).join(':')),
4545
])
4646
}

packages/devtools/src/app/components/flowmap/Node.vue

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const props = defineProps<{
44
top?: boolean
55
bottom?: boolean
66
}
7+
classNodeInline?: string
78
classNodeOuter?: string
89
classNodeInner?: string
910
}>()
@@ -14,11 +15,13 @@ const props = defineProps<{
1415
<div v-if="props.lines?.top" absolute top-0 left-10 border="r base" h="1/2" max-h-4 z-flowmap-line />
1516
<div v-if="props.lines?.bottom" absolute bottom-0 left-10 border="r base" h="1/2" max-h-4 z-flowmap-line />
1617
<slot name="before" />
17-
<div flex="~">
18-
<div :class="props.classNodeOuter" border="~ base rounded-full" bg-base>
19-
<div px3 py1 :class="props.classNodeInner" flex="~ inline gap-2 items-center">
20-
<slot name="content" />
21-
</div>
18+
<div flex="~" :class="props.classNodeInline">
19+
<div :class="props.classNodeOuter" border="~ base rounded-full" bg-base of-hidden>
20+
<slot name="inner">
21+
<div px3 py1 :class="props.classNodeInner" flex="~ inline gap-2 items-center">
22+
<slot name="content" />
23+
</div>
24+
</slot>
2225
</div>
2326
<slot name="inline-after" />
2427
</div>

0 commit comments

Comments
 (0)