提交 909d4952 编写于 作者: B Benjamin Pasero

Save file even without file changes - so that Nodemon, Gulp, Chokidar and...

Save file even without file changes - so that Nodemon, Gulp, Chokidar and other file watchers restart (fixes #7470)
上级 ec85ff4b
......@@ -108,6 +108,10 @@ export function readlink(path: string): TPromise<string> {
return nfcall<string>(fs.readlink, path);
}
export function utimes(path: string, atime:Date, mtime:Date): Promise {
return nfcall(fs.utimes, path, atime, mtime);
}
function doStatMultiple(paths: string[]): TPromise<{ path: string; stats: fs.Stats; }> {
let path = paths.shift();
return stat(path).then((value) => {
......
......@@ -92,6 +92,12 @@ export interface IFileService {
*/
rename(resource: URI, newName: string): TPromise<IFileStat>;
/**
* Creates a new empty file if the given path does not exist and otherwise
* will set the mtime and atime of the file to the current date.
*/
touchFile(resource: URI): TPromise<IFileStat>;
/**
* Deletes the provided file. The optional useTrash parameter allows to
* move the file to trash.
......
......@@ -1510,7 +1510,7 @@ export abstract class BaseSaveFileAction extends BaseActionWithErrorReporting {
}
// Just save
return this.textFileService.save(source);
return this.textFileService.save(source, { force: true /* force a change to the file to trigger external watchers if any */});
}
return TPromise.as(false);
......
......@@ -301,6 +301,15 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
isDisposed(): boolean;
}
export interface ISaveOptions {
/**
* Save the file on disk even if not dirty. If the file is not dirty, it will be touched
* so that mtime and atime are updated. This helps to trigger external file watchers.
*/
force: boolean;
}
export interface ITextFileService extends IDisposable {
_serviceBrand: any;
onAutoSaveConfigurationChange: Event<IAutoSaveConfiguration>;
......@@ -338,7 +347,7 @@ export interface ITextFileService extends IDisposable {
* @param resource the resource to save
* @return true iff the resource was saved.
*/
save(resource: URI): TPromise<boolean>;
save(resource: URI, options?: ISaveOptions): TPromise<boolean>;
/**
* Saves the provided resource asking the user for a file name.
......
......@@ -11,7 +11,7 @@ import DOM = require('vs/base/browser/dom');
import errors = require('vs/base/common/errors');
import objects = require('vs/base/common/objects');
import Event, {Emitter} from 'vs/base/common/event';
import {IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, ITextFileEditorModelManager, ITextFileEditorModel} from 'vs/workbench/parts/files/common/files';
import {IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, ITextFileEditorModelManager, ITextFileEditorModel, ISaveOptions} from 'vs/workbench/parts/files/common/files';
import {ConfirmResult} from 'vs/workbench/common/editor';
import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
......@@ -244,7 +244,13 @@ export abstract class TextFileService implements ITextFileService {
return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString());
}
public save(resource: URI): TPromise<boolean> {
public save(resource: URI, options?: ISaveOptions): TPromise<boolean> {
// touch resource if options tell so and file is not dirty
if (options && options.force && resource.scheme === 'file' && !this.isDirty(resource)) {
return this.fileService.touchFile(resource).then(() => true);
}
return this.saveAll([resource]).then(result => result.results.length === 1 && result.results[0].success);
}
......
......@@ -216,6 +216,10 @@ export class FileService implements IFileService {
return this.raw.createFolder(resource);
}
public touchFile(resource: uri): TPromise<IFileStat> {
return this.raw.touchFile(resource);
}
public rename(resource: uri, newName: string): TPromise<IFileStat> {
return this.raw.rename(resource, newName);
}
......
......@@ -314,6 +314,31 @@ export class FileService implements IFileService {
});
}
public touchFile(resource: uri): TPromise<IFileStat> {
const absolutePath = this.toAbsolutePath(resource);
// 1.) check file
return this.checkFile(absolutePath).then(exists => {
let createPromise: TPromise<IFileStat>;
if (exists) {
createPromise = TPromise.as(null);
} else {
createPromise = this.createFile(resource);
}
// 2.) create file as needed
return createPromise.then(() => {
// 3.) update atime and mtime
return pfs.utimes(absolutePath, new Date(), new Date()).then(() => {
// 4.) resolve
return this.resolve(resource);
});
});
});
}
public rename(resource: uri, newName: string): TPromise<IFileStat> {
const newPath = paths.join(paths.dirname(resource.fsPath), newName);
......@@ -532,7 +557,7 @@ export class FileService implements IFileService {
return null;
}
private checkFile(absolutePath: string, options: IUpdateContentOptions): TPromise<boolean /* exists */> {
private checkFile(absolutePath: string, options: IUpdateContentOptions = Object.create(null)): TPromise<boolean /* exists */> {
return pfs.exists(absolutePath).then(exists => {
if (exists) {
return pfs.stat(absolutePath).then(stat => {
......
......@@ -10,6 +10,7 @@ import path = require('path');
import os = require('os');
import assert = require('assert');
import {TPromise} from 'vs/base/common/winjs.base';
import {FileService, IEncodingOverride} from 'vs/workbench/services/files/node/fileService';
import {EventType, FileChangesEvent, FileOperationResult, IFileOperationResult} from 'vs/platform/files/common/files';
import {nfcall} from 'vs/base/common/async';
......@@ -79,6 +80,26 @@ suite('FileService', () => {
});
});
test('touchFile', function (done: () => void) {
service.touchFile(uri.file(path.join(testDir, 'test.txt'))).done(s => {
assert.equal(s.name, 'test.txt');
assert.equal(fs.existsSync(s.resource.fsPath), true);
assert.equal(fs.readFileSync(s.resource.fsPath).length, 0);
const stat = fs.statSync(s.resource.fsPath);
return TPromise.timeout(10).then(() => {
return service.touchFile(s.resource).done(s => {
const statNow = fs.statSync(s.resource.fsPath);
assert.ok(statNow.mtime.getTime() >= stat.mtime.getTime()); // one some OS the resolution seems to be 1s, so we use >= here
assert.equal(statNow.size, stat.size);
done();
});
});
});
});
test('renameFile', function (done: () => void) {
service.resolveFile(uri.file(path.join(testDir, 'index.html'))).done(source => {
return service.rename(source.resource, 'other.html').then(renamed => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册