193 lines
5.1 KiB
Vue
193 lines
5.1 KiB
Vue
<template>
|
|
<div class="editor-container">
|
|
<div ref="editor" class="code-editor"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { ref, onMounted, onBeforeUnmount, defineProps, defineEmits } from "vue";
|
|
import { EditorView, keymap, gutter, GutterMarker, lineNumbers } from "@codemirror/view";
|
|
import { EditorState, StateField, StateEffect } from "@codemirror/state";
|
|
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
|
|
import { RangeSet } from "@codemirror/rangeset";
|
|
import { javascript } from "@codemirror/lang-javascript";
|
|
import { oneDark } from "@codemirror/theme-one-dark";
|
|
import { indentUnit } from "@codemirror/language";
|
|
|
|
class BreakpointMarker extends GutterMarker {
|
|
toDOM() {
|
|
const marker = document.createElement("div");
|
|
marker.className = "breakpoint-marker";
|
|
marker.textContent = "●";
|
|
return marker;
|
|
}
|
|
}
|
|
|
|
export default {
|
|
name: "CodeEditor",
|
|
props: {
|
|
modelValue: String, // 双向绑定代码内容
|
|
language: String, // 代码语言,默认 JS
|
|
height: {
|
|
type: String,
|
|
default: '600px'
|
|
}
|
|
},
|
|
emits: ["update:modelValue"], // 监听代码变更
|
|
setup(props, { emit }) {
|
|
const editor = ref(null);
|
|
let editorView = null;
|
|
|
|
const setBreakpoint = StateEffect.define();
|
|
const breakpointsField = StateField.define({
|
|
create: () => new Set(),
|
|
update(breakpoints, tr) {
|
|
const newBreakpoints = new Set(breakpoints);
|
|
for (let effect of tr.effects) {
|
|
if (effect.is(setBreakpoint)) {
|
|
const line = effect.value;
|
|
if (newBreakpoints.has(line)) {
|
|
newBreakpoints.delete(line);
|
|
} else {
|
|
newBreakpoints.add(line);
|
|
}
|
|
}
|
|
}
|
|
return newBreakpoints;
|
|
},
|
|
});
|
|
|
|
const breakpointGutter = gutter({
|
|
class: "cm-breakpoint-gutter",
|
|
markers: (view) => {
|
|
const breakpoints = view.state.field(breakpointsField);
|
|
const markers = [];
|
|
for (const line of breakpoints) {
|
|
try {
|
|
const lineInfo = view.state.doc.line(line);
|
|
markers.push(new BreakpointMarker().range(lineInfo.from));
|
|
} catch (e) {
|
|
console.warn("Invalid line number skipped:", line);
|
|
}
|
|
}
|
|
return RangeSet.of(markers, true);
|
|
},
|
|
domEventHandlers: {
|
|
click: (view, block) => {
|
|
const lineNumber = view.state.doc.lineAt(block.from).number;
|
|
view.dispatch({
|
|
effects: [setBreakpoint.of(lineNumber)],
|
|
});
|
|
return true;
|
|
},
|
|
},
|
|
});
|
|
|
|
onMounted(() => {
|
|
if (!editor.value) return;
|
|
|
|
editorView = new EditorView({
|
|
state: EditorState.create({
|
|
doc: props.modelValue || "",
|
|
extensions: [
|
|
lineNumbers(),
|
|
breakpointGutter,
|
|
breakpointsField,
|
|
javascript(),
|
|
oneDark,
|
|
indentUnit.of(" "), // 使用4个空格作为缩进
|
|
keymap.of([
|
|
...defaultKeymap,
|
|
indentWithTab
|
|
]),
|
|
EditorView.updateListener.of((update) => {
|
|
if (update.docChanged) {
|
|
emit("update:modelValue", update.state.doc.toString());
|
|
}
|
|
}),
|
|
EditorView.theme({
|
|
"&": {
|
|
fontSize: "14px",
|
|
height: "100%"
|
|
},
|
|
".cm-content": {
|
|
fontFamily: "'Fira Code', monospace",
|
|
padding: "12px 0",
|
|
},
|
|
".cm-line": {
|
|
padding: "0 12px",
|
|
lineHeight: "1.6"
|
|
},
|
|
".cm-gutters": {
|
|
backgroundColor: "#1e1e1e",
|
|
color: "#666",
|
|
border: "none"
|
|
},
|
|
".cm-activeLineGutter": {
|
|
backgroundColor: "#333"
|
|
},
|
|
".cm-activeLine": {
|
|
backgroundColor: "rgba(255,255,255,0.03)"
|
|
}
|
|
}),
|
|
EditorView.baseTheme({
|
|
".cm-breakpoint-gutter": {
|
|
width: "30px",
|
|
backgroundColor: "#1e1e1e",
|
|
borderRight: "1px solid #333"
|
|
},
|
|
".breakpoint-marker": {
|
|
color: "#ff5555",
|
|
fontSize: "14px",
|
|
cursor: "pointer",
|
|
textAlign: "center"
|
|
},
|
|
}),
|
|
],
|
|
}),
|
|
parent: editor.value,
|
|
});
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
editorView?.destroy();
|
|
});
|
|
|
|
return { editor };
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style>
|
|
.editor-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
background: #282c34;
|
|
}
|
|
|
|
.code-editor {
|
|
height: 100%;
|
|
overflow: auto;
|
|
}
|
|
|
|
/* 自定义滚动条样式 */
|
|
.code-editor ::-webkit-scrollbar {
|
|
width: 12px;
|
|
height: 12px;
|
|
}
|
|
|
|
.code-editor ::-webkit-scrollbar-track {
|
|
background: #21252b;
|
|
}
|
|
|
|
.code-editor ::-webkit-scrollbar-thumb {
|
|
background: #454c59;
|
|
border-radius: 6px;
|
|
border: 3px solid #21252b;
|
|
}
|
|
|
|
.code-editor ::-webkit-scrollbar-thumb:hover {
|
|
background: #535b6a;
|
|
}
|
|
</style>
|