alm
Version:
The best IDE for TypeScript
176 lines (175 loc) • 7.34 kB
JavaScript
"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;
}