alm
Version:
The best IDE for TypeScript
329 lines (328 loc) • 14.8 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 socketClient_1 = require("../../../socket/socketClient");
var docCache = require("../model/docCache");
var cursorHistory = require("../../cursorHistory");
var search = require("../search/monacoSearch");
var semanticView = require("../addons/semanticView");
var monacoUtils = require("../monacoUtils");
var gitStatus = require("../addons/gitStatus");
var liveAnalysis = require("../addons/liveAnalysis");
var quickFix = require("../addons/quickFix");
var linter = require("../addons/linter");
var docblockr = require("../addons/dockblockr");
var doctor = require("../addons/doctor");
var testedMonaco = require("../addons/testedMonaco");
var utils = require("../../../common/utils");
var autoCloseTag = require("../addons/autoCloseTag");
// Any other style modifications
require('./codeEditor.css');
exports.monokai = [
{ token: '', foreground: 'f8f8f2' },
{ token: 'comment', foreground: '75715e' },
{ token: 'string', foreground: 'e6db74' },
{ token: 'support.property-value.string.value.json', foreground: 'e6db74' },
{ token: 'constant.numeric', foreground: 'ae81ff' },
{ token: 'constant.language', foreground: 'ae81ff' },
{ token: 'constant.character', foreground: 'ae81ff' },
{ token: 'constant.other', foreground: 'ae81ff' },
{ token: 'keyword', foreground: 'f92672' },
{ token: 'support.property-value.keyword.json', foreground: 'f92672' },
{ token: 'storage', foreground: 'aae354' },
{ token: 'storage.type', foreground: '66d9ef', fontStyle: 'italic' },
{ token: 'entity.name.class', foreground: 'a6e22e' },
{ token: 'entity.other', foreground: 'a6e22e' },
{ token: 'entity.name.function', foreground: 'a6e22e' },
{ token: 'entity.name.tag', foreground: 'f92672' },
{ token: 'entity.other.attribute-name', foreground: 'a6e22e' },
{ token: 'variable', foreground: 'f8f8f2' },
{ token: 'variable.parameter', foreground: 'fd971f', fontStyle: 'italic' },
{ token: 'support.function', foreground: '66d9ef' },
{ token: 'support.constant', foreground: '66d9ef' },
{ token: 'support.type', foreground: '66d9ef' },
{ token: 'support.class', foreground: '66d9ef', fontStyle: 'italic' },
/** We use qualifier for `const`, `var`, `private` etc. */
{ token: 'qualifier', foreground: '00d0ff' },
/* `def` does not exist. We like to use it for variable definitions */
{ token: 'def', foreground: 'fd971f' },
/** variable-2 doesn't exist. We use it for identifiers in type positions */
{ token: 'variable-2', foreground: '9effff' },
];
monaco.editor.defineTheme('monokai', {
base: 'vs-dark',
inherit: true,
rules: exports.monokai
});
var CodeEditor = /** @class */ (function (_super) {
__extends(CodeEditor, _super);
function CodeEditor(props) {
var _this = _super.call(this, props) || this;
/** Ready after the doc is loaded */
_this.ready = false;
_this.afterReadyQueue = [];
/** If already ready it execs ... otherwise waits */
_this.afterReady = function (cb) {
if (_this.ready)
cb();
else {
_this.afterReadyQueue.push(cb);
}
};
_this.firstFocus = true;
_this.focus = function () {
if (!_this.ready && _this.firstFocus) {
_this.firstFocus = false;
_this.afterReadyQueue.push(function () {
_this.resize();
_this.focus();
});
}
else if (_this.editor) {
_this.editor.focus();
}
};
_this.resize = function () {
if (_this.editor) {
var before_1 = _this.editor.getDomNode().scrollHeight;
_this.refresh();
var after_1 = _this.editor.getDomNode().scrollHeight;
var worthRestoringScrollPosition = (after_1 !== before_1) && (after_1 != 0);
/** Restore last scroll position on refresh after a blur */
if (_this.lastScrollPosition != undefined && worthRestoringScrollPosition) {
setTimeout(function () {
_this.editor.setScrollTop(_this.lastScrollPosition);
// console.log(this.props.filePath, before, after, worthRestoringScrollPosition, this.lastScrollPosition); // DEBUG
_this.lastScrollPosition = undefined;
});
}
}
};
_this.lastScrollPosition = undefined;
_this.gotoPosition = function (position) {
_this.afterReady(function () {
_this.lastScrollPosition = undefined; // Don't even think about restoring scroll position
/** e.g. if the tab is already active we don't call `focus` from `gotoPosition` ... however it might not have focus */
if (!_this.editor.isFocused()) {
_this.editor.focus();
}
/** SetTimeout because if editor comes out of hidden state we goto position too fast and then the scroll position is off */
setTimeout(function () { return monacoUtils.gotoPosition({ editor: _this.editor, position: position }); });
});
};
_this.refresh = function () {
_this.editor.layout();
};
_this.focusChanged = function (focused) {
_this.setState({
isFocused: focused
});
_this.props.onFocusChange && _this.props.onFocusChange(focused);
};
_this.search = function (options) {
search.commands.search(_this.editor, options);
};
_this.hideSearch = function () {
search.commands.hideSearch(_this.editor);
};
_this.findNext = function (options) {
search.commands.findNext(_this.editor, options);
};
_this.findPrevious = function (options) {
search.commands.findPrevious(_this.editor, options);
};
_this.replaceNext = function (newText) {
search.commands.replaceNext(_this.editor, newText);
};
_this.replacePrevious = function (newText) {
search.commands.replacePrevious(_this.editor, newText);
};
_this.replaceAll = function (newText) {
search.commands.replaceAll(_this.editor, newText);
};
_this.handleCursorActivity = function () {
var cursor = _this.editor.getSelection();
cursorHistory.addEntry({
line: cursor.startLineNumber - 1,
ch: cursor.startColumn - 1,
});
};
_this.state = {
isFocused: false,
loading: true,
};
return _this;
}
CodeEditor.prototype.componentDidMount = function () {
var _this = this;
var mountNode = this.refs.codeEditor;
var filePath = this.props.filePath;
this.editor = monaco.editor.create(mountNode, {
value: '...',
theme: 'monokai',
folding: true,
autoClosingBrackets: true,
wrappingColumn: 0,
readOnly: false,
scrollBeyondLastLine: false,
formatOnType: true,
contextmenu: false,
/** Move snippet suggestions to the bottom */
snippetSuggestions: 'bottom',
/** Since everything else in our UI is Square */
roundedSelection: false,
/** For git status, find results, errors */
overviewRulerLanes: 3,
/** Don't reserve too much space for line numbers */
lineNumbersMinChars: 4,
/** We need the glyph margin to show live analysis stuff */
glyphMargin: true,
/**
* Change the default font.
* The default is `consolas` , `courier new`.
* This means that if user does not have consolas they get *aweful* courier new.
* Don't want that.
* Also the default change by OS.
* I prefer consistency so going with custom font everywhere
*/
fontFamily: 'consolas, menlo, monospace',
/** Also make the font a bit bigger */
fontSize: 16,
}, []);
this.editor.filePath = filePath;
// Utility to load editor options
var loadEditorOptions = function (editorOptions) {
// Feels consistent with https://code.visualstudio.com/Docs/customization/userandworkspace
_this.editor.getModel().updateOptions({
insertSpaces: editorOptions.convertTabsToSpaces,
tabSize: editorOptions.tabSize
});
};
this.disposible.add(socketClient_1.cast.editorOptionsChanged.on(function (res) {
if (res.filePath === _this.props.filePath) {
loadEditorOptions(res.editorOptions);
}
}));
// load up the doc
docCache.getLinkedDoc(this.props.filePath, this.editor).then(function (_a) {
var doc = _a.doc, editorOptions = _a.editorOptions;
// Load editor options
loadEditorOptions(editorOptions);
if (_this.props.preview) {
_this.gotoPreview(_this.props.preview);
}
// linter
// NOTE: done here because it depends on model :)
_this.disposible.add(linter.setup(_this.editor));
/** Tested */
if (!_this.props.readOnly) {
_this.disposible.add(testedMonaco.setup(_this.editor));
}
/** Auto close tag */
var ext = utils.getExt(_this.props.filePath);
if (ext === 'tsx') {
_this.disposible.add(autoCloseTag.setup(_this.editor));
}
// Mark as ready and do anything that was waiting for ready to occur 🌹
_this.afterReadyQueue.forEach(function (cb) { return cb(); });
_this.ready = true;
_this.setState({ loading: false });
});
this.disposible.add(this.editor.onDidFocusEditor(this.focusChanged.bind(this, true)));
this.disposible.add(this.editor.onDidBlurEditor(this.focusChanged.bind(this, false)));
// cursor history
if (!this.props.readOnly) {
this.disposible.add(this.editor.onDidChangeCursorPosition(this.handleCursorActivity));
}
// live analysis
this.disposible.add(liveAnalysis.setup(this.editor));
// quick fix
if (!this.props.readOnly) {
this.disposible.add(quickFix.setup(this.editor));
}
// Git status
this.disposible.add(gitStatus.setup(this.editor));
// Docblockr
this.disposible.add(docblockr.setup(this.editor));
};
CodeEditor.prototype.componentWillUnmount = function () {
_super.prototype.componentWillUnmount.call(this);
docCache.removeLinkedDoc(this.props.filePath, this.editor);
this.editor.dispose();
this.editor = null;
};
CodeEditor.prototype.willBlur = function () {
this.lastScrollPosition = this.editor.getScrollTop();
// console.log('Storing:', this.props.filePath, this.lastScrollPosition); // DEBUG
};
CodeEditor.prototype.getValue = function () {
this.editor.getValue();
};
/**
* used to seed the initial search if coming out of hidden
*/
CodeEditor.prototype.getSelectionSearchString = function () {
var selection = this.editor.getSelection();
if (selection.startLineNumber === selection.endLineNumber) {
if (selection.isEmpty()) {
var wordAtPosition = this.editor.getModel().getWordAtPosition(selection.getStartPosition());
if (wordAtPosition) {
return wordAtPosition.word;
}
}
else {
return this.editor.getModel().getValueInRange(selection);
}
}
return undefined;
};
CodeEditor.prototype.render = function () {
var className = 'ReactCodeEditor';
if (this.state.isFocused) {
className += ' ReactCodeEditor--focused';
}
var loadingStyle = {
position: 'absolute', top: '45%', left: '45%', zIndex: 1,
color: '#999',
border: '5px solid #999',
borderRadius: '5px',
fontSize: '2rem',
padding: '5px',
transition: '.2s opacity',
opacity: this.state.loading ? 1 : 0,
pointerEvents: 'none',
};
return (React.createElement("div", { className: className, style: csx.extend(csx.horizontal, csx.flex, { position: 'relative', maxWidth: '100%' }) },
!this.props.readOnly && React.createElement(doctor.Doctor, { cm: this.editor, filePath: this.props.filePath }),
React.createElement("div", { style: loadingStyle }, "LOADING"),
React.createElement("div", { ref: "codeEditor", style: { display: 'flex', flexDirection: 'column', flex: 1, overflow: 'hidden' } }),
!this.props.readOnly && React.createElement(semanticView.SemanticView, { editor: this.editor, filePath: this.props.filePath })));
};
CodeEditor.prototype.componentWillReceiveProps = function (nextProps) {
// If next props are getting a preview then old props had them too (based on how we use preview)
if (nextProps.preview && nextProps.preview.start !== this.props.preview.start) {
this.gotoPreview(nextProps.preview);
}
};
CodeEditor.prototype.gotoPreview = function (preview) {
// Re-layout as for preview style editors monaco seems to render faster than CSS 🌹
this.editor.layout();
var pos = this.editor.getModel().getPositionAt(preview.start);
this.editor.revealLineInCenterIfOutsideViewport(pos.lineNumber);
this.editor.setPosition(pos);
};
return CodeEditor;
}(ui.BaseComponent));
exports.CodeEditor = CodeEditor;