UNPKG

alm

Version:

The best IDE for TypeScript

176 lines (175 loc) 7.34 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var utils = require("../../common/utils"); var os = require("os"); var fsu = require("../utils/fsu"); var chokidar = require("chokidar"); var events_1 = require("../../common/events"); var editorOptions_1 = require("./editorOptions"); /** * Loads a file from disk * watches it on fs and then if it changes sends the new content to the client * TODO: File is *always* saved to cache for recovery * * Has a model like code mirror ... just use lines at all places ... till we actually write to disk */ var FileModel = /** @class */ (function () { function FileModel(config) { var _this = this; this.config = config; this.text = []; /** last known state of the file system text */ this.savedText = []; /** * New contents is only sent if the file has no pending changes. Otherwise it is silently ignored */ this.onSavedFileChangedOnDisk = new events_1.TypedEvent(); /** * Always emit */ this.didEdits = new events_1.TypedEvent(); /** * Always emit */ this.didStatusChange = new events_1.TypedEvent(); /** * Editor config changed * Only after initial load */ this.editorOptionsChanged = new events_1.TypedEvent(); this._justWroteFileToDisk = false; this.fileListener = function (eventName, path) { var contents = fsu.existsSync(_this.config.filePath) ? fsu.readFile(_this.config.filePath) : ''; var text = _this.splitlines(contents); // If we wrote the file no need to do any further checks // Otherwise sometime we end up editing the file and change event fires too late and we think its new content if (_this._justWroteFileToDisk) { _this._justWroteFileToDisk = false; return; } // If new text same as current text nothing to do. if (arraysEqualWithWhitespace(text, _this.savedText)) { return; } if (_this.saved()) { _this.text = text; _this.savedText = _this.text.slice(); _this.onSavedFileChangedOnDisk.emit({ contents: _this.getContents() }); } }; /** The chokidar watcher */ this.fsWatcher = null; var contents = fsu.readFile(config.filePath); this.newLine = this.getExpectedNewline(contents); this.text = this.splitlines(contents); this.savedText = this.text.slice(); this.watchFile(); this.editorOptions = editorOptions_1.getEditorOptions(config.filePath); } FileModel.prototype.getContents = function () { return this.text.join('\n'); }; /** Returns true if the file is same as what was on disk */ FileModel.prototype.edits = function (codeEdits) { var _this = this; /** PREF: This batching can probably be made more efficient */ codeEdits.forEach(function (edit) { _this.edit(edit); }); var saved = this.saved(); this.didEdits.emit({ codeEdits: codeEdits }); this.didStatusChange.emit({ saved: saved, eol: this.newLine }); return { saved: saved }; }; FileModel.prototype.edit = function (codeEdit) { var lastLine = this.text.length - 1; var beforeLines = this.text.slice(0, codeEdit.from.line); // there might not be any after lines. This just might be a new line :) var afterLines = codeEdit.to.line === lastLine ? [] : this.text.slice(codeEdit.to.line + 1, this.text.length); var lines = this.text.slice(codeEdit.from.line, codeEdit.to.line + 1); var content = lines.join('\n'); var contentBefore = content.substr(0, codeEdit.from.ch); var contentAfter = lines[lines.length - 1].substr(codeEdit.to.ch); content = contentBefore + codeEdit.newText + contentAfter; lines = content.split('\n'); this.text = beforeLines.concat(lines).concat(afterLines); }; FileModel.prototype.delete = function () { this.unwatchFile(); fsu.deleteFile(this.config.filePath); }; FileModel.prototype.save = function () { // NOTE we can never easily mutate our local `text` otherwise we have to send the changes out and sync them which is going to be nightmare var textToWrite = this.editorOptions.trimTrailingWhitespace ? this.text.map(function (t) { return t.replace(/[ \f\t\v]*$/gm, ''); }) : this.text; var contents = textToWrite.join(this.newLine); if (this.editorOptions.insertFinalNewline && !contents.endsWith(this.newLine)) { contents = contents + this.newLine; } fsu.writeFile(this.config.filePath, contents); this._justWroteFileToDisk = true; this.savedText = this.text.slice(); this.didStatusChange.emit({ saved: true, eol: this.newLine }); }; FileModel.prototype.saved = function () { return utils.arraysEqual(this.text, this.savedText); }; FileModel.prototype.watchFile = function () { this.fsWatcher = chokidar.watch(this.config.filePath, { ignoreInitial: true }); this.fsWatcher.on('change', this.fileListener); }; FileModel.prototype.unwatchFile = function () { this.fsWatcher.close(); this.fsWatcher = null; }; /** Just updates `text` saves */ FileModel.prototype.setContents = function (contents) { this.text = this.splitlines(contents); this.save(); }; /** * Someone else should call this if an editor config file changes * Here we need to re-evalute our options */ FileModel.prototype.recheckEditorOptions = function () { this.editorOptions = editorOptions_1.getEditorOptions(this.config.filePath); this.editorOptionsChanged.emit(this.editorOptions); }; /** Great for error messages etc. Ofcourse `0` based */ FileModel.prototype.getLinePreview = function (line) { return this.text[line]; }; /** * split lines * https://github.com/codemirror/CodeMirror/blob/5738f9b2cff5241ea13e32db3579eb347e56e7a0/lib/codemirror.js#L8594 */ FileModel.prototype.splitlines = function (string) { return string.split(/\r\n?|\n/); }; ; /** https://github.com/sindresorhus/detect-newline/blob/master/index.js */ FileModel.prototype.getExpectedNewline = function (str) { var newlines = (str.match(/(?:\r?\n)/g) || []); var crlf = newlines.filter(function (el) { return el === '\r\n'; }).length; var lf = newlines.length - crlf; // My addition if (lf == 0 && crlf == 0) return os.EOL; return crlf > lf ? '\r\n' : '\n'; }; return FileModel; }()); exports.FileModel = FileModel; /** * shallow equality of sorted string arrays that considers whitespace to be insignificant */ function arraysEqualWithWhitespace(a, b) { if (a === b) return true; if (a == null || b == null) return false; if (a.length !== b.length) return false; for (var i = 0; i < a.length; ++i) { if (a[i].trim() !== b[i].trim()) return false; } return true; }