Using codemirror in vue (element) to realize code highlighting, code completion and version difference comparison

Keywords: Front-end npm Javascript shell SQL

vue language is used, and component of element is used. To make an online editing code, code content is required to be input, highlighted display can be carried out, different languages can be switched, keyword completion is supported, and a code left-right comparison between different versions is required.

As for why codemirror is selected, please check A comprehensive comparison of code highlighting plug-ins in vue (element)

OK, let's start work now!

First, you need to download the codemirror component and the diff match patch component

npm install codemirror
npm install diff-match-patch

Of course, npm download is very slow, change a domestic image, Taobao. Sure enough, the speed swished.

npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install

cnpm install codemirror
cnpm install diff-match-patch

You can see the directory structure under node modules

The core code is as follows, which is simple and easy to understand. You can customize the development according to yourself.

<template>
  <div>
    <!-- Operation button --> 
    <el-button @click="handleAdd">Code editing, highlight completion</el-button>
    <el-button @click="handleMerge">Code version, difference comparison</el-button>
      
    <!-- Code editing, highlight the content of the completion dialog box -->  
    <el-dialog :title="title" :visible.sync="open">
      <el-form ref="form" :model="form" label-width="80px">
        <el-form-item label="Script content">
          <div class="in-coder-panel">
            <textarea ref="textarea"></textarea>
            <el-select class="code-mode-select" v-model="mode" @change="changeMode">
              <el-option v-for="mode in modes" :key="mode.value" :label="mode.label" :value="mode.value">
              </el-option>
            </el-select>
          </div>
        </el-form-item>
      </el-form>
    </el-dialog>
    
    <!-- Code version, difference comparison dialog content -->
    <el-dialog :title="titleBBB" :visible.sync="openBBB">
      <div id="view"></div>
    </el-dialog>
      
  </div>
</template>

