How can I search multiple instances of codemirror?

Let's say I have the following simple page with two instances of CodeMirror:

const body = document.querySelector('body') const title = document.createElement('h1') title.textContent = 'This is a document with multiple CodeMirrors' body.appendChild(title); const area1 = document.createElement('textarea') body.appendChild(area1) const editor1 = CodeMirror.fromTextArea(area1, { lineNumbers: true, }) const segway = document.createElement('h2') segway.textContent = 'Moving on to another editor' body.appendChild(segway) const area2 = document.createElement('textarea') body.appendChild(area2) const editor2 = CodeMirror.fromTextArea(area2, { lineNumbers: true, }) 

and what I turned on

  • codemirror/addon/search/search
  • codemirror/addon/search/searchcursor
  • codemirror/addon/dialog/dialog

Each CodeMirror instance now has its own search handler when it is focused on the editor (launched via ctrl / cmd-f). How can I implement a search / replace that works across multiple instances of CodeMirror?

At least one way to perform find for each editor: editor.execCommand . I see no way to go to him or ask what results are available.

CodePen with code example and import

GitHub issue for a project wanting to use this, nteract .

In a problem with CodeMirror, Marijn states: "You will have to code it yourself." This is fair - I'm not sure how to approach this.

+5
source share
1 answer

The find and replace commands are associated with addons and there seems to be no way to access them through instances, at least not with a request that is not passed through the dialog.

But you can restore most of what is in search.js and add it as an extension to which you can pass the request. But then you will need to set up a global dialog box or a way to get an instance-independent request and run it on each instance. Something like this should work, it is only for search, but replacing should also be easy:

 CodeMirror.defineExtension('search', function(query) { // This is all taken from search.js, pretty much as is for the first part. 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 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 queryCaseInsensitive(query) { return typeof query == "string" && query == query.toLowerCase(); } function parseString(string) { return string.replace(/\\(.)/g, function(_, ch) { if (ch == "n") return "\n" if (ch == "r") return "\r" return ch }) } function parseQuery(query) { var isRE = query.match(/^\/(.*)\/([az]*)$/); 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; } // From here it still from search.js, but a bit tweaked so it applies // as an extension, these are basically clearSearch and startSearch. var state = getSearchState(this); state.lastQuery = state.query; state.query = state.queryText = null; this.removeOverlay(state.overlay); if (state.annotate) { state.annotate.clear(); state.annotate = null; } state.queryText = query; state.query = parseQuery(query); this.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); this.addOverlay(state.overlay); if (this.showMatchesOnScrollbar) { if (state.annotate) { state.annotate.clear(); state.annotate = null; } state.annotate = this.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); } }); // this is to have an external input, but you could have your own way of // providing your query. Important thing is that you can run search on // an instance with a query. const body = document.querySelector('body') const searchAll = document.createElement('input'); body.appendChild(searchAll); searchAll.placeholder = 'Search All'; searchAll.addEventListener('input', function(e) { var query = e.target.value; var codeMirrorInstances = document.getElementsByClassName('CodeMirror'); for (var i = 0; i < codeMirrorInstances.length; i++) { var curInst = codeMirrorInstances[i].CodeMirror; curInst.search(query); } }); const title = document.createElement('h1') title.textContent = 'This is a document with multiple CodeMirrors' body.appendChild(title); const area1 = document.createElement('textarea') body.appendChild(area1) const editor1 = CodeMirror.fromTextArea(area1, { lineNumbers: true, }) const segway = document.createElement('h2') segway.textContent = 'Moving on to another editor' body.appendChild(segway) const area2 = document.createElement('textarea') body.appendChild(area2) const editor2 = CodeMirror.fromTextArea(area2, { lineNumbers: true, }); 

http://codepen.io/anon/pen/yavrRk?editors=0010

EDIT:

Other commands, such as findNext , will work fine once the query is applied, but of course it also depends on the instance. If you need to implement findNext for all instances, it becomes more complex, you need to manage different things, such as the current focused instance, and override certain types of behavior, such as findNext such things. This can be done, but depending on the required level of accuracy, it can be very complex. Something like this works, it is not very elegant, but shows how to do it:

 CodeMirror.defineExtension('findNext', function(query) { 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()); } // You tweak findNext a bit so it doesn't loop and so that it returns // false when at the last occurence. You could make findPrevious as well var state = getSearchState(this); var cursor = this.getSearchCursor(state.query, state.posTo, state.query.toLowerCase()); if (!cursor.find(false)) { state.posTo = CodeMirror.Pos(0, 0); this.setSelection(CodeMirror.Pos(0, 0)); return false; } else { this.setSelection(cursor.from(), cursor.to()); this.scrollIntoView({ from: cursor.from(), to: cursor.to() }, 20); state.posFrom = cursor.from(); state.posTo = cursor.to(); return true; } }); // You make a find next button that will handle all instances const findNextBtn = document.createElement('button'); body.appendChild(findNextBtn); findNextBtn.textContent = 'Find next'; findNextBtn.addEventListener('click', function(e) { // Here you need to keep track of where you want to start the search // and iterating through all instances. var curFocusIndex = -1; var codeMirrorInstances = Array.prototype.slice.call(document.getElementsByClassName('CodeMirror')); var focusedIndex = codeMirrorInstances.indexOf(lastFocused.getWrapperElement()); // with the new return in findNext you can control when you go to // next instance var findInCurCm = lastFocused.findNext(); while (!findInCurCm && curFocusIndex !== focusedIndex) { curFocusIndex = codeMirrorInstances.indexOf(lastFocused.getWrapperElement()) + 1; curFocusIndex = curFocusIndex === codeMirrorInstances.length ? 0 : curFocusIndex; codeMirrorInstances[curFocusIndex].CodeMirror.focus(); lastFocused = codeMirrorInstances[curFocusIndex].CodeMirror; var findInCurCm = lastFocused.findNext(); } }); 

http://codepen.io/anon/pen/ORvJpK?editors=0010

+2
source

Source: https://habr.com/ru/post/1257594/


All Articles