|
| 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> |
0 commit comments