UNPKG

wed

Version:

Wed is a schema-aware editor for XML documents.

184 lines 7.51 kB
define(["require", "exports", "../dloc", "../domutil", "../search"], function (require, exports, dloc_1, domutil_1, search_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Context = search_1.Context; exports.Direction = search_1.Direction; var Edge; (function (Edge) { Edge[Edge["START"] = 0] = "START"; Edge[Edge["END"] = 1] = "END"; })(Edge || (Edge = {})); /** * A search-and-replace engine for editor instances. This implements the code * that is common to quick searches and more complex searches. This object is * responsible for maintaining a search position in the document, and replacing * hits as required. */ class SearchReplace { /** * @param editor The editor for which we are searching. * * @param scroller The scroller holding the document. */ constructor(editor, scroller) { this.editor = editor; this.scroller = scroller; this.lastMatch = null; this.caretManager = this.editor.caretManager; const sel = this.caretManager.sel; const scope = (sel !== undefined && !sel.collapsed) ? new dloc_1.DLocRange(sel.anchor, sel.focus) : undefined; // If we have a scope, then we had a selection and we want to use the // selection's anchor, which is scope.start at this point. const start = scope !== undefined ? scope.start : this.caretManager.caret; if (start === undefined) { throw new Error("search without a caret!"); } this.search = new search_1.Search(this.caretManager, editor.guiRoot, start, scope); } /** * The current match. This is ``undefined`` if we have not searched yet. It * is ``null`` if nothing matches. */ get current() { return this.search.current; } /** * Whether we can replace the current hit. If there is no hit, then this is * ``false``. If the hit is somehow collapsed, this is also * ``false``. Otherwise, the hit must be a well-formed range. */ get canReplace() { const current = this.search.current; if (current == null) { return false; } if (current.collapsed) { return false; } return domutil_1.isWellFormedRange(current.mustMakeDOMRange()); } /** * Update the pattern to a new value. Calling this method attempts to update * the current hit first, and may move in the direction of the search if * updating the current hit is not possible. This updates [[current]]. * * @param value The new pattern value. * * @param options The search options. */ updatePattern(value, options) { this.search.pattern = value; this.search.direction = options.direction; this.search.context = options.context; this.search.updateCurrent(); this.updateHighlight(); } /** * Find the next hit in the direction of the search. This updates [[current]]. * * @param options The search options. */ next(options) { this.search.direction = options.direction; this.search.context = options.context; this.search.next(); this.updateHighlight(); } /** * Update the highlight marking the current hit. */ updateHighlight() { this.clearHighlight(); const match = this.current; if (match != null) { this.lastMatch = match; this.setCaretToMatch(); const range = match.start.mustMakeDLocRange(match.end); const domRange = range.mustMakeDOMRange(); this.highlight = this.caretManager.highlightRange(range); const scRect = this.scroller.getBoundingClientRect(); const rect = domRange.getBoundingClientRect(); const leftOffset = this.scroller.scrollLeft - scRect.left; const topOffset = this.scroller.scrollTop - scRect.top; this.scroller.scrollIntoView(rect.left + leftOffset, rect.top + topOffset, rect.right + leftOffset, rect.bottom + topOffset); } } /** * Clear the highlight that this object produced to mark a hit. */ clearHighlight() { if (this.highlight !== undefined) { this.highlight.parentNode.removeChild(this.highlight); this.highlight = undefined; } } /** * Set the caret position to the latest hit we ran into. */ setCaretToMatch() { if (this.lastMatch !== null) { const loc = this.getDirectionalEnd(this.lastMatch); this.caretManager.setCaret(loc, { focus: false }); } } getDirectionalEnd(range) { return this.getDirectionalEdge(range, Edge.END); } getDirectionalStart(range) { return this.getDirectionalEdge(range, Edge.START); } getDirectionalEdge(range, edge) { let field; const direction = this.search.direction; const start = edge === Edge.START; switch (direction) { case search_1.Direction.FORWARD: field = start ? "start" : "end"; break; case search_1.Direction.BACKWARDS: field = start ? "end" : "start"; break; default: const d = direction; throw new Error(`unknown direction: ${d}`); } return range[field]; } /** * Replace the current hit with text. * * @param value The new text. * * @throw {Error} When called if [[canReplace]] is false. */ replace(value) { if (!this.canReplace) { throw new Error("tried to replace when it is not possible"); } const current = this.current; // With the !this.canReplace test above, it is not currently possible to // hit this condition. if (current == null) { throw new Error("no current match"); } const caret = this.getDirectionalStart(current); this.caretManager.setCaret(caret, { focus: false }); // tslint:disable-next-line:no-object-literal-type-assertion this.editor.fireTransformation(this.editor.replaceRangeTr, { range: current, newText: value, caretAtEnd: false, }); this.clearHighlight(); const caretAfter = this.caretManager.caret; if (caretAfter === undefined) { throw new Error("no caret after replacement!"); } // We must update the current match because the old range is no longe valid. this.search.current = caretAfter.mustMakeDLocRange(); } } exports.SearchReplace = SearchReplace; }); //# sourceMappingURL=search-replace.js.map