create-react-app中使用CodeMirror輕量級代碼編輯器,實現自動提示,自動補全代碼問題

構建create-react-app,這裏忽略······

安裝   代碼編輯器 CodeMirror 的輕量級 React 組件

npm install @uiw/react-codemirror --save

安裝好了之後,就可以直接引入使用了,直接上代碼:

import CodeMirror from '@uiw/react-codemirror';
import 'codemirror/theme/monokai.css';
import 'codemirror/addon/selection/active-line'
import 'codemirror/addon/hint/javascript-hint'
import 'codemirror/addon/hint/show-hint'
import 'codemirror/addon/hint/show-hint.css'


class App extends React.Component{
    render() {
      const code = 'var a = 0;';
        return (
            <div>
              <CodeMirror
                  value={code}
                  options={{
                    theme: 'monokai',
                    mode: 'JavaScript',
                    extraKeys: {"Ctrl": "autocomplete"},//ctrl可以彈出提示
                    styleActiveLine: true
                  }}
              />
            </div>
        );
    }
}
export default App;

代碼解析:

import CodeMirror from '@uiw/react-codemirror';    //引入組件,然後這個<CodeMirror />標籤組件就是代碼編輯器了

import 'codemirror/theme/monokai.css';    //主題樣式 引入後 配置其屬性  theme: 'monokai'

import 'codemirror/addon/hint/javascript-hint'    //光標所在行突出顏色   引入後 配置其屬性  styleActiveLine: true

//JavaScript代碼提示功能需要引入三個依賴

import 'codemirror/addon/hint/javascript-hint'    //js代碼提示語庫

import 'codemirror/addon/hint/show-hint'    //代碼提示功能

import 'codemirror/addon/hint/show-hint.css'    //代碼提示樣式

以上三個依賴引入後,配置其屬性    

mode: 'JavaScript',  //提示語言
extraKeys: {"Ctrl": "autocomplete"},//ctrl可以彈出提示

這樣就有了JavaScript的代碼提示,這裏僅僅用了JavaScript來示範,其他語言同理。

到這裏雖然有了提示,但是按一個快捷鍵纔會觸發的提示,遠沒達到我們想要的自動提示的效果。

接下來,想要做成自動提示,一般想到的是,從onChange 函數入手

值得注意的是,CodeMirror不能用onChange函數來觸發代碼提示功能,會死循環。。

我們用的是 onCursorActivity 事件函數  //當鼠標點擊內容區、選中內容、修改內容時被觸發

onCursorActivity={e => e.showHint()/*調用顯示提示*/}

這樣自動提示功能就有了。

但是呢,還有問題需要處理,就是點擊和選中,換行,空格,等都會觸發提示,而且關鍵詞刪除不了等問題。

這樣就比較尷尬了,需要優化一下。

打開 import 'codemirror/addon/hint/javascript-hint' 這個的源碼。

先找到 scriptHint 函數 ,在該函數最後的 return 調 getCompletions函數,這裏開始做手腳。

獻上改動的代碼部分:

// If it is a property, find out what it is a property of.
    while (tprop.type == "property") {
      tprop = getToken(editor, Pos(cur.line, tprop.start));
      if (tprop.string != ".") return;
      tprop = getToken(editor, Pos(cur.line, tprop.start));
      if (!context) var context = [];
      context.push(tprop);
    }

    var uValues = this.uValues;//獲取上次的內容
    this.uValues = editor.getValue();//更新上次內容
    var isUpdate = uValues==this.uValues;//比較內容是否和上次的一樣

    return {list: getCompletions(token, context,isUpdate, keywords, options),
            from: Pos(cur.line, token.start),
            to: Pos(cur.line, token.end)};
  }

找到 getCompletions 函數,添加一下代碼:

獻上改動的代碼:

