CodeMirror编辑器自定义搜索框

首先安装自定义搜索插件

npm install --save codemirror-revisedsearch

然后新建一个codemirror-revisedsearch.js文件,因为这个搜索插件本身的样式并不符合预期,当然想要插件的就不用单独新建js文件了。

codemirror-revisedsearch.js代码:

'use strict'

var _typeof = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? function (obj) { return typeof obj } : function (obj) { return obj && typeof Symbol === 'function' && obj.constructor === Symbol && obj !== Symbol.prototype ? 'symbol' : typeof obj };

// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

// Revised search plugin written by Jamie Morris
// Define search commands. Depends on advanceddialog.js
(function (mod) {
  if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) == 'object' && (typeof module === 'undefined' ? 'undefined' : _typeof(module)) == 'object') // CommonJS
  {
    mod(require('codemirror'), require('codemirror-advanceddialog'))
  }
  //  else if (typeof define == 'function' && define.amd) {
  //   console.log('初始化2');
  //   define(['codemirror', 'codemirror-advanceddialog'], mod);
  // }
  // else {
  //   console.log('初始化3');
  //   mod(CodeMirror)
  // }// Plain browser env

})(function (CodeMirror) {
  'use strict'

  var replaceDialog = `\n    <div class="row find">\n      <label for="CodeMirror-find-field">替换:</label>\n      <input id="CodeMirror-find-field" type="text" class="CodeMirror-search-field" placeholder="Find" />\n      <span class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>\n      <span class="CodeMirror-search-count"></span>\n    </div>\n    <div class="row replace">\n      <label for="CodeMirror-replace-field">With:</label>\n      <input id="CodeMirror-replace-field" type="text" class="CodeMirror-search-field" placeholder="Replace" />\n    </div>\n    <div class="buttons">\n      <button type="button">Find Previous</button>\n      <button type="button">Find Next</button>\n      <button type="button">Replace</button>\n      <button type="button">Replace All</button>\n      <button type="button">Close</button>\n    </div>\n  `

  var findDialog = `\n    <div class="row find">\n      <label for="CodeMirror-find-field"></label>\n      <input id="CodeMirror-find-field" type="text" class="CodeMirror-search-field" placeholder="请输入关键字查询" />\n      <span class="CodeMirror-search-hint"></span>\n      <span class="CodeMirror-search-count">0/0</span>\n    </div>\n    <div class="buttons">\n      <button type="button"><i class="el-icon-top"></i></button>\n      <button type="button"><i class="el-icon-bottom"></i></button>\n      <button type="button"><i class="el-icon-close"></i></button>\n    </div>\n  `

  var numMatches = 0
  var searchOverlay = function searchOverlay(query, caseInsensitive) {
    if (typeof query == 'string') query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), caseInsensitive ? 'gi' : 'g'); else if (!query.global) query = new RegExp(query.source, query.ignoreCase ? 'gi' : 'g')

    return {
      token: function token(stream) {
        query.lastIndex = stream.pos
        var match = query.exec(stream.string)
        if (match && match.index == stream.pos) {
          stream.pos += match[0].length || 1
          return 'searching'
        } else if (match) {
          stream.pos = match.index
        } else {
          stream.skipToEnd()
        }
      }
    }
  }

  function SearchState() {
    this.posFrom = this.posTo = this.lastQuery = this.query = null
    this.overlay = null
  }

  var getSearchState = function getSearchState(cm) {
    return cm.state.search || (cm.state.search = new SearchState())
  }

  var queryCaseInsensitive = function queryCaseInsensitive(query) {
    return typeof query == 'string' && query == query.toLowerCase()
  }

  var getSearchCursor = function getSearchCursor(cm, query, pos) {
    // Heuristic: if the query string is all lowercase, do a case insensitive search.
    return cm.getSearchCursor(parseQuery(query), pos, queryCaseInsensitive(query))
  }

  var parseString = function parseString(string) {
    return string.replace(/\\(.)/g, function (_, ch) {
      if (ch == 'n') return '\n'
      if (ch == 'r') return '\r'
      return ch
    })
  }

  var parseQuery = function parseQuery(query) {
    if (query.exec) {
      return query
    }
    var isRE = query.indexOf('/') === 0 && query.lastIndexOf('/') > 0
    if (isRE) {
      try {
        var matches = query.match(/^\/(.*)\/([a-z]*)$/)
        query = new RegExp(matches[1], matches[2].indexOf('i') == -1 ? '' : 'i')
      } catch (e) {
        console.log(e)
      } // Not a regular expression after all, do a string search
    } else {
      query = parseString(query)
    }
    if (typeof query == 'string' ? query == '' : query.test('')) query = /x^/
    return query
  }

  var startSearch = function startSearch(cm, state, query) {
    if (!query || query === '') return
    state.queryText = query
    state.query = parseQuery(query)
    cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query))
    state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query))
    cm.addOverlay(state.overlay)
    if (cm.showMatchesOnScrollbar) {
      if (state.annotate) {
        state.annotate.clear()
        state.annotate = null
      }
      state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query))
    }
  }

  var doSearch = function doSearch(cm, query, reverse, moveToNext) {
    var hiding = null
    var state = getSearchState(cm)
    if (query != state.queryText) {
      startSearch(cm, state, query)
      state.posFrom = state.posTo = cm.getCursor()
    }
    if (moveToNext || moveToNext === undefined) {
      findNext(cm, reverse || false)
    }
    updateCount(cm)
  }

  var clearSearch = function clearSearch(cm) {
    cm.operation(function () {
      var state = getSearchState(cm)
      state.lastQuery = state.query
      if (!state.query) return
      state.query = state.queryText = null
      cm.removeOverlay(state.overlay)
      if (state.annotate) {
        state.annotate.clear()
        state.annotate = null
      }
    })
  }

  var findNext = function findNext(cm, reverse, callback) {
    cm.operation(function () {
      var state = getSearchState(cm)
      var cursor = getSearchCursor(cm, state.query, reverse ? state.posFrom : state.posTo)
      if (!cursor.find(reverse)) {
        cursor = getSearchCursor(cm, state.query, reverse ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0))
        if (!cursor.find(reverse)) return
      }
      cm.setSelection(cursor.from(), cursor.to())
      cm.scrollIntoView({
        from: cursor.from(),
        to: cursor.to()
      }, 20)
      state.posFrom = cursor.from()
      state.posTo = cursor.to()
      if (callback) callback(cursor.from(), cursor.to())
    })
  }

  var replaceNext = function replaceNext(cm, query, text) {
    var cursor = getSearchCursor(cm, query, cm.getCursor('from'))
    var start = cursor.from()
    var match = cursor.findNext()
    if (!match) {
      cursor = getSearchCursor(cm, query)
      match = cursor.findNext()
      if (!match || start && cursor.from().line === start.line && cursor.from().ch === start?.ch) return
    }
    cm.setSelection(cursor.from(), cursor.to())
    cm.scrollIntoView({
      from: cursor.from(),
      to: cursor.to()
    })
    cursor.replace(typeof query === 'string' ? text : text.replace(/$(\d)/g, function (_, i) {
      return match[i]
    }))
  }

  var replaceAll = function replaceAll(cm, query, text) {
    cm.operation(function () {
      for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
        if (typeof query != 'string') {
          var match = cm.getRange(cursor.from(), cursor.to()).match(query)
          cursor.replace(text.replace(/$(\d)/g, function (_, i) {
            return match[i]
          }))
        } else cursor.replace(text)
      }
    })
  }

  var closeSearchCallback = function closeSearchCallback(cm, state) {
    var states = getSearchState(cm)
    states.posFrom = null
    states.posTo = null
    if (state.annotate) {
      state.annotate.clear()
      state.annotate = null
    }
    clearSearch(cm)
  }

  var getOnReadOnlyCallback = function getOnReadOnlyCallback(callback) {
    var closeFindDialogOnReadOnly = function closeFindDialogOnReadOnly(cm, opt) {
      if (opt === 'readOnly' && !!cm.getOption('readOnly')) {
        callback()
        cm.off('optionChange', closeFindDialogOnReadOnly)
      }
    }
    return closeFindDialogOnReadOnly
  }

  var updateCount = function updateCount(cm) {
    var state = getSearchState(cm)
    var value = cm.getDoc().getValue()
    var globalQuery = void 0
    var queryText = state.queryText

    if (!queryText || queryText === '') {
      resetCount(cm)
      return
    }

    while (queryText.charAt(queryText.length - 1) === '\\') {
      queryText = queryText.substring(0, queryText.lastIndexOf('\\'))
    }

    if (typeof state.query === 'string') {
      globalQuery = new RegExp(queryText, 'ig')
    } else {
      globalQuery = new RegExp(state.query.source, state.query.flags + 'g')
    }

    var matches = value.match(globalQuery)
    var count = matches ? matches.length : 0
    var countText = '0/' + count

    var states = getSearchState(cm)
    var searchMatches = states.annotate.matches
    if (searchMatches) {
      if (states.posTo === null) {
        states.posTo = {
          ch: cm.getCursor().ch,
          line: cm.getCursor().line
        }
      }
      var fa = searchMatches.findIndex(ha => {
        return ha.to?.ch === state.posTo?.ch && ha.to.line === state.posTo.line
      })
      var curSearchIndex = searchMatches.length > 0 ? fa + 1 : 0
      countText = curSearchIndex + '/' + count
    }
    cm.getWrapperElement().parentNode.querySelector('.CodeMirror-search-count').innerHTML = countText
  }

  var resetCount = function resetCount(cm) {
    cm.getWrapperElement().parentNode.querySelector('.CodeMirror-search-count').innerHTML = '0/0'
  }

  var getFindBehaviour = function getFindBehaviour(cm, defaultText, callback) {
    if (!defaultText) {
      defaultText = ''
    }
    var behaviour = {
      value: defaultText,
      focus: true,
      selectValueOnOpen: true,
      closeOnEnter: false,
      closeOnBlur: false,
      callback: function callback(inputs, e) {
        var query = inputs[0].value
        if (!query) return
        doSearch(cm, query, !!e.shiftKey)
      },
      onInput: function onInput(inputs, e) {
        var query = inputs[0].value
        if (!query) {
          resetCount(cm)
          clearSearch(cm)
          return
        }
        doSearch(cm, query, !!e.shiftKey, false)
      }
    }
    if (callback) {
      behaviour.callback = callback
    }
    return behaviour
  }

  var getFindPrevBtnBehaviour = function getFindPrevBtnBehaviour(cm) {
    return {
      callback: function callback(inputs) {
        var query = inputs[0].value
        if (!query) return
        doSearch(cm, query, true)
      }
    }
  }

  var getFindNextBtnBehaviour = function getFindNextBtnBehaviour(cm) {
    return {
      callback: function callback(inputs) {
        var query = inputs[0].value
        if (!query) return
        doSearch(cm, query, false)
      }
    }
  }

  var closeBtnBehaviour = {
    callback: null
  }

  CodeMirror.commands.find = function (cm) {
    if (cm.getOption('readOnly')) return
    clearSearch(cm)
    var state = getSearchState(cm)
    var query = cm.getSelection() || getSearchState(cm).lastQuery
    var closeDialog = cm.openAdvancedDialog(findDialog, {
      shrinkEditor: true,
      inputBehaviours: [getFindBehaviour(cm, query)],
      buttonBehaviours: [getFindPrevBtnBehaviour(cm), getFindNextBtnBehaviour(cm), closeBtnBehaviour],
      onClose: function onClose() {
        closeSearchCallback(cm, state)
      }
    })

    cm.on('optionChange', getOnReadOnlyCallback(closeDialog))
    startSearch(cm, state, query)
    updateCount(cm)
  }

  CodeMirror.commands.replace = function (cm, all) {
    if (cm.getOption('readOnly')) return
    clearSearch(cm)

    var replaceNextCallback = function replaceNextCallback(inputs) {
      var query = parseQuery(inputs[0].value)
      var text = parseString(inputs[1].value)
      if (!query) return
      replaceNext(cm, query, text)
      doSearch(cm, query)
    }

    var state = getSearchState(cm)
    var query = cm.getSelection() || state.lastQuery
    var closeDialog = cm.openAdvancedDialog(replaceDialog, {
      shrinkEditor: true,
      inputBehaviours: [getFindBehaviour(cm, query, function (inputs) {
        inputs[1].focus()
        inputs[1].select()
      }), {
        closeOnEnter: false,
        closeOnBlur: false,
        callback: replaceNextCallback
      }],
      buttonBehaviours: [getFindPrevBtnBehaviour(cm), getFindNextBtnBehaviour(cm), {
        callback: replaceNextCallback
      }, {
        callback: function callback(inputs) {
          // Replace all
          var query = parseQuery(inputs[0].value)
          var text = parseString(inputs[1].value)
          if (!query) return
          replaceAll(cm, query, text)
        }
      }, closeBtnBehaviour],
      onClose: function onClose() {
        closeSearchCallback(cm, state)
      }
    })

    cm.on('optionChange', getOnReadOnlyCallback(closeDialog))
    startSearch(cm, state, query)
    updateCount(cm)
  }
})

