alm
Version:
The best IDE for TypeScript
237 lines (236 loc) • 10.7 kB
JavaScript
;
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;