UNPKG

alm

Version:

The best IDE for TypeScript

237 lines (236 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var socketClient_1 = require("../../../socket/socketClient"); var editBatcher = require("./editBatcher"); var utils = require("../../../common/utils"); var classifierCache = require("./classifierCache"); var state = require("../../state/state"); var monacoUtils = require("../../monaco/monacoUtils"); var events = require("../../../common/events"); /** * Setup any additional languages */ var monacoTypeScript_1 = require("../../monaco/languages/typescript/monacoTypeScript"); monacoTypeScript_1.setupMonacoTypecript(); var monacoJson_1 = require("../../monaco/languages/json/monacoJson"); monacoJson_1.setupMonacoJson(); /** * Ext lookup */ var extLookup = {}; monaco.languages.getLanguages().forEach(function (language) { // console.log(language); // DEBUG language.extensions.forEach(function (ext) { // ext is like `.foo`. We really want `foo` ext = ext.substr(1); extLookup[ext] = language; }); }); /** * Gets the mode for a give filePath (based on its ext) */ var getLanguage = function (filePath) { var mode = extLookup[utils.getExt(filePath)]; return mode ? mode.id : 'plaintext'; }; // to track the source of changes, local vs. network var localSourceId = utils.createId(); function getLinkedDoc(filePath, editor) { return getOrCreateDoc(filePath) .then(function (_a) { var doc = _a.doc, isJsOrTsFile = _a.isJsOrTsFile, editorOptions = _a.editorOptions; // Everytime a typescript file is opened we ask for its output status (server pull) // On editing this happens automatically (server push) socketClient_1.server.getJSOutputStatus({ filePath: filePath }).then(function (res) { if (res.inActiveProject) { state.fileOuputStatusUpdated(res.outputStatus); } }); /** Wire up the doc */ editor.setModel(doc); /** Add to list of editors */ doc._editors.push(editor); return { doc: doc, editorOptions: editorOptions }; }); } exports.getLinkedDoc = getLinkedDoc; function removeLinkedDoc(filePath, editor) { editor.getModel()._editors = editor.getModel()._editors.filter(function (e) { return e != editor; }); // if this was the last editor using this model then we remove it from the cache as well // otherwise we might get a file even if its deleted from the server if (!editor.getModel()._editors.length) { docByFilePathPromised[filePath].then(function (x) { x.disposable.dispose(); delete docByFilePathPromised[filePath]; }); } } exports.removeLinkedDoc = removeLinkedDoc; var docByFilePathPromised = Object.create(null); function getOrCreateDoc(filePath) { if (docByFilePathPromised[filePath]) { return docByFilePathPromised[filePath]; } else { return docByFilePathPromised[filePath] = socketClient_1.server.openFile({ filePath: filePath }).then(function (res) { var disposable = new events.CompositeDisposible(); var ext = utils.getExt(filePath); var isJsOrTsFile = utils.isJsOrTs(filePath); var language = getLanguage(filePath); // console.log(res.editorOptions); // DEBUG // console.log(mode,supportedModesMap[ext]); // Debug mode // Add to classifier cache if (isJsOrTsFile) { classifierCache.addFile(filePath, res.contents); disposable.add({ dispose: function () { return classifierCache.removeFile(filePath); } }); } // create the doc var doc = monaco.editor.createModel(res.contents, language); doc.setEOL(monaco.editor.EndOfLineSequence.LF); // The true eol is only with the file model at the backend. The frontend doesn't care 🌹 doc.filePath = filePath; doc._editors = []; doc._editorOptions = res.editorOptions; /** Susbcribe to editor options changing */ disposable.add(socketClient_1.cast.editorOptionsChanged.on(function (res) { if (res.filePath === filePath) { doc._editorOptions = res.editorOptions; } })); /** * We ignore edit notifications from monaco if *we* did the edit e.g. * - if the server sent the edit and we applied it. */ var countOfEditNotificationsToIgnore = 0; /** This is used for monaco edit operation version counting purposes */ var editorOperationCounter = 0; // setup to push doc changes to server disposable.add(doc.onDidChangeContent(function (evt) { // Keep the ouput status cache informed state.ifJSStatusWasCurrentThenMoveToOutOfDate({ inputFilePath: filePath }); // if this edit is happening // because *we edited it due to a server request* // we should exit if (countOfEditNotificationsToIgnore) { countOfEditNotificationsToIgnore--; return; } var codeEdit = { from: { line: evt.range.startLineNumber - 1, ch: evt.range.startColumn - 1 }, to: { line: evt.range.endLineNumber - 1, ch: evt.range.endColumn - 1 }, newText: evt.text, sourceId: localSourceId }; // Send the edit editBatcher.addToQueue(filePath, codeEdit); // Keep the classifier in sync if (isJsOrTsFile) { classifierCache.editFile(filePath, codeEdit); } })); // setup to get doc changes from server disposable.add(socketClient_1.cast.didEdits.on(function (res) { // console.log('got server edit', res.edit.sourceId,'our', sourceId) var codeEdits = res.edits; codeEdits.forEach(function (codeEdit) { // Easy exit for local edits getting echoed back if (res.filePath == filePath && codeEdit.sourceId !== localSourceId) { // Keep the classifier in sync if (isJsOrTsFile) { classifierCache.editFile(filePath, codeEdit); } // make the edits var editOperation = { identifier: { major: editorOperationCounter++, minor: 0 }, text: codeEdit.newText, range: new monaco.Range(codeEdit.from.line + 1, codeEdit.from.ch + 1, codeEdit.to.line + 1, codeEdit.to.ch + 1), forceMoveMarkers: false, isAutoWhitespaceEdit: false, }; /** Mark as ignoring before applying the edit */ countOfEditNotificationsToIgnore++; doc.pushEditOperations([], [editOperation], null); } }); })); // setup loading saved files changing on disk disposable.add(socketClient_1.cast.savedFileChangedOnDisk.on(function (res) { if (res.filePath == filePath && doc.getValue() !== res.contents) { // preserve cursor doc._editors.forEach(function (e) { var cursors = e.getSelections(); setTimeout(function () { e.setSelections(cursors); }); }); // Keep the classifier in sync if (isJsOrTsFile) { classifierCache.setContents(filePath, res.contents); } // Note that we use *mark as coming from server* so we don't go into doc.change handler later on :) countOfEditNotificationsToIgnore++; // NOTE: we don't do `setValue` as // - that creates a new tokenizer (we would need to use `window.creatingModelFilePath`) // - looses all undo history. // Instead we *replace* all the text that is there 🌹 var totalDocRange = doc.getFullModelRange(); monacoUtils.replaceRange({ range: totalDocRange, model: doc, newText: res.contents }); } })); // Finally return the doc var result = { doc: doc, isJsOrTsFile: isJsOrTsFile, editorOptions: res.editorOptions, disposable: disposable, }; return result; }); } } /** * Don't plan to export as giving others our true docs can have horrible consequences if they mess them up */ function getOrCreateDocs(filePaths) { var promises = filePaths.map(function (fp) { return getOrCreateDoc(fp); }); return Promise.all(promises).then(function (docs) { var res = {}; docs.forEach(function (_a) { var doc = _a.doc; return res[doc.filePath] = doc; }); return res; }); } function applyRefactoringsToTsDocs(refactorings) { var filePaths = Object.keys(refactorings); getOrCreateDocs(filePaths).then(function (docsByFilePath) { filePaths.forEach(function (fp) { var doc = docsByFilePath[fp]; var changes = refactorings[fp]; for (var _i = 0, changes_1 = changes; _i < changes_1.length; _i++) { var change = changes_1[_i]; var from = classifierCache.getLineAndCharacterOfPosition(fp, change.span.start); var to = classifierCache.getLineAndCharacterOfPosition(fp, change.span.start + change.span.length); monacoUtils.replaceRange({ model: doc, range: { startLineNumber: from.line + 1, startColumn: from.ch + 1, endLineNumber: to.line + 1, endColumn: to.ch + 1 }, newText: change.newText }); } }); }); } exports.applyRefactoringsToTsDocs = applyRefactoringsToTsDocs;