UNPKG

alm

Version:

The best IDE for TypeScript

575 lines (574 loc) 28 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var ui = require("../../ui"); var csx = require("../../base/csx"); var React = require("react"); var ReactDOM = require("react-dom"); var socketClient_1 = require("../../../socket/socketClient"); var commands = require("../../commands/commands"); var utils = require("../../../common/utils"); var styles = require("../../styles/styles"); var icon_1 = require("../../components/icon"); var Mousetrap = require("mousetrap"); var buttons = require("../../components/buttons"); var types = require("../../../common/types"); var gls = require("../../base/gls"); var typestyle = require("typestyle"); var EOL = '\n'; /** * The styles */ var findAndReplace_1 = require("../../findAndReplace"); var inputBlackStyleBase = styles.Input.inputBlackStyleBase; var inputBlackClassName = typestyle.style(inputBlackStyleBase); var ResultsStyles; (function (ResultsStyles) { ResultsStyles.rootClassName = typestyle.style(csx.flex, csx.scroll, styles.padded1, { border: '1px solid grey', $nest: { '&:focus': { outline: 'none', border: '1px solid ' + styles.highlightColor, } } }); ResultsStyles.header = csx.extend(styles.padded1, { cursor: 'default', fontSize: '1.5em', fontWeight: 'bold', color: styles.textColor, background: 'black', border: '2px solid grey', }); ResultsStyles.preview = { padding: '3px', background: 'black', cursor: 'pointer', userSelect: 'text', }; ResultsStyles.selected = { backgroundColor: styles.selectedBackgroundColor }; ResultsStyles.noFocusClassName = typestyle.style({ $nest: { '&:focus': { outline: 'none' } } }); })(ResultsStyles || (ResultsStyles = {})); var FindAndReplaceView = /** @class */ (function (_super) { __extends(FindAndReplaceView, _super); function FindAndReplaceView(props) { var _this = _super.call(this, props) || this; _this.findInput = function () { return ReactDOM.findDOMNode(_this.refs.find); }; _this.replaceInput = function () { return ReactDOM.findDOMNode(_this.refs.replace); }; _this.regexInput = function () { return ReactDOM.findDOMNode(_this.refs.regex.refs.input); }; _this.caseInsensitiveInput = function () { return ReactDOM.findDOMNode(_this.refs.caseInsensitive.refs.input); }; _this.fullWordInput = function () { return ReactDOM.findDOMNode(_this.refs.fullWord.refs.input); }; _this.replaceWith = function () { return _this.replaceInput().value; }; _this.toggleFilePathExpansion = function (filePath) { _this.state.collapsedState[filePath] = !_this.state.collapsedState[filePath]; _this.setState({ collapsedState: _this.state.collapsedState, selected: { filePath: filePath, line: -1 } }); }; /** * Input Change handlers */ _this.fullWordKeyDownHandler = function (e) { var _a = ui.getKeyStates(e), tab = _a.tab, shift = _a.shift, enter = _a.enter; if (tab && !shift) { _this.findInput().focus(); e.preventDefault(); return; } }; _this.findKeyDownHandler = function (e) { var _a = ui.getKeyStates(e), tab = _a.tab, shift = _a.shift, enter = _a.enter, mod = _a.mod; if (shift && tab) { _this.fullWordInput() && _this.fullWordInput().focus(); e.preventDefault(); return; } /** * Handling commit */ if (!_this.state.findQuery) { return; } if (enter) { _this.startSearch(); } }; _this.findChanged = function () { var val = _this.findInput().value.trim(); _this.setState({ findQuery: val }); }; _this.handleRegexChange = function (e) { var val = e.target.checked; _this.setState({ isRegex: val }); _this.startSearch(); }; _this.handleCaseSensitiveChange = function (e) { var val = e.target.checked; _this.setState({ isCaseSensitive: val }); _this.startSearch(); }; _this.handleFullWordChange = function (e) { var val = e.target.checked; _this.setState({ isFullWord: val }); _this.startSearch(); }; /** * Sends the search query * debounced as state needs to be set before this execs */ _this.startSearch = utils.debounce(function () { var config = { query: _this.state.findQuery, isRegex: _this.state.isRegex, isFullWord: _this.state.isFullWord, isCaseSensitive: _this.state.isCaseSensitive, globs: [] }; socketClient_1.server.startFarming(config); _this.setState({ // Clear previous search collapsedState: {}, selected: {}, // Set new results preemptively results: [], config: config, queryRegex: utils.findOptionsToQueryRegex({ query: config.query, isRegex: config.isRegex, isFullWord: config.isFullWord, isCaseSensitive: config.isCaseSensitive, }), completed: false, farmResultByFilePath: {}, }); }, 100); _this.cancelAnyRunningSearch = function () { socketClient_1.server.stopFarmingIfRunning({}); }; _this.openSearchResult = function (filePath, line) { commands.doOpenOrFocusFile.emit({ filePath: filePath, position: { line: line - 1, ch: 0 } }); _this.setSelected(filePath, line); }; _this.setSelected = function (filePath, line) { _this.setState({ selected: { filePath: filePath, line: line } }); _this.focusFilePath(filePath, line); }; _this.focusFilePath = function (filePath, line) { _this.resultRef(filePath).focus(filePath, line); }; /** * TAB implementation */ _this.resize = function () { // This layout doesn't need it }; /** Allows us to focus on input on certain keystrokes instead of search results */ _this.focusOnInput = function () { return setTimeout(function () { return _this.findInput().focus(); }, 500); }; _this.focus = function () { _this.refs.results.focus(); }; _this.save = function () { }; _this.close = function () { }; _this.gotoPosition = function (position) { }; _this.search = { doSearch: function (options) { // not needed }, hideSearch: function () { // not needed }, findNext: function (options) { }, findPrevious: function (options) { }, replaceNext: function (_a) { var newText = _a.newText; }, replacePrevious: function (_a) { var newText = _a.newText; }, replaceAll: function (_a) { var newText = _a.newText; } }; var _a = utils.getFilePathAndProtocolFromUrl(props.url), protocol = _a.protocol, filePath = _a.filePath; _this.filePath = filePath; _this.state = { completed: true, farmResultByFilePath: {}, collapsedState: {}, selected: {}, }; return _this; } FindAndReplaceView.prototype.componentDidMount = function () { var _this = this; /** * Keyboard: Focus */ this.focusOnInput(); // On initial mount as well :) this.disposible.add(commands.findAndReplace.on(this.focusOnInput)); this.disposible.add(commands.findAndReplaceMulti.on(this.focusOnInput)); /** * Keyboard: Stop */ this.disposible.add(commands.esc.on(function () { // Disabled as esc is used to focus search results as well // this.cancelAnyRunningSearch(); })); /** * Initial load && keeping it updated */ socketClient_1.server.farmResults({}).then(function (res) { _this.parseResults(res); }); this.disposible.add(socketClient_1.cast.farmResultsUpdated.on(function (res) { _this.parseResults(res); })); /** * Handle the keyboard in the search results */ var treeRoot = this.refs.results; var handlers = new Mousetrap(treeRoot); var selectFirst = function () { if (_this.state.results && _this.state.results.length) { _this.setSelected(_this.state.results[0].filePath, -1); } }; handlers.bind('up', function () { // initial state if (!_this.state.results || !_this.state.results.length) return false; if (!_this.state.selected.filePath) { selectFirst(); return false; } /** If we have an actual line go to previous or filePath */ if (_this.state.selected.line !== -1) { var relevantResults = _this.state.farmResultByFilePath[_this.state.selected.filePath]; var indexInResults = relevantResults .map(function (x) { return x.line; }) .indexOf(_this.state.selected.line); if (indexInResults === 0) { _this.setSelected(_this.state.selected.filePath, -1); } else { _this.setSelected(_this.state.selected.filePath, relevantResults[indexInResults - 1].line); } } else { var filePaths = Object.keys(_this.state.farmResultByFilePath); var filePathIndex = filePaths.indexOf(_this.state.selected.filePath); if (filePathIndex === 0) return false; var previousFilePath = filePaths[filePathIndex - 1]; if (_this.state.collapsedState[previousFilePath]) { _this.setSelected(previousFilePath, -1); } else { var results = _this.state.farmResultByFilePath[previousFilePath]; _this.setSelected(previousFilePath, results[results.length - 1].line); } } return false; }); handlers.bind('down', function () { if (!_this.state.results || !_this.state.results.length) return false; if (!_this.state.selected.filePath) { selectFirst(); return false; } /** If we are on a filePath (collaped) go to next filePath if any (expanded) go to first child */ if (_this.state.selected.line === -1) { if (_this.state.collapsedState[_this.state.selected.filePath]) { var filePaths = Object.keys(_this.state.farmResultByFilePath); var filePathIndex = filePaths.indexOf(_this.state.selected.filePath); if (filePathIndex === filePaths.length - 1) return false; var nextFilePath = filePaths[filePathIndex + 1]; _this.setSelected(nextFilePath, -1); } else { var results = _this.state.farmResultByFilePath[_this.state.selected.filePath]; _this.setSelected(results[0].filePath, results[0].line); } } else { var results = _this.state.farmResultByFilePath[_this.state.selected.filePath]; var indexIntoResults = results.map(function (x) { return x.line; }).indexOf(_this.state.selected.line); if (indexIntoResults === results.length - 1) { // Goto next filePath if any var filePaths = Object.keys(_this.state.farmResultByFilePath); var filePathIndex = filePaths.indexOf(_this.state.selected.filePath); if (filePathIndex === filePaths.length - 1) return false; var nextFilePath = filePaths[filePathIndex + 1]; _this.setSelected(nextFilePath, -1); } else { var nextResult = results[indexIntoResults + 1]; _this.setSelected(nextResult.filePath, nextResult.line); } } return false; }); handlers.bind('left', function () { /** Just select and collapse the folder irrespective of our current state */ if (!_this.state.results || !_this.state.results.length) return false; if (!_this.state.selected.filePath) return false; _this.state.collapsedState[_this.state.selected.filePath] = true; _this.setState({ collapsedState: _this.state.collapsedState }); _this.setSelected(_this.state.selected.filePath, -1); return false; }); handlers.bind('right', function () { /** Expand only if a filePath root is currently selected */ if (!_this.state.results || !_this.state.results.length) return false; _this.state.collapsedState[_this.state.selected.filePath] = false; _this.setState({ collapsedState: _this.state.collapsedState }); return false; }); handlers.bind('enter', function () { /** Enter always takes you into the filePath */ if (!_this.state.results || !_this.state.results.length) return false; if (!_this.state.selected.filePath) { selectFirst(); } _this.openSearchResult(_this.state.selected.filePath, _this.state.selected.line); return false; }); // Listen to tab events var api = this.props.api; this.disposible.add(api.resize.on(this.resize)); this.disposible.add(api.focus.on(this.focus)); this.disposible.add(api.save.on(this.save)); this.disposible.add(api.close.on(this.close)); this.disposible.add(api.gotoPosition.on(this.gotoPosition)); // Listen to search tab events this.disposible.add(api.search.doSearch.on(this.search.doSearch)); this.disposible.add(api.search.hideSearch.on(this.search.hideSearch)); this.disposible.add(api.search.findNext.on(this.search.findNext)); this.disposible.add(api.search.findPrevious.on(this.search.findPrevious)); this.disposible.add(api.search.replaceNext.on(this.search.replaceNext)); this.disposible.add(api.search.replacePrevious.on(this.search.replacePrevious)); this.disposible.add(api.search.replaceAll.on(this.search.replaceAll)); }; FindAndReplaceView.prototype.render = function () { var hasSearch = !!this.state.config; var rendered = (React.createElement("div", { className: ResultsStyles.noFocusClassName, style: csx.extend(csx.vertical, csx.flex, styles.someChildWillScroll) }, React.createElement("div", { ref: "results", tabIndex: 0, className: ResultsStyles.rootClassName }, hasSearch ? this.renderSearchResults() : React.createElement("div", { style: ResultsStyles.header }, "No Search")), React.createElement("div", { style: csx.extend(csx.content, styles.padded1) }, this.renderSearchControls()))); return rendered; }; FindAndReplaceView.prototype.renderSearchControls = function () { return (React.createElement("div", { style: csx.vertical }, React.createElement("div", { style: csx.extend(csx.horizontal, csx.center) }, React.createElement("div", { style: csx.extend(csx.flex, csx.vertical) }, React.createElement("div", { style: csx.extend(csx.horizontal, csx.center, styles.padded1) }, React.createElement("input", { tabIndex: 1, ref: "find", placeholder: "Find", className: inputBlackClassName, style: csx.extend(findAndReplace_1.inputCodeStyle, csx.flex), onKeyDown: this.findKeyDownHandler, onChange: this.findChanged, defaultValue: '' }))), React.createElement("div", { style: csx.content }, React.createElement("div", { style: csx.extend(csx.horizontal, csx.aroundJustified, styles.padded1) }, React.createElement("label", { style: csx.extend(csx.horizontal, csx.center) }, React.createElement(ui.Toggle, { tabIndex: 3, ref: "regex", onChange: this.handleRegexChange }), React.createElement("span", { style: findAndReplace_1.searchOptionsLabelStyle }, ".*")), React.createElement("label", { style: csx.extend(csx.horizontal, csx.center) }, React.createElement(ui.Toggle, { tabIndex: 4, ref: "caseInsensitive", onChange: this.handleCaseSensitiveChange }), React.createElement("span", { style: findAndReplace_1.searchOptionsLabelStyle }, "Aa")), React.createElement("label", { style: csx.extend(csx.horizontal, csx.center) }, React.createElement(ui.Toggle, { tabIndex: 5, ref: "fullWord", onKeyDown: this.fullWordKeyDownHandler, onChange: this.handleFullWordChange }), React.createElement("span", { style: findAndReplace_1.searchOptionsLabelStyle }, React.createElement(icon_1.Icon, { name: "text-width" })))))), React.createElement("div", { style: styles.Tip.root }, React.createElement("div", null, "Controls:", ' ', React.createElement("span", { style: styles.Tip.keyboardShortCutStyle }, "Esc"), " to focus on results", ' ', React.createElement("span", { style: styles.Tip.keyboardShortCutStyle }, "Enter"), " to start/restart search", ' ', React.createElement("span", { style: styles.Tip.keyboardShortCutStyle }, "Toggle \uD83D\uDD18 switches"), " start/restart search"), React.createElement("div", null, "Results:", ' ', React.createElement("span", { style: styles.Tip.keyboardShortCutStyle }, "Up/Down"), " to go through results", ' ', React.createElement("span", { style: styles.Tip.keyboardShortCutStyle }, "Enter"), " to open a search result")))); }; FindAndReplaceView.prototype.renderSearchResults = function () { var _this = this; var filePaths = Object.keys(this.state.farmResultByFilePath); var queryRegex = this.state.queryRegex; var queryRegexStr = (this.state.queryRegex && this.state.queryRegex.toString()) || ''; queryRegexStr = queryRegexStr && " (Query : " + queryRegexStr + ") "; return (React.createElement("div", { style: csx.extend(csx.flex, styles.errorsPanel.main, { userSelect: 'none' }) }, React.createElement("div", { style: csx.extend(ResultsStyles.header, csx.horizontal) }, "Total Results (", this.state.results.length, ")", (this.state.results.length >= types.maxCountFindAndReplaceMultiResults) && React.createElement("span", { className: "hint--info hint--bottom", "data-hint": "(search limited to " + types.maxCountFindAndReplaceMultiResults + ")" }, "(+)"), !this.state.completed && React.createElement("span", null, React.createElement(gls.SmallHorizontalSpace, null), React.createElement(buttons.ButtonBlack, { key: "button", onClick: this.cancelAnyRunningSearch, text: "Cancel" })), React.createElement("span", { style: csx.flex }), queryRegexStr), filePaths.map(function (filePath, i) { var results = _this.state.farmResultByFilePath[filePath]; var selectedRoot = filePath === _this.state.selected.filePath && _this.state.selected.line == -1; var selectedResultLine = filePath === _this.state.selected.filePath ? _this.state.selected.line : -2 /* -2 means not selected */; return (React.createElement(FileResults.FileResults, { key: i, ref: filePath, filePath: filePath, results: results, queryRegex: _this.state.queryRegex, expanded: !_this.state.collapsedState[filePath], onClickFilePath: _this.toggleFilePathExpansion, openSearchResult: _this.openSearchResult, selectedRoot: selectedRoot, selectedResultLine: selectedResultLine })); }))); }; /** * Scrolling through results */ FindAndReplaceView.prototype.resultRef = function (filePath) { var ref = filePath; return this.refs[ref]; }; /** Parses results as they come and puts them into the state */ FindAndReplaceView.prototype.parseResults = function (response) { // Convert as needed // console.log(response); // DEBUG var results = response.results; var loaded = utils.createMapByKey(results, function (result) { return result.filePath; }); var queryRegex = response.config ? utils.findOptionsToQueryRegex({ query: response.config.query, isRegex: response.config.isRegex, isFullWord: response.config.isFullWord, isCaseSensitive: response.config.isCaseSensitive, }) : null; // Finally rerender this.setState({ results: results, config: response.config, queryRegex: queryRegex, completed: response.completed, farmResultByFilePath: loaded }); }; return FindAndReplaceView; }(ui.BaseComponent)); exports.FindAndReplaceView = FindAndReplaceView; var FileResults; (function (FileResults_1) { var FileResults = /** @class */ (function (_super) { __extends(FileResults, _super); function FileResults() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.renderResultsForFilePath = function (results) { return results.map(function (result, i) { var selectedStyle = _this.props.selectedResultLine === result.line ? ResultsStyles.selected : {}; return (React.createElement("div", { key: i, ref: result.filePath + ':' + result.line, tabIndex: 0, className: ResultsStyles.noFocusClassName, style: csx.extend(styles.padded1, { cursor: 'pointer', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'pre', }, selectedStyle), onClick: function (e) { e.stopPropagation(); _this.props.openSearchResult(result.filePath, result.line); } }, utils.padLeft((result.line).toString(), 6), " : ", React.createElement("span", { style: ResultsStyles.preview }, _this.renderMatched(result.preview, _this.props.queryRegex)))); }); }; return _this; } FileResults.prototype.render = function () { var _this = this; var selectedStyle = this.props.selectedRoot ? ResultsStyles.selected : {}; return (React.createElement("div", { onClick: function () { return _this.props.onClickFilePath(_this.props.filePath); } }, React.createElement("div", { ref: this.props.filePath + ':' + -1, tabIndex: 0, className: ResultsStyles.noFocusClassName, style: csx.extend(selectedStyle, styles.errorsPanel.filePath, { margin: '8px 0px', padding: '3px' }) }, !this.props.expanded ? "+" : "-", " ", this.props.filePath, " (", this.props.results.length, ")"), !this.props.expanded ? React.createElement("noscript", null) : this.renderResultsForFilePath(this.props.results))); }; FileResults.prototype.focus = function (filePath, line) { var dom = this.refs[this.props.filePath + ':' + line]; dom.scrollIntoViewIfNeeded(false); }; FileResults.prototype.renderMatched = function (preview, queryRegex) { var matched = this.getMatchedSegments(preview, queryRegex); var matchedStyle = { fontWeight: 'bold', color: '#66d9ef' }; return matched.map(function (item, i) { return React.createElement("span", { key: i, style: item.matched ? matchedStyle : {} }, item.str); }); }; FileResults.prototype.getMatchedSegments = function (preview, queryRegex) { var result = []; var match; var previewCollectedIndex = 0; var collectUnmatched = function (matchStart, matchLength) { if (previewCollectedIndex < matchStart) { result.push({ str: preview.substring(previewCollectedIndex, matchStart), matched: false }); previewCollectedIndex = (matchStart + matchLength); } }; while (match = queryRegex.exec(preview)) { var matchStart = match.index; var matchLength = match[0].length; // let nextMatchStart = queryRegex.lastIndex; // Since we don't need it // If we have an unmatched string portion that is still not collected // Collect it :) collectUnmatched(matchStart, matchLength); result.push({ str: preview.substr(matchStart, matchLength), matched: true }); } // If we still have some string portion uncollected, collect it if (previewCollectedIndex !== preview.length) { collectUnmatched(preview.length, 0); } return result; }; return FileResults; }(React.PureComponent)); FileResults_1.FileResults = FileResults; })(FileResults || (FileResults = {}));