function getCompletions(token, context,isUpdate, keywords, options) {
    //這裏是優化 點擊,選中和改動 都會觸發提示,而且關鍵詞刪除不了等問題
    var start = this.start;
    var end = this.end;
    this.start = token.start;
    this.end = token.end;
    if (token.type == null || (start == this.start  && !((end - this.end) == -1))||isUpdate) {
      return {list: {}};
    }

···

這樣就可以完美解決以上問題了。

不過除了關鍵詞的提示外,我們還可以添加我們自定義的提示詞哦。

獻上全部代碼:

import React from 'react';
import CodeMirror from '@uiw/react-codemirror';
import 'codemirror/theme/monokai.css';
import 'codemirror/addon/hint/javascript-hint'
import 'codemirror/addon/hint/show-hint'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/selection/active-line'

class App extends React.Component{

  MyOnCursorActivity = e =>{
    //把自定義的提示詞傳進去
    e.ukeys = ["aaaa","bbbb","cccc"];
    //調用顯示提示
    e.showHint()
  }

    render() {
      const code = 'var a = 0;';
        return (
            <div>
              <CodeMirror
                  value={code}
                  onCursorActivity={e => this.MyOnCursorActivity(e)}
                  options={{
                    theme: 'monokai',
                    mode: 'JavaScript',
                    styleActiveLine: true
                  }}
              />
            </div>
        );
    }
}
export default App;
import 'codemirror/addon/hint/javascript-hint' 改動後的代碼:
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
  var Pos = CodeMirror.Pos;

  function forEach(arr, f) {
    for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
  }

  function arrayContains(arr, item) {
    if (!Array.prototype.indexOf) {
      var i = arr.length;
      while (i--) {
        if (arr[i] === item) {
          return true;
        }
      }
      return false;
    }
    return arr.indexOf(item) != -1;
  }

  function scriptHint(editor, keywords, getToken, options) {
    // Find the token at the cursor
    var cur = editor.getCursor(), token = getToken(editor, cur);
    if (/\b(?:string|comment)\b/.test(token.type)) return;
    var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
    if (innerMode.mode.helperType === "json") return;
    token.state = innerMode.state;

    // If it's not a 'word-style' token, ignore the token.
    if (!/^[\w$_]*$/.test(token.string)) {
      token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
               type: token.string == "." ? "property" : null};
    } else if (token.end > cur.ch) {
      token.end = cur.ch;
      token.string = token.string.slice(0, cur.ch - token.start);
    }

    var tprop = token;
    // If it is a property, find out what it is a property of.
    while (tprop.type == "property") {
      tprop = getToken(editor, Pos(cur.line, tprop.start));
      if (tprop.string != ".") return;
      tprop = getToken(editor, Pos(cur.line, tprop.start));
      if (!context) var context = [];
      context.push(tprop);
    }

    var ukeys =  editor.ukeys;//獲取用戶的自定義的單詞
    var uValues = this.uValues;//獲取上次的內容
    this.uValues = editor.getValue();//更新上次內容
    var isUpdate = uValues==this.uValues;//比較內容是否和上次的一樣

    return {list: getCompletions(token, context,ukeys,isUpdate, keywords, options),
            from: Pos(cur.line, token.start),
            to: Pos(cur.line, token.end)};
  }

  function javascriptHint(editor, options) {
    return scriptHint(editor, javascriptKeywords,
                      function (e, cur) {return e.getTokenAt(cur);},
                      options);
  };
  CodeMirror.registerHelper("hint", "javascript", javascriptHint);

  function getCoffeeScriptToken(editor, cur) {
  // This getToken, it is for coffeescript, imitates the behavior of
  // getTokenAt method in javascript.js, that is, returning "property"
  // type and treat "." as indepenent token.
    var token = editor.getTokenAt(cur);
    if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
      token.end = token.start;
      token.string = '.';
      token.type = "property";
    }
    else if (/^\.[\w$_]*$/.test(token.string)) {
      token.type = "property";
      token.start++;
      token.string = token.string.replace(/\./, '');
    }
    return token;
  }

  function coffeescriptHint(editor, options) {
    return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
  }
  CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);

  var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
                     "toUpperCase toLowerCase split concat match replace search").split(" ");
  var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
                    "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
  var funcProps = "prototype apply call bind".split(" ");
  var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
                  "if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" ");
  var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
                  "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");

  function forAllProps(obj, callback) {
    if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
      for (var name in obj) callback(name)
    } else {
      for (var o = obj; o; o = Object.getPrototypeOf(o))
        Object.getOwnPropertyNames(o).forEach(callback)
    }
  }

  function getCompletions(token, context,ukeys,isUpdate, keywords, options) {
    //這裏是優化 點擊,選中和改動 都會觸發提示,而且關鍵詞刪除不了等問題
    var start = this.start;
    var end = this.end;
    this.start = token.start;
    this.end = token.end;
    if (token.type == null || (start == this.start  && !((end - this.end) == -1))||isUpdate) {
      return {list: {}};
    }

    var found = [], start = token.string, global = options && options.globalScope || window;
    function maybeAdd(str) {
      if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
    }
    function gatherCompletions(obj) {
      if (typeof obj == "string") forEach(stringProps, maybeAdd);
      else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
      else if (obj instanceof Function) forEach(funcProps, maybeAdd);
      if(ukeys!=null&&ukeys.length>0){
        forEach(ukeys,maybeAdd);//添加傳進來的自定義的單詞
      }
      forAllProps(obj, maybeAdd)
    }

    if (context && context.length) {
      // If this is a property, see if it belongs to some object we can
      // find in the current environment.
      var obj = context.pop(), base;
      if (obj.type && obj.type.indexOf("variable") === 0) {
        if (options && options.additionalContext)
          base = options.additionalContext[obj.string];
        if (!options || options.useGlobalScope !== false)
          base = base || global[obj.string];
      } else if (obj.type == "string") {
        base = "";
      } else if (obj.type == "atom") {
        base = 1;
      } else if (obj.type == "function") {
        if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
            (typeof global.jQuery == 'function'))
          base = global.jQuery();
        else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
          base = global._();
      }
      while (base != null && context.length)
        base = base[context.pop().string];
      if (base != null) gatherCompletions(base);
    } else {
      // If not, just look in the global object and any local scope
      // (reading into JS mode internals to get at the local and global variables)
      for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
      for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
      if (!options || options.useGlobalScope !== false)
        gatherCompletions(global);
      forEach(keywords, maybeAdd);
    }
    return found;
  }
});

如果需要顯示用戶前面輸入過的代碼,變量等。還可以用正則表達式,把編輯器的內容拆分出來然後傳進提示詞裏即可:

MyOnCursorActivity 函數,稍微做一下修改如下:

MyOnCursorActivity = e =>{
    //獲取用戶當前的編輯器中的編寫的代碼
    var words = e.getValue() + "";
    //利用正則取出用戶輸入的所有的英文的字母
    words = words.replace(/[a-z]+[\-|\']+[a-z]+/ig, '').match(/([a-z]+)/ig);
    //把單個字母去除掉
    var isFor=true;
    while (isFor){
      if(words==null){break}
      isFor=false;
      for(var i=0;i<words.length;i++){
        if (words[i].length==1) {
          var s=words.splice(i, 1);
          isFor=true;
          break;
        }
      }
    }
    //將獲取到的用戶的單詞傳入CodeMirror,並在javascript-hint中做匹配
    e.ukeys = words;
    //調用顯示提示
    e.showHint()
  }

這個就可以是挺完美的了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章