<script>
  // Introduce global instance
  import CodeMirror from 'codemirror'
  // Core style
  import 'codemirror/lib/codemirror.css'
  // After the theme is introduced, you need to specify the theme in options to take effect
  import 'codemirror/theme/idea.css'

  // You need to introduce a specific syntax highlighting library to have the corresponding syntax highlighting effect
  // codemirror officially supports dynamic loading of corresponding syntax highlight library through / addon/mode/loadmode.js and / mode/meta.js
  // However, vue does not seem to be able to dynamically load the corresponding JS after instance initialization, so the corresponding JS is introduced in advance here
  import 'codemirror/mode/javascript/javascript.js'
  import 'codemirror/mode/css/css.js'
  import 'codemirror/mode/xml/xml.js'
  import 'codemirror/mode/shell/shell.js'
  import 'codemirror/mode/sql/sql.js'

  //Code completion prompt
  import 'codemirror/addon/hint/anyword-hint.js';
  import 'codemirror/addon/hint/css-hint.js';
  import 'codemirror/addon/hint/html-hint.js';
  import 'codemirror/addon/hint/javascript-hint.js';
  import 'codemirror/addon/hint/show-hint.css';
  import 'codemirror/addon/hint/show-hint.js';
  import 'codemirror/addon/hint/sql-hint.js';
  import 'codemirror/addon/hint/xml-hint.js';
    
  //Code version difference comparison
  import 'codemirror/addon/merge/merge.js'
  import 'codemirror/addon/merge/merge.css'
  import DiffMatchPatch from 'diff-match-patch'

  window.diff_match_patch = DiffMatchPatch
  window.DIFF_DELETE = -1
  window.DIFF_INSERT = 1
  window.DIFF_EQUAL = 0

  export default {
    name: "Code",

    props: {
      // External incoming content for two-way binding
      value: String,
      // Syntax type passed in from outside
      language: {
        type: String,
        default: null
      }
    },

    data() {
      return {
        // What's real inside
        code: '#!/bin/bash\n' +
        'cd /root/\n' +
        'list=(`ls`)\n' +
        ' \n' +
        'for i in ${list[@]}\n' +
        'do\n' +
        '   if [ -d $i ]\n' +
        '   then\n' +
        '       cp -r $i /tmp/\n' +
        '   fi\n' +
        'done',
        // Default syntax type
        mode: 'shell',
        // Editor instance
        coder: null,
        // Default configuration
        options: {
          // Indentation format
          tabSize: 4,
          // Subject, corresponding subject library JS needs to be introduced in advance
          theme: 'idea',
          // set number
          lineNumbers: true,
          line: true,
          extraKeys: {"Ctrl": "autocomplete"},
        },
        // The syntax highlighting type that supports switching. The corresponding JS has been introduced in advance
        // MIME type is used, but text / as prefix is written dead when specified later
        modes: [{
          value: 'css',
          label: 'CSS'
        }, {
          value: 'javascript',
          label: 'Javascript'
        }, {
          value: 'html',
          label: 'XML/HTML'
        },  {
          value: 'x-sh',
          label: 'Shell'
        }, {
          value: 'x-sql',
          label: 'SQL'
        }],
        // Pop up layer title
        title: "",
        titleBBB: "",
        // Show pop-up layer or not
        open: false,
        openBBB: false,
    },

    methods: {
      // Initialization
      _initialize() {
        // Initialize the editor instance, pass in the text field object and default configuration to be instantiated
        this.coder = CodeMirror.fromTextArea(this.$refs.textarea, this.options)
        // Editor assignment
        this.coder.setValue(this.value || this.code)

        // Supports two-way binding
        this.coder.on('change', (coder) => {
          this.code = coder.getValue()

          if (this.$emit) {
            this.$emit('input', this.code)
          }
        })

        // Attempt to get syntax type from parent container
        if (this.language) {
          // Get specific syntax type object
          let modeObj = this._getLanguage(this.language)

          // Determine whether the syntax passed in by the parent container is supported
          if (modeObj) {
            this.mode = modeObj.label
          }
        }
      },
      // Get the current syntax type
      _getLanguage(language) {
        // Find the incoming syntax type in the list of supported syntax types
        return this.modes.find((mode) => {
          // All values ignore case for comparison
          let currentLanguage = language.toLowerCase()
          let currentLabel = mode.label.toLowerCase()
          let currentValue = mode.value.toLowerCase()

          // Because the real value may not be standardized, for example, the real value of Java is x-java, so it is said that value and label are compared with the incoming syntax at the same time
          return currentLabel === currentLanguage || currentValue === currentLanguage
        })
      },
      // Change mode
      changeMode(val) {
        // Modify the syntax configuration of the editor
        this.coder.setOption('mode', `text/${val}`)

        // Get the modified syntax
        let label = this._getLanguage(val).label.toLowerCase()

        // Allows the parent container to listen for the current syntax value through the following functions
        this.$emit('language-change', label)
      },
          
      initUI(value, orig) {
        if (value == null) return;
        let target = document.getElementById("view");
        target.innerHTML = "";
        CodeMirror.MergeView(target, {
          value: value,//Last content
          origLeft: null,
          orig: orig,//This content
          lineNumbers: true,//set number
          mode: "shell",
          highlightDifferences: true,
          styleActiveLine: true,
          matchBrackets: true,
          connect: 'align',
          readOnly: true,//Read only and immutable
        });
      },
      
      /** Button operation */
      handleAdd() {
        this.open = true;
        this.title = "Code editing, highlight completion";
          
        this.$nextTick(function () {
          this._initialize();
        });
          
      },
          
      /** Button operation */    
      handleUpdateBBB() {
        this.openBBB = true;
        this.titleBBB = "Code version, difference comparison";
          
        // Initialize version differences
        this.$nextTick(function () {
          this.initUI("#!/bin/bash\n" +
            "ip=\"123.21.12.11\"\n" +
            "email=\"lgx@example\"\n" +
            " \n" +
            "while 1\n" +
            "do\n" +
            "  ping -c10 $ip > /dev/null 2>/dev/null\n" +
            "  if [ $? != \"0\" ]\n" +
            "  then\n" +
            "       # Call a script to send mail \ n "+
            "     python /usr/local/sbin/mail.py $email \"$ip down\" \"$ip is down\"\n" +
            "  fi\n" +
            "  sleep 30\n" +
            "done", "#!/bin/bash\n" +
            "ip=\"123.21.12.11\"\n" +
            "email=\"admin@example\"\n" +
            " \n" +
            "while 1\n" +
            "do\n" +
            "  ping -c10 $ip > /dev/null 2>/dev/null\n" +
            "  if [ $? != \"0\" ]\n" +
            "  then\n" +
            "       # Call a script to send mail \ n "+
            "     python /usr/local/sbin/mail.py $email \"$ip down\" \"$ip is down\"\n" +
            "  fi\n" +
            "done")
        });
      },    
          
    }
  };
</script>

<style lang="stylus" rel="stylesheet/stylus">
  .in-coder-panel
    flex-grow 1
    display flex
    position relative

    .CodeMirror
      flex-grow 1
      z-index 1
      .CodeMirror-code
        line-height 19px

    .code-mode-select
      position absolute
      z-index 2
      right 10px
      top 10px
      max-width 130px
</style>

After running, the code highlights as follows;

After running, on the code auto completion effect class, I bind the Ctrl key, input the first few letters of the keyword, and press and hold the Ctrl key to automatically complete according to the selected language;

After running, the code version difference comparison effect is as follows:;

Scroll either side of the vertical scroll bar or the horizontal scroll bar, the other side will also move with the amplitude.

Posted by deltatango5 on Fri, 17 Apr 2020 01:50:42 -0700