CodeMirror 6+ vue3实现Atomic Ranges 功能

2,663 阅读1分钟

[Talk is cheap. Show me the code]

前言

实现一个带有插入标签的代码编辑器功能,如果只是简单的代码编辑器建议使用 monaco editor,本文参考掘金文章 # CodeMirror 6+ vue3 实现简单的计算公式,插入标签功能

直接上代码

直接复制即可展示效果

 pnpm i  @codemirror/state
 pnpm i  @codemirror/lang-javascript
 pnpm i @codemirror/view
 pnpm i codemirror
 

第一步

<template>
  <div class="code" ref="codeMirror"></div>
  <div @click="addText('名称')">测试数据</div>
  <div>获取数据</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { EditorView, minimalSetup, basicSetup } from "codemirror";
import { EditorState } from "@codemirror/state";
import { javascript } from "@codemirror/lang-javascript";
import { MatchDecorator, WidgetType } from "@codemirror/view";
import { PlaceholderWidget } from "./index";
import {
  Decoration,
  DecorationSet,
  ViewPlugin,
  ViewUpdate,
} from "@codemirror/view";

const codeMirror = ref(null);
let doc = `console.log(2222)`;
const addText = () => {
  let val = { name: "测试数据年就开了", id: "222" };
  if (editor) {
    console.log("codeMirror.value.state", editor);

    editor.dispatch({
      changes: {
        from: editor.state.selection.main.head,
        to: editor.state.selection.main.head,
        insert: `[[${val.id}.${val.name}]]`,
      },
      // 光标位置
      selection: {
        anchor:
          editor.state.selection.main.head +
          val.name.length +
          5 +
          val.id.length,
      },
    });
  }
};
let editor;
onMounted(() => {
  const placeholderMatcher = new MatchDecorator({
    // regexp: /\[\[(\w+)\]\]/g, // 原有逻辑
    regexp: /\[\[(.+?)\]\]/g, //支持中文
    decoration: (match) =>
      Decoration.replace({
        widget: new PlaceholderWidget(match[1]),
      }),
  });
  const placeholders = ViewPlugin.fromClass(
    class {
      placeholders: DecorationSet;
      constructor(view: EditorView) {
        this.placeholders = placeholderMatcher.createDeco(view);
      }
      update(update: ViewUpdate) {
        this.placeholders = placeholderMatcher.updateDeco(
          update,
          this.placeholders
        );
      }
    },
    {
      decorations: (instance) => instance.placeholders,
      provide: (plugin) =>
        EditorView.atomicRanges.of((view) => {
          return view.plugin(plugin)?.placeholders || Decoration.none;
        }),
    }
  );

  if (codeMirror.value) {
    const baseTheme = EditorView.baseTheme({
      ".cm-mywidget": {
        paddingLeft: "6px",
        paddingRight: "6px",
        paddingTop: "3px",
        paddingBottom: "3px",
        marginLeft: "3px",
        marginRight: "3px",
        backgroundColor: "#ffcdcc",
        borderRadius: "4px",
      },
    });
    editor = new EditorView({
      state: EditorState.create({
        doc: "Dear [[name]],\nYour [[item]] is on its way. Please see [[order]] for details.\n",
        extensions: [placeholders, baseTheme, basicSetup, javascript()],
      }),
      parent: codeMirror.value,
    });

    // new EditorView({
    //   state: EditorState.create({
    //     doc: "Dear [[name]],\nYour [[item]] is on its way. Please see [[order]] for details.\n",
    //     extensions: [basicSetup, javascript(), [baseTheme, [], placeholders]],
    //   }),
    //   parent: codeMirror.value,
    // });

    // let editor = new EditorView({
    //   parent: codeMirror.value,
    //   doc: "Dear [[name]],\nYour [[item]] is on its way. Please see [[order]] for details.\n",
    //   extensions: [placeholders, baseTheme, basicSetup, javascript()],
    // });
  }
});
</script>

<style scoped>
.code {
  width: 100%;
  height: 300px;
}
.ͼ1 .cm-merge-b .cm-changedLine {
  background: #ddfbe6;
}
.ͼ1 .cm-merge-b .cm-changedText {
  background: #c6efd0;
}
.cm-merge-revert {
  display: none;
}
.cm-merge-a .cm-changedText,
.cm-deletedChunk .cm-deletedText {
  background: #eac3cc;
}
.cm-changeGutter {
  width: 100%;
}
.cm-changeGutter {
  position: absolute;
  left: 0;
  z-index: -1;
}
.cm-merge-b .cm-changedLineGutter {
  background: #ddfbe6;
}
.cm-merge-a .cm-changedLineGutter,
.cm-deletedLineGutter {
  background: #f9d7dc;
}
.cm-merge-a .cm-changedLine,
.cm-deletedChunk {
  background: #fbe9eb;
}
.cm-merge-b .cm-changedLine {
  background: #ecfdf0;
}
.cm-line {
  padding: 2px 2px 0 6px;
  padding-left: 40px;
  position: relative;
  font-size: 12px;
}
.cm-merge-a .cm-changedLine::before {
  content: "-";
  display: inline-block;
  position: absolute;
  left: 10px;
  color: #9bb0a1;
}

.cm-merge-b .cm-changedLine::before {
  content: "+";
  display: inline-block;
  position: absolute;
  left: 10px;
  color: #9bb0a1;
}
.cm-lineNumbers .cm-gutterElement {
  /* padding: 2px 3px 0 5px; */
  color: rgba(0, 0, 0, 0.3);
}
.cm-merge-revert {
  display: none;
}
.cm-gutters {
  padding-left: 30px;
}
.cm-content {
  padding: 0;
}
</style>

第二步

// 此处为 import { PlaceholderWidget } from "./index"; 的index 文件
import {
 
  WidgetType,
} from "@codemirror/view";

export class PlaceholderWidget extends WidgetType {
  constructor(name) {
    super();
    this.name = name;
  }
  eq(other) {
    return this.name == other.name;
  }
  toDOM() {
    let elt = document.createElement("span");
    elt.style.cssText = `
      border: 1px solid blue;
      border-radius: 4px;
      padding: 0 3px;
      background: lightblue;`;
    elt.textContent = this.name;
    return elt;
  }
  ignoreEvent() {
    return false;
  }
}


效果图: image.png

重新赋值

 editor.dispatch({
      changes: {
        from: 0,
        to: editor.state.doc.length,
        insert: "New Test Text",
      },
    });

如果有任何关于本文的意见,请在文章下方留言,我会在看到的第一时间回复。