123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- // CodeMirror, copyright (c) by Marijn Haverbeke and others
- // Distributed under an MIT license: https://codemirror.net/LICENSE
- // Define search commands. Depends on dialog.js or another
- // implementation of the openDialog method.
- // Replace works a little oddly -- it will do the replace on the next
- // Ctrl-G (or whatever is bound to findNext) press. You prevent a
- // replace by making sure the match is no longer selected when hitting
- // Ctrl-G.
- ;(function (mod) {
- if (typeof exports == 'object' && typeof module == 'object')
- // CommonJS
- mod(require('../../lib/codemirror'), require('./searchcursor'), require('../dialog/dialog'))
- else if (typeof define == 'function' && define.amd)
- // AMD
- define(['../../lib/codemirror', './searchcursor', '../dialog/dialog'], mod)
- // Plain browser env
- else mod(CodeMirror)
- })(function (CodeMirror) {
- 'use strict'
- // default search panel location
- CodeMirror.defineOption('search', { bottom: false })
- 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 (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
- }
- function getSearchState(cm) {
- return cm.state.search || (cm.state.search = new SearchState())
- }
- function queryCaseInsensitive(query) {
- return typeof query == 'string' && query == query.toLowerCase()
- }
- function getSearchCursor(cm, query, pos) {
- // Heuristic: if the query string is all lowercase, do a case insensitive search.
- return cm.getSearchCursor(query, pos, { caseFold: queryCaseInsensitive(query), multiline: true })
- }
- function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
- cm.openDialog(text, onEnter, {
- value: deflt,
- selectValueOnOpen: true,
- closeOnEnter: false,
- onClose: function () {
- clearSearch(cm)
- },
- onKeyDown: onKeyDown,
- bottom: cm.options.search.bottom,
- })
- }
- function dialog(cm, text, shortText, deflt, f) {
- if (cm.openDialog) cm.openDialog(text, f, { value: deflt, selectValueOnOpen: true, bottom: cm.options.search.bottom })
- else f(prompt(shortText, deflt))
- }
- function confirmDialog(cm, text, shortText, fs) {
- if (cm.openConfirm) cm.openConfirm(text, fs)
- else if (confirm(shortText)) fs[0]()
- }
- function parseString(string) {
- return string.replace(/\\([nrt\\])/g, function (match, ch) {
- if (ch == 'n') return '\n'
- if (ch == 'r') return '\r'
- if (ch == 't') return '\t'
- if (ch == '\\') return '\\'
- return match
- })
- }
- function parseQuery(query) {
- var isRE = query.match(/^\/(.*)\/([a-z]*)$/)
- if (isRE) {
- try {
- query = new RegExp(isRE[1], isRE[2].indexOf('i') == -1 ? '' : 'i')
- } catch (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
- }
- function startSearch(cm, state, query) {
- 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))
- }
- }
- function doSearch(cm, rev, persistent, immediate) {
- var state = getSearchState(cm)
- if (state.query) return findNext(cm, rev)
- var q = cm.getSelection() || state.lastQuery
- if (q instanceof RegExp && q.source == 'x^') q = null
- if (persistent && cm.openDialog) {
- var hiding = null
- var searchNext = function (query, event) {
- CodeMirror.e_stop(event)
- if (!query) return
- if (query != state.queryText) {
- startSearch(cm, state, query)
- state.posFrom = state.posTo = cm.getCursor()
- }
- if (hiding) hiding.style.opacity = 1
- findNext(cm, event.shiftKey, function (_, to) {
- var dialog
- if (
- to.line < 3 &&
- document.querySelector &&
- (dialog = cm.display.wrapper.querySelector('.CodeMirror-dialog')) &&
- dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, 'window').top
- )
- (hiding = dialog).style.opacity = 0.4
- })
- }
- persistentDialog(cm, getQueryDialog(cm), q, searchNext, function (event, query) {
- var keyName = CodeMirror.keyName(event)
- var extra = cm.getOption('extraKeys'),
- cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption('keyMap')][keyName]
- if (cmd == 'findNext' || cmd == 'findPrev' || cmd == 'findPersistentNext' || cmd == 'findPersistentPrev') {
- CodeMirror.e_stop(event)
- startSearch(cm, getSearchState(cm), query)
- cm.execCommand(cmd)
- } else if (cmd == 'find' || cmd == 'findPersistent') {
- CodeMirror.e_stop(event)
- searchNext(query, event)
- }
- })
- if (immediate && q) {
- startSearch(cm, state, q)
- findNext(cm, rev)
- }
- } else {
- dialog(cm, getQueryDialog(cm), 'Search for:', q, function (query) {
- if (query && !state.query)
- cm.operation(function () {
- startSearch(cm, state, query)
- state.posFrom = state.posTo = cm.getCursor()
- findNext(cm, rev)
- })
- })
- }
- }
- function findNext(cm, rev, callback) {
- cm.operation(function () {
- var state = getSearchState(cm)
- var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo)
- if (!cursor.find(rev)) {
- cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0))
- if (!cursor.find(rev)) 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())
- })
- }
- 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
- }
- })
- }
- function el(tag, attrs) {
- var element = tag ? document.createElement(tag) : document.createDocumentFragment()
- for (var key in attrs) {
- element[key] = attrs[key]
- }
- for (var i = 2; i < arguments.length; i++) {
- var child = arguments[i]
- element.appendChild(typeof child == 'string' ? document.createTextNode(child) : child)
- }
- return element
- }
- function getQueryDialog(cm) {
- return el(
- '',
- null,
- el('span', { className: 'CodeMirror-search-label' }, cm.phrase('Search:')),
- ' ',
- el('input', { type: 'text', style: 'width: 10em', className: 'CodeMirror-search-field' }),
- ' ',
- el('span', { style: 'color: #888', className: 'CodeMirror-search-hint' }, cm.phrase('(Use /re/ syntax for regexp search)'))
- )
- }
- function getReplaceQueryDialog(cm) {
- return el(
- '',
- null,
- ' ',
- el('input', { type: 'text', style: 'width: 10em', className: 'CodeMirror-search-field' }),
- ' ',
- el('span', { style: 'color: #888', className: 'CodeMirror-search-hint' }, cm.phrase('(Use /re/ syntax for regexp search)'))
- )
- }
- function getReplacementQueryDialog(cm) {
- return el('', null, el('span', { className: 'CodeMirror-search-label' }, cm.phrase('With:')), ' ', el('input', { type: 'text', style: 'width: 10em', className: 'CodeMirror-search-field' }))
- }
- function getDoReplaceConfirm(cm) {
- return el(
- '',
- null,
- el('span', { className: 'CodeMirror-search-label' }, cm.phrase('Replace?')),
- ' ',
- el('button', {}, cm.phrase('Yes')),
- ' ',
- el('button', {}, cm.phrase('No')),
- ' ',
- el('button', {}, cm.phrase('All')),
- ' ',
- el('button', {}, cm.phrase('Stop'))
- )
- }
- 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)
- }
- })
- }
- function replace(cm, all) {
- if (cm.getOption('readOnly')) return
- var query = cm.getSelection() || getSearchState(cm).lastQuery
- var dialogText = all ? cm.phrase('Replace all:') : cm.phrase('Replace:')
- var fragment = el('', null, el('span', { className: 'CodeMirror-search-label' }, dialogText), getReplaceQueryDialog(cm))
- dialog(cm, fragment, dialogText, query, function (query) {
- if (!query) return
- query = parseQuery(query)
- dialog(cm, getReplacementQueryDialog(cm), cm.phrase('Replace with:'), '', function (text) {
- text = parseString(text)
- if (all) {
- replaceAll(cm, query, text)
- } else {
- clearSearch(cm)
- var cursor = getSearchCursor(cm, query, cm.getCursor('from'))
- var advance = function () {
- var start = cursor.from(),
- match
- if (!(match = cursor.findNext())) {
- cursor = getSearchCursor(cm, query)
- if (!(match = cursor.findNext()) || (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() })
- confirmDialog(cm, getDoReplaceConfirm(cm), cm.phrase('Replace?'), [
- function () {
- doReplace(match)
- },
- advance,
- function () {
- replaceAll(cm, query, text)
- },
- ])
- }
- var doReplace = function (match) {
- cursor.replace(
- typeof query == 'string'
- ? text
- : text.replace(/\$(\d)/g, function (_, i) {
- return match[i]
- })
- )
- advance()
- }
- advance()
- }
- })
- })
- }
- CodeMirror.commands.find = function (cm) {
- clearSearch(cm)
- doSearch(cm)
- }
- CodeMirror.commands.findPersistent = function (cm) {
- clearSearch(cm)
- doSearch(cm, false, true)
- }
- CodeMirror.commands.findPersistentNext = function (cm) {
- doSearch(cm, false, true, true)
- }
- CodeMirror.commands.findPersistentPrev = function (cm) {
- doSearch(cm, true, true, true)
- }
- CodeMirror.commands.findNext = doSearch
- CodeMirror.commands.findPrev = function (cm) {
- doSearch(cm, true)
- }
- CodeMirror.commands.clearSearch = clearSearch
- CodeMirror.commands.replace = replace
- CodeMirror.commands.replaceAll = function (cm) {
- replace(cm, true)
- }
- })
|