这是在下载插件的revised-search.js基础上改的,这样我们引入的时候就不用使用插件的js文件了,而是我们自定义的js文件(第四条),想知道修改了哪里可以跟原文件进行比对。

import 'codemirror/addon/display/panel.js'
import 'codemirror/addon/search/matchesonscrollbar.js'
import 'codemirror/addon/search/matchesonscrollbar.css'
import './codemirror-revisedsearch.js'

css样式:

        // codemirror 查找功能
        ::v-deep .CodeMirror-advanced-dialog {
          position: fixed;
          right: 90px;
          top: 150px;
          z-index: 100;
          padding: 5px;
          border: 1px solid rgba(0, 0, 0, 0.1);
          background-color: rgba(250, 250, 250, 1);
          box-shadow: 1px 1px 12px 0 rgba(0, 0, 0, 0.15);
          display: flex;
          align-items: center;
          .row {
            width: auto;
            display: flex;
            align-items: center;
            .CodeMirror-search-hint {
              display: none;
            }
          }
          .buttons {
            margin-left: 5px;
            button {
              display: inline-block;
              padding: 1px 3px 0px;
              cursor: pointer;
              border: none;
              i {
                font-size: 16px;
                color: #3c72e7;
              }
            }
          }
          .CodeMirror-search-field {
            display: block;
            resize: vertical;
            padding: 5px 15px;
            line-height: 1.5;
            -webkit-box-sizing: border-box;
            box-sizing: border-box;
            width: 100%;
            font-size: inherit;
            color: #1c202d;
            background-color: #fff;
            background-image: none;
            border: 1px solid #dfdfdf;
            height: 32px;
            line-height: 32px;
            -webkit-border-radius: 4px;
            border-radius: 4px;
            -webkit-transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
            -o-transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
            transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
            &:focus {
              outline: 0;
              border-color: #3c72e7;
            }
            &::placeholder {
              color: #c0c4cc;
            }
          }
          .CodeMirror-search-count {
            display: inline-block;
            padding: 0 5px;
            color: #1c202d;
          }
        }

我这里直接写css不生效,使用了::v-deep。

效果图如下:

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值