alm
Version:
The best IDE for TypeScript
575 lines (574 loc) • 28 kB
JavaScript
"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 = {}));