Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
掘金者说
vscode
提交
782fadfe
V
vscode
项目概览
掘金者说
/
vscode
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
782fadfe
编写于
4月 11, 2019
作者:
B
Benjamin Pasero
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
files2 - implement file writing via provider
上级
7a079921
变更
20
展开全部
隐藏空白更改
内联
并排
Showing
20 changed file
with
1450 addition
and
1036 deletion
+1450
-1036
src/vs/platform/files/common/files.ts
src/vs/platform/files/common/files.ts
+34
-15
src/vs/workbench/services/files/node/fileService.ts
src/vs/workbench/services/files/node/fileService.ts
+2
-291
src/vs/workbench/services/files/node/remoteFileService.ts
src/vs/workbench/services/files/node/remoteFileService.ts
+3
-72
src/vs/workbench/services/files/test/electron-browser/fileService.test.ts
.../services/files/test/electron-browser/fileService.test.ts
+10
-158
src/vs/workbench/services/files2/common/fileService2.ts
src/vs/workbench/services/files2/common/fileService2.ts
+2
-10
src/vs/workbench/services/files2/node/diskFileSystemProvider.ts
.../workbench/services/files2/node/diskFileSystemProvider.ts
+58
-11
src/vs/workbench/services/files2/test/node/diskFileService.test.ts
...rkbench/services/files2/test/node/diskFileService.test.ts
+6
-6
src/vs/workbench/services/textfile/common/textFileService.ts
src/vs/workbench/services/textfile/common/textFileService.ts
+10
-9
src/vs/workbench/services/textfile/node/textFileService.ts
src/vs/workbench/services/textfile/node/textFileService.ts
+253
-4
src/vs/workbench/services/textfile/test/fixtures/index.html
src/vs/workbench/services/textfile/test/fixtures/index.html
+121
-0
src/vs/workbench/services/textfile/test/fixtures/lorem.txt
src/vs/workbench/services/textfile/test/fixtures/lorem.txt
+283
-0
src/vs/workbench/services/textfile/test/fixtures/small.txt
src/vs/workbench/services/textfile/test/fixtures/small.txt
+1
-0
src/vs/workbench/services/textfile/test/fixtures/small_umlaut.txt
...orkbench/services/textfile/test/fixtures/small_umlaut.txt
+1
-0
src/vs/workbench/services/textfile/test/fixtures/some_utf16le.css
...orkbench/services/textfile/test/fixtures/some_utf16le.css
+0
-0
src/vs/workbench/services/textfile/test/fixtures/some_utf8_bom.txt
...rkbench/services/textfile/test/fixtures/some_utf8_bom.txt
+1
-0
src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts
...kbench/services/textfile/test/textFileEditorModel.test.ts
+153
-188
src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts
...services/textfile/test/textFileEditorModelManager.test.ts
+78
-108
src/vs/workbench/services/textfile/test/textFileService.io.test.ts
...rkbench/services/textfile/test/textFileService.io.test.ts
+317
-0
src/vs/workbench/services/textfile/test/textFileService.test.ts
.../workbench/services/textfile/test/textFileService.test.ts
+113
-152
src/vs/workbench/test/workbenchTestServices.ts
src/vs/workbench/test/workbenchTestServices.ts
+4
-12
未找到文件。
src/vs/platform/files/common/files.ts
浏览文件 @
782fadfe
...
...
@@ -124,11 +124,6 @@ export interface IFileService {
*/
resolveStreamContent
(
resource
:
URI
,
options
?:
IResolveContentOptions
):
Promise
<
IStreamContent
>
;
/**
* @deprecated use writeFile instead
*/
updateContent
(
resource
:
URI
,
value
:
string
|
ITextSnapshot
,
options
?:
IWriteTextFileOptions
):
Promise
<
IFileStatWithMetadata
>
;
/**
* Updates the content replacing its previous value.
*/
...
...
@@ -148,18 +143,13 @@ export interface IFileService {
*/
copy
(
source
:
URI
,
target
:
URI
,
overwrite
?:
boolean
):
Promise
<
IFileStatWithMetadata
>
;
/**
* @deprecated use createFile2 instead
*/
createFile
(
resource
:
URI
,
content
?:
string
,
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
;
/**
* Creates a new file with the given path and optional contents. The returned promise
* will have the stat model object as a result.
*
* The optional parameter content can be used as value to fill into the new file.
*/
createFile
2
(
resource
:
URI
,
bufferOrReadable
?:
VSBuffer
|
VSBufferReadable
,
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
;
createFile
(
resource
:
URI
,
bufferOrReadable
?:
VSBuffer
|
VSBufferReadable
,
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
;
/**
* Creates a new folder with the given path. The returned promise
...
...
@@ -666,6 +656,7 @@ export interface ITextSnapshot {
*/
export
function
snapshotToString
(
snapshot
:
ITextSnapshot
):
string
{
const
chunks
:
string
[]
=
[];
let
chunk
:
string
|
null
;
while
(
typeof
(
chunk
=
snapshot
.
read
())
===
'
string
'
)
{
chunks
.
push
(
chunk
);
...
...
@@ -674,6 +665,22 @@ export function snapshotToString(snapshot: ITextSnapshot): string {
return
chunks
.
join
(
''
);
}
export
function
stringToSnapshot
(
value
:
string
):
ITextSnapshot
{
let
done
=
false
;
return
{
read
():
string
|
null
{
if
(
!
done
)
{
done
=
true
;
return
value
;
}
return
null
;
}
};
}
export
class
TextSnapshotReadable
implements
VSBufferReadable
{
private
preambleHandled
:
boolean
;
...
...
@@ -703,6 +710,22 @@ export class TextSnapshotReadable implements VSBufferReadable {
}
}
export
function
toBufferOrReadable
(
value
:
string
):
VSBuffer
;
export
function
toBufferOrReadable
(
value
:
ITextSnapshot
):
VSBufferReadable
;
export
function
toBufferOrReadable
(
value
:
string
|
ITextSnapshot
):
VSBuffer
|
VSBufferReadable
;
export
function
toBufferOrReadable
(
value
:
string
|
ITextSnapshot
|
undefined
):
VSBuffer
|
VSBufferReadable
|
undefined
;
export
function
toBufferOrReadable
(
value
:
string
|
ITextSnapshot
|
undefined
):
VSBuffer
|
VSBufferReadable
|
undefined
{
if
(
typeof
value
===
'
undefined
'
)
{
return
undefined
;
}
if
(
typeof
value
===
'
string
'
)
{
return
VSBuffer
.
fromString
(
value
);
}
return
new
TextSnapshotReadable
(
value
);
}
/**
* Streamable content and meta information of a file.
*/
...
...
@@ -1158,8 +1181,4 @@ export interface ILegacyFileService extends IDisposable {
resolveContent
(
resource
:
URI
,
options
?:
IResolveContentOptions
):
Promise
<
IContent
>
;
resolveStreamContent
(
resource
:
URI
,
options
?:
IResolveContentOptions
):
Promise
<
IStreamContent
>
;
updateContent
(
resource
:
URI
,
value
:
string
|
ITextSnapshot
,
options
?:
IWriteTextFileOptions
):
Promise
<
IFileStatWithMetadata
>
;
createFile
(
resource
:
URI
,
content
?:
string
,
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
;
}
\ No newline at end of file
src/vs/workbench/services/files/node/fileService.ts
浏览文件 @
782fadfe
...
...
@@ -5,28 +5,21 @@
import
*
as
paths
from
'
vs/base/common/path
'
;
import
*
as
fs
from
'
fs
'
;
import
*
as
os
from
'
os
'
;
import
*
as
assert
from
'
assert
'
;
import
{
FileOperation
,
FileOperationEvent
,
IContent
,
IResolveContentOptions
,
IFileStat
,
IStreamContent
,
FileOperationError
,
FileOperationResult
,
IWriteTextFileOptions
,
ICreateFileOptions
,
IContentData
,
ITextSnapshot
,
ILegacyFileService
,
IFileStatWithMetadata
,
IFileService
,
IFileSystemProvider
,
etag
}
from
'
vs/platform/files/common/files
'
;
import
{
FileOperation
Event
,
IContent
,
IResolveContentOptions
,
IFileStat
,
IStreamContent
,
FileOperationError
,
FileOperationResult
,
IContentData
,
ILegacyFileService
,
IFileService
,
IFileSystemProvider
}
from
'
vs/platform/files/common/files
'
;
import
{
MAX_FILE_SIZE
,
MAX_HEAP_SIZE
}
from
'
vs/platform/files/node/fileConstants
'
;
import
*
as
objects
from
'
vs/base/common/objects
'
;
import
{
timeout
}
from
'
vs/base/common/async
'
;
import
{
URI
as
uri
}
from
'
vs/base/common/uri
'
;
import
*
as
nls
from
'
vs/nls
'
;
import
{
isWindows
,
isMacintosh
}
from
'
vs/base/common/platform
'
;
import
{
IDisposable
,
Disposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
IWorkspaceContextService
}
from
'
vs/platform/workspace/common/workspace
'
;
import
*
as
pfs
from
'
vs/base/node/pfs
'
;
import
{
detectEncodingFromBuffer
,
decodeStream
,
detectEncodingByBOM
,
UTF8
}
from
'
vs/base/node/encoding
'
;
import
{
detectEncodingFromBuffer
,
decodeStream
}
from
'
vs/base/node/encoding
'
;
import
{
Event
,
Emitter
}
from
'
vs/base/common/event
'
;
import
{
ITextResourceConfigurationService
}
from
'
vs/editor/common/services/resourceConfiguration
'
;
import
{
IEnvironmentService
}
from
'
vs/platform/environment/common/environment
'
;
import
{
CancellationToken
,
CancellationTokenSource
}
from
'
vs/base/common/cancellation
'
;
import
{
Schemas
}
from
'
vs/base/common/network
'
;
import
{
onUnexpectedError
}
from
'
vs/base/common/errors
'
;
import
product
from
'
vs/platform/product/node/product
'
;
import
{
IEncodingOverride
,
ResourceEncodings
}
from
'
vs/workbench/services/files/node/encoding
'
;
import
{
createReadableOfSnapshot
}
from
'
vs/workbench/services/files/node/streams
'
;
import
{
withUndefinedAsNull
}
from
'
vs/base/common/types
'
;
export
interface
IFileServiceTestOptions
{
...
...
@@ -380,290 +373,8 @@ export class LegacyFileService extends Disposable implements ILegacyFileService
//#endregion
//#region File Writing
updateContent
(
resource
:
uri
,
value
:
string
|
ITextSnapshot
,
options
:
IWriteTextFileOptions
=
Object
.
create
(
null
)):
Promise
<
IFileStatWithMetadata
>
{
if
(
options
.
writeElevated
)
{
return
this
.
doUpdateContentElevated
(
resource
,
value
,
options
);
}
return
this
.
doUpdateContent
(
resource
,
value
,
options
);
}
private
doUpdateContent
(
resource
:
uri
,
value
:
string
|
ITextSnapshot
,
options
:
IWriteTextFileOptions
=
Object
.
create
(
null
)):
Promise
<
IFileStatWithMetadata
>
{
const
absolutePath
=
this
.
toAbsolutePath
(
resource
);
// 1.) check file for writing
return
this
.
checkFileBeforeWriting
(
absolutePath
,
options
).
then
(
exists
=>
{
let
createParentsPromise
:
Promise
<
any
>
;
if
(
exists
)
{
createParentsPromise
=
Promise
.
resolve
();
}
else
{
createParentsPromise
=
pfs
.
mkdirp
(
paths
.
dirname
(
absolutePath
));
}
// 2.) create parents as needed
return
createParentsPromise
.
then
(()
=>
{
const
{
encoding
,
hasBOM
}
=
this
.
_encoding
.
getWriteEncoding
(
resource
,
options
.
encoding
);
let
addBomPromise
:
Promise
<
boolean
>
=
Promise
.
resolve
(
false
);
// Some encodings come with a BOM automatically
if
(
hasBOM
)
{
addBomPromise
=
Promise
.
resolve
(
hasBOM
);
}
// Existing UTF-8 file: check for options regarding BOM
else
if
(
exists
&&
encoding
===
UTF8
)
{
if
(
options
.
overwriteEncoding
)
{
addBomPromise
=
Promise
.
resolve
(
false
);
// if we are to overwrite the encoding, we do not preserve it if found
}
else
{
addBomPromise
=
detectEncodingByBOM
(
absolutePath
).
then
(
enc
=>
enc
===
UTF8
);
// otherwise preserve it if found
}
}
// 3.) check to add UTF BOM
return
addBomPromise
.
then
(
addBom
=>
{
// 4.) set contents and resolve
if
(
!
exists
||
!
isWindows
)
{
return
this
.
doSetContentsAndResolve
(
resource
,
absolutePath
,
value
,
addBom
,
encoding
);
}
// On Windows and if the file exists, we use a different strategy of saving the file
// by first truncating the file and then writing with r+ mode. This helps to save hidden files on Windows
// (see https://github.com/Microsoft/vscode/issues/931) and prevent removing alternate data streams
// (see https://github.com/Microsoft/vscode/issues/6363)
else
{
// 4.) truncate
return
pfs
.
truncate
(
absolutePath
,
0
).
then
(()
=>
{
// 5.) set contents (with r+ mode) and resolve
return
this
.
doSetContentsAndResolve
(
resource
,
absolutePath
,
value
,
addBom
,
encoding
,
{
flag
:
'
r+
'
}).
then
(
undefined
,
error
=>
{
if
(
this
.
environmentService
.
verbose
)
{
console
.
error
(
`Truncate succeeded, but save failed (
${
error
}
), retrying after 100ms`
);
}
// We heard from one user that fs.truncate() succeeds, but the save fails (https://github.com/Microsoft/vscode/issues/61310)
// In that case, the file is now entirely empty and the contents are gone. This can happen if an external file watcher is
// installed that reacts on the truncate and keeps the file busy right after. Our workaround is to retry to save after a
// short timeout, assuming that the file is free to write then.
return
timeout
(
100
).
then
(()
=>
this
.
doSetContentsAndResolve
(
resource
,
absolutePath
,
value
,
addBom
,
encoding
,
{
flag
:
'
r+
'
}));
});
},
error
=>
{
if
(
this
.
environmentService
.
verbose
)
{
console
.
error
(
`Truncate failed (
${
error
}
), falling back to normal save`
);
}
// we heard from users that fs.truncate() fails (https://github.com/Microsoft/vscode/issues/59561)
// in that case we simply save the file without truncating first (same as macOS and Linux)
return
this
.
doSetContentsAndResolve
(
resource
,
absolutePath
,
value
,
addBom
,
encoding
);
});
}
});
});
}).
then
(
undefined
,
error
=>
{
if
(
error
.
code
===
'
EACCES
'
||
error
.
code
===
'
EPERM
'
)
{
return
Promise
.
reject
(
new
FileOperationError
(
nls
.
localize
(
'
filePermission
'
,
"
Permission denied writing to file ({0})
"
,
resource
.
toString
(
true
)),
FileOperationResult
.
FILE_PERMISSION_DENIED
,
options
));
}
return
Promise
.
reject
(
error
);
});
}
private
doSetContentsAndResolve
(
resource
:
uri
,
absolutePath
:
string
,
value
:
string
|
ITextSnapshot
,
addBOM
:
boolean
,
encodingToWrite
:
string
,
options
?:
{
mode
?:
number
;
flag
?:
string
;
}):
Promise
<
IFileStat
>
{
// Configure encoding related options as needed
const
writeFileOptions
:
pfs
.
IWriteFileOptions
=
options
?
options
:
Object
.
create
(
null
);
if
(
addBOM
||
encodingToWrite
!==
UTF8
)
{
writeFileOptions
.
encoding
=
{
charset
:
encodingToWrite
,
addBOM
};
}
let
writeFilePromise
:
Promise
<
void
>
;
if
(
typeof
value
===
'
string
'
)
{
writeFilePromise
=
pfs
.
writeFile
(
absolutePath
,
value
,
writeFileOptions
);
}
else
{
writeFilePromise
=
pfs
.
writeFile
(
absolutePath
,
createReadableOfSnapshot
(
value
),
writeFileOptions
);
}
// set contents
return
writeFilePromise
.
then
(()
=>
{
// resolve
return
this
.
fileService
.
resolve
(
resource
);
});
}
private
doUpdateContentElevated
(
resource
:
uri
,
value
:
string
|
ITextSnapshot
,
options
:
IWriteTextFileOptions
=
Object
.
create
(
null
)):
Promise
<
IFileStatWithMetadata
>
{
const
absolutePath
=
this
.
toAbsolutePath
(
resource
);
// 1.) check file for writing
return
this
.
checkFileBeforeWriting
(
absolutePath
,
options
,
options
.
overwriteReadonly
/* ignore readonly if we overwrite readonly, this is handled via sudo later */
).
then
(
exists
=>
{
const
writeOptions
:
IWriteTextFileOptions
=
objects
.
assign
(
Object
.
create
(
null
),
options
);
writeOptions
.
writeElevated
=
false
;
writeOptions
.
encoding
=
this
.
_encoding
.
getWriteEncoding
(
resource
,
options
.
encoding
).
encoding
;
// 2.) write to a temporary file to be able to copy over later
const
tmpPath
=
paths
.
join
(
os
.
tmpdir
(),
`code-elevated-
${
Math
.
random
().
toString
(
36
).
replace
(
/
[^
a-z
]
+/g
,
''
).
substr
(
0
,
6
)}
`
);
return
this
.
updateContent
(
uri
.
file
(
tmpPath
),
value
,
writeOptions
).
then
(()
=>
{
// 3.) invoke our CLI as super user
return
import
(
'
sudo-prompt
'
).
then
(
sudoPrompt
=>
{
return
new
Promise
<
void
>
((
resolve
,
reject
)
=>
{
const
promptOptions
=
{
name
:
this
.
environmentService
.
appNameLong
.
replace
(
'
-
'
,
''
),
icns
:
(
isMacintosh
&&
this
.
environmentService
.
isBuilt
)
?
paths
.
join
(
paths
.
dirname
(
this
.
environmentService
.
appRoot
),
`
${
product
.
nameShort
}
.icns`
)
:
undefined
};
const
sudoCommand
:
string
[]
=
[
`"
${
this
.
environmentService
.
cliPath
}
"`
];
if
(
options
.
overwriteReadonly
)
{
sudoCommand
.
push
(
'
--file-chmod
'
);
}
sudoCommand
.
push
(
'
--file-write
'
,
`"
${
tmpPath
}
"`
,
`"
${
absolutePath
}
"`
);
sudoPrompt
.
exec
(
sudoCommand
.
join
(
'
'
),
promptOptions
,
(
error
:
string
,
stdout
:
string
,
stderr
:
string
)
=>
{
if
(
error
||
stderr
)
{
reject
(
error
||
stderr
);
}
else
{
resolve
(
undefined
);
}
});
});
}).
then
(()
=>
{
// 3.) delete temp file
return
pfs
.
rimraf
(
tmpPath
,
pfs
.
RimRafMode
.
MOVE
).
then
(()
=>
{
// 4.) resolve again
return
this
.
fileService
.
resolve
(
resource
);
});
});
});
}).
then
(
undefined
,
error
=>
{
if
(
this
.
environmentService
.
verbose
)
{
onUnexpectedError
(
`Unable to write to file '
${
resource
.
toString
(
true
)}
' as elevated user (
${
error
}
)`
);
}
if
(
!
FileOperationError
.
isFileOperationError
(
error
))
{
error
=
new
FileOperationError
(
nls
.
localize
(
'
filePermission
'
,
"
Permission denied writing to file ({0})
"
,
resource
.
toString
(
true
)),
FileOperationResult
.
FILE_PERMISSION_DENIED
,
options
);
}
return
Promise
.
reject
(
error
);
});
}
//#endregion
//#region Create File
createFile
(
resource
:
uri
,
content
:
string
=
''
,
options
:
ICreateFileOptions
=
Object
.
create
(
null
)):
Promise
<
IFileStatWithMetadata
>
{
const
absolutePath
=
this
.
toAbsolutePath
(
resource
);
let
checkFilePromise
:
Promise
<
boolean
>
;
if
(
options
.
overwrite
)
{
checkFilePromise
=
Promise
.
resolve
(
false
);
}
else
{
checkFilePromise
=
pfs
.
exists
(
absolutePath
);
}
// Check file exists
return
checkFilePromise
.
then
(
exists
=>
{
if
(
exists
&&
!
options
.
overwrite
)
{
return
Promise
.
reject
(
new
FileOperationError
(
nls
.
localize
(
'
fileExists
'
,
"
File to create already exists ({0})
"
,
resource
.
toString
(
true
)),
FileOperationResult
.
FILE_MODIFIED_SINCE
,
options
));
}
// Create file
return
this
.
updateContent
(
resource
,
content
).
then
(
result
=>
{
// Events
this
.
_onAfterOperation
.
fire
(
new
FileOperationEvent
(
resource
,
FileOperation
.
CREATE
,
result
));
return
result
;
});
});
}
//#endregion
//#region Helpers
private
checkFileBeforeWriting
(
absolutePath
:
string
,
options
:
IWriteTextFileOptions
=
Object
.
create
(
null
),
ignoreReadonly
?:
boolean
):
Promise
<
boolean
/* exists */
>
{
return
pfs
.
exists
(
absolutePath
).
then
(
exists
=>
{
if
(
exists
)
{
return
pfs
.
stat
(
absolutePath
).
then
(
stat
=>
{
if
(
stat
.
isDirectory
())
{
return
Promise
.
reject
(
new
Error
(
'
Expected file is actually a directory
'
));
}
// Dirty write prevention: if the file on disk has been changed and does not match our expected
// mtime and etag, we bail out to prevent dirty writing.
//
// First, we check for a mtime that is in the future before we do more checks. The assumption is
// that only the mtime is an indicator for a file that has changd on disk.
//
// Second, if the mtime has advanced, we compare the size of the file on disk with our previous
// one using the etag() function. Relying only on the mtime check has prooven to produce false
// positives due to file system weirdness (especially around remote file systems). As such, the
// check for size is a weaker check because it can return a false negative if the file has changed
// but to the same length. This is a compromise we take to avoid having to produce checksums of
// the file content for comparison which would be much slower to compute.
if
(
typeof
options
.
mtime
===
'
number
'
&&
typeof
options
.
etag
===
'
string
'
&&
options
.
mtime
<
stat
.
mtime
.
getTime
()
&&
options
.
etag
!==
etag
(
stat
.
size
,
options
.
mtime
))
{
return
Promise
.
reject
(
new
FileOperationError
(
nls
.
localize
(
'
fileModifiedError
'
,
"
File Modified Since
"
),
FileOperationResult
.
FILE_MODIFIED_SINCE
,
options
));
}
// Throw if file is readonly and we are not instructed to overwrite
if
(
!
ignoreReadonly
&&
!
(
stat
.
mode
&
128
)
/* readonly */
)
{
if
(
!
options
.
overwriteReadonly
)
{
return
this
.
readOnlyError
<
boolean
>
(
options
);
}
// Try to change mode to writeable
let
mode
=
stat
.
mode
;
mode
=
mode
|
128
;
return
pfs
.
chmod
(
absolutePath
,
mode
).
then
(()
=>
{
// Make sure to check the mode again, it could have failed
return
pfs
.
stat
(
absolutePath
).
then
(
stat
=>
{
if
(
!
(
stat
.
mode
&
128
)
/* readonly */
)
{
return
this
.
readOnlyError
<
boolean
>
(
options
);
}
return
exists
;
});
});
}
return
exists
;
});
}
return
exists
;
});
}
private
readOnlyError
<
T
>
(
options
:
IWriteTextFileOptions
):
Promise
<
T
>
{
return
Promise
.
reject
(
new
FileOperationError
(
nls
.
localize
(
'
fileReadOnlyError
'
,
"
File is Read Only
"
),
FileOperationResult
.
FILE_READ_ONLY
,
options
));
}
private
toAbsolutePath
(
arg1
:
uri
|
IFileStat
):
string
{
let
resource
:
uri
;
if
(
arg1
instanceof
uri
)
{
...
...
src/vs/workbench/services/files/node/remoteFileService.ts
浏览文件 @
782fadfe
...
...
@@ -7,28 +7,16 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import
{
Schemas
}
from
'
vs/base/common/network
'
;
import
*
as
resources
from
'
vs/base/common/resources
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
IDecodeStreamOptions
,
toDecodeStream
,
encodeStream
}
from
'
vs/base/node/encoding
'
;
import
{
IDecodeStreamOptions
,
toDecodeStream
}
from
'
vs/base/node/encoding
'
;
import
{
ITextResourceConfigurationService
}
from
'
vs/editor/common/services/resourceConfiguration
'
;
import
{
localize
}
from
'
vs/nls
'
;
import
{
IEnvironmentService
}
from
'
vs/platform/environment/common/environment
'
;
import
{
FileOperation
,
FileOperationError
,
FileOperationEvent
,
FileOperationResult
,
FileWriteOptions
,
FileSystemProviderCapabilities
,
IContent
,
ICreateFileOptions
,
IFileSystemProvider
,
IResolveContentOptions
,
IStreamContent
,
ITextSnapshot
,
IWriteTextFileOptions
,
ILegacyFileService
,
IFileService
,
toFileOperationResult
,
IFileStatWithMetadata
}
from
'
vs/platform/files/common/files
'
;
import
{
FileOperation
Error
,
FileOperationResult
,
IContent
,
IFileSystemProvider
,
IResolveContentOptions
,
IStreamContent
,
ILegacyFileService
,
IFileService
}
from
'
vs/platform/files/common/files
'
;
import
{
IWorkspaceContextService
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
LegacyFileService
}
from
'
vs/workbench/services/files/node/fileService
'
;
import
{
createReadableOfProvider
,
createReadableOfSnapshot
,
createWritableOfProvider
}
from
'
vs/workbench/services/files/node/streams
'
;
import
{
createReadableOfProvider
}
from
'
vs/workbench/services/files/node/streams
'
;
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
class
StringSnapshot
implements
ITextSnapshot
{
private
_value
:
string
|
null
;
constructor
(
value
:
string
)
{
this
.
_value
=
value
;
}
read
():
string
|
null
{
let
ret
=
this
.
_value
;
this
.
_value
=
null
;
return
ret
;
}
}
export
class
LegacyRemoteFileService
extends
LegacyFileService
{
private
readonly
_provider
:
Map
<
string
,
IFileSystemProvider
>
;
...
...
@@ -163,63 +151,6 @@ export class LegacyRemoteFileService extends LegacyFileService {
// --- saving
private
static
_throwIfFileSystemIsReadonly
(
provider
:
IFileSystemProvider
):
IFileSystemProvider
{
if
(
provider
.
capabilities
&
FileSystemProviderCapabilities
.
Readonly
)
{
throw
new
FileOperationError
(
localize
(
'
err.readonly
'
,
"
Resource can not be modified.
"
),
FileOperationResult
.
FILE_PERMISSION_DENIED
);
}
return
provider
;
}
createFile
(
resource
:
URI
,
content
?:
string
,
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
if
(
resource
.
scheme
===
Schemas
.
file
)
{
return
super
.
createFile
(
resource
,
content
,
options
);
}
else
{
return
this
.
_withProvider
(
resource
).
then
(
LegacyRemoteFileService
.
_throwIfFileSystemIsReadonly
).
then
(
provider
=>
{
return
this
.
fileService
.
createFolder
(
resources
.
dirname
(
resource
)).
then
(()
=>
{
const
{
encoding
}
=
this
.
encoding
.
getWriteEncoding
(
resource
);
return
this
.
_writeFile
(
provider
,
resource
,
new
StringSnapshot
(
content
||
''
),
encoding
,
{
create
:
true
,
overwrite
:
Boolean
(
options
&&
options
.
overwrite
)
});
});
}).
then
(
fileStat
=>
{
this
.
_onAfterOperation
.
fire
(
new
FileOperationEvent
(
resource
,
FileOperation
.
CREATE
,
fileStat
));
return
fileStat
;
},
err
=>
{
const
message
=
localize
(
'
err.create
'
,
"
Failed to create file {0}
"
,
resource
.
toString
(
false
));
const
result
=
toFileOperationResult
(
err
);
throw
new
FileOperationError
(
message
,
result
,
options
);
});
}
}
updateContent
(
resource
:
URI
,
value
:
string
|
ITextSnapshot
,
options
?:
IWriteTextFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
if
(
resource
.
scheme
===
Schemas
.
file
)
{
return
super
.
updateContent
(
resource
,
value
,
options
);
}
else
{
return
this
.
_withProvider
(
resource
).
then
(
LegacyRemoteFileService
.
_throwIfFileSystemIsReadonly
).
then
(
provider
=>
{
return
this
.
fileService
.
createFolder
(
resources
.
dirname
(
resource
)).
then
(()
=>
{
const
snapshot
=
typeof
value
===
'
string
'
?
new
StringSnapshot
(
value
)
:
value
;
return
this
.
_writeFile
(
provider
,
resource
,
snapshot
,
options
&&
options
.
encoding
,
{
create
:
true
,
overwrite
:
true
});
});
});
}
}
private
_writeFile
(
provider
:
IFileSystemProvider
,
resource
:
URI
,
snapshot
:
ITextSnapshot
,
preferredEncoding
:
string
|
undefined
=
undefined
,
options
:
FileWriteOptions
):
Promise
<
IFileStatWithMetadata
>
{
const
readable
=
createReadableOfSnapshot
(
snapshot
);
const
{
encoding
,
hasBOM
}
=
this
.
encoding
.
getWriteEncoding
(
resource
,
preferredEncoding
);
const
encoder
=
encodeStream
(
encoding
,
{
addBOM
:
hasBOM
});
const
target
=
createWritableOfProvider
(
provider
,
resource
,
options
);
return
new
Promise
((
resolve
,
reject
)
=>
{
readable
.
pipe
(
encoder
).
pipe
(
target
);
target
.
once
(
'
error
'
,
err
=>
reject
(
err
));
target
.
once
(
'
finish
'
,
(
_
:
unknown
)
=>
resolve
(
undefined
));
}).
then
(
_
=>
{
return
this
.
fileService
.
resolve
(
resource
,
{
resolveMetadata
:
true
})
as
Promise
<
IFileStatWithMetadata
>
;
});
}
private
static
_asContent
(
content
:
IStreamContent
):
Promise
<
IContent
>
{
return
new
Promise
<
IContent
>
((
resolve
,
reject
)
=>
{
let
result
:
IContent
=
{
...
...
src/vs/workbench/services/files/test/electron-browser/fileService.test.ts
浏览文件 @
782fadfe
...
...
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
*
as
fs
from
'
fs
'
;
//
import * as fs from 'fs';
import
*
as
path
from
'
vs/base/common/path
'
;
import
*
as
os
from
'
os
'
;
import
*
as
assert
from
'
assert
'
;
...
...
@@ -17,7 +17,6 @@ import { TestEnvironmentService, TestContextService, TestTextResourceConfigurati
import
{
getRandomTestPath
}
from
'
vs/base/test/node/testUtils
'
;
import
{
Workspace
,
toWorkspaceFolders
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
TestConfigurationService
}
from
'
vs/platform/configuration/test/common/testConfigurationService
'
;
import
{
TextModel
}
from
'
vs/editor/common/model/textModel
'
;
import
{
IEncodingOverride
}
from
'
vs/workbench/services/files/node/encoding
'
;
import
{
getPathFromAmdModule
}
from
'
vs/base/common/amd
'
;
import
{
FileService2
}
from
'
vs/workbench/services/files2/common/fileService2
'
;
...
...
@@ -53,153 +52,6 @@ suite('LegacyFileService', () => {
return
pfs
.
rimraf
(
parentDir
,
pfs
.
RimRafMode
.
MOVE
);
});
test
(
'
updateContent - use encoding (UTF 16 BE)
'
,
function
()
{
const
resource
=
uri
.
file
(
path
.
join
(
testDir
,
'
small.txt
'
));
const
encoding
=
'
utf16be
'
;
return
service
.
resolveContent
(
resource
).
then
(
c
=>
{
c
.
encoding
=
encoding
;
return
service
.
updateContent
(
c
.
resource
,
c
.
value
,
{
encoding
:
encoding
}).
then
(
c
=>
{
return
encodingLib
.
detectEncodingByBOM
(
c
.
resource
.
fsPath
).
then
((
enc
)
=>
{
assert
.
equal
(
enc
,
encodingLib
.
UTF16be
);
return
service
.
resolveContent
(
resource
).
then
(
c
=>
{
assert
.
equal
(
c
.
encoding
,
encoding
);
});
});
});
});
});
test
(
'
updateContent - use encoding (UTF 16 BE, ITextSnapShot)
'
,
function
()
{
const
resource
=
uri
.
file
(
path
.
join
(
testDir
,
'
small.txt
'
));
const
encoding
=
'
utf16be
'
;
return
service
.
resolveContent
(
resource
).
then
(
c
=>
{
c
.
encoding
=
encoding
;
const
model
=
TextModel
.
createFromString
(
c
.
value
);
return
service
.
updateContent
(
c
.
resource
,
model
.
createSnapshot
(),
{
encoding
:
encoding
}).
then
(
c
=>
{
return
encodingLib
.
detectEncodingByBOM
(
c
.
resource
.
fsPath
).
then
((
enc
)
=>
{
assert
.
equal
(
enc
,
encodingLib
.
UTF16be
);
return
service
.
resolveContent
(
resource
).
then
(
c
=>
{
assert
.
equal
(
c
.
encoding
,
encoding
);
model
.
dispose
();
});
});
});
});
});
test
(
'
updateContent - encoding preserved (UTF 16 LE)
'
,
function
()
{
const
encoding
=
'
utf16le
'
;
const
resource
=
uri
.
file
(
path
.
join
(
testDir
,
'
some_utf16le.css
'
));
return
service
.
resolveContent
(
resource
).
then
(
c
=>
{
assert
.
equal
(
c
.
encoding
,
encoding
);
c
.
value
=
'
Some updates
'
;
return
service
.
updateContent
(
c
.
resource
,
c
.
value
,
{
encoding
:
encoding
}).
then
(
c
=>
{
return
encodingLib
.
detectEncodingByBOM
(
c
.
resource
.
fsPath
).
then
((
enc
)
=>
{
assert
.
equal
(
enc
,
encodingLib
.
UTF16le
);
return
service
.
resolveContent
(
resource
).
then
(
c
=>
{
assert
.
equal
(
c
.
encoding
,
encoding
);
});
});
});
});
});
test
(
'
updateContent - encoding preserved (UTF 16 LE, ITextSnapShot)
'
,
function
()
{
const
encoding
=
'
utf16le
'
;
const
resource
=
uri
.
file
(
path
.
join
(
testDir
,
'
some_utf16le.css
'
));
return
service
.
resolveContent
(
resource
).
then
(
c
=>
{
assert
.
equal
(
c
.
encoding
,
encoding
);
const
model
=
TextModel
.
createFromString
(
'
Some updates
'
);
return
service
.
updateContent
(
c
.
resource
,
model
.
createSnapshot
(),
{
encoding
:
encoding
}).
then
(
c
=>
{
return
encodingLib
.
detectEncodingByBOM
(
c
.
resource
.
fsPath
).
then
((
enc
)
=>
{
assert
.
equal
(
enc
,
encodingLib
.
UTF16le
);
return
service
.
resolveContent
(
resource
).
then
(
c
=>
{
assert
.
equal
(
c
.
encoding
,
encoding
);
model
.
dispose
();
});
});
});
});
});
test
(
'
updateContent - UTF 8 BOMs
'
,
function
()
{
// setup
const
_id
=
uuid
.
generateUuid
();
const
_testDir
=
path
.
join
(
parentDir
,
_id
);
const
_sourceDir
=
getPathFromAmdModule
(
require
,
'
./fixtures/service
'
);
const
resource
=
uri
.
file
(
path
.
join
(
testDir
,
'
index.html
'
));
const
fileService
=
new
FileService2
(
new
NullLogService
());
fileService
.
registerProvider
(
Schemas
.
file
,
new
DiskFileSystemProvider
(
new
NullLogService
()));
const
_service
=
new
LegacyFileService
(
fileService
,
new
TestContextService
(
new
Workspace
(
_testDir
,
toWorkspaceFolders
([{
path
:
_testDir
}]))),
TestEnvironmentService
,
new
TestTextResourceConfigurationService
()
);
return
pfs
.
copy
(
_sourceDir
,
_testDir
).
then
(()
=>
{
return
pfs
.
readFile
(
resource
.
fsPath
).
then
(
data
=>
{
assert
.
equal
(
encodingLib
.
detectEncodingByBOMFromBuffer
(
data
,
512
),
null
);
const
model
=
TextModel
.
createFromString
(
'
Hello Bom
'
);
// Update content: UTF_8 => UTF_8_BOM
return
_service
.
updateContent
(
resource
,
model
.
createSnapshot
(),
{
encoding
:
encodingLib
.
UTF8_with_bom
}).
then
(()
=>
{
return
pfs
.
readFile
(
resource
.
fsPath
).
then
(
data
=>
{
assert
.
equal
(
encodingLib
.
detectEncodingByBOMFromBuffer
(
data
,
512
),
encodingLib
.
UTF8
);
// Update content: PRESERVE BOM when using UTF-8
model
.
setValue
(
'
Please stay Bom
'
);
return
_service
.
updateContent
(
resource
,
model
.
createSnapshot
(),
{
encoding
:
encodingLib
.
UTF8
}).
then
(()
=>
{
return
pfs
.
readFile
(
resource
.
fsPath
).
then
(
data
=>
{
assert
.
equal
(
encodingLib
.
detectEncodingByBOMFromBuffer
(
data
,
512
),
encodingLib
.
UTF8
);
// Update content: REMOVE BOM
model
.
setValue
(
'
Go away Bom
'
);
return
_service
.
updateContent
(
resource
,
model
.
createSnapshot
(),
{
encoding
:
encodingLib
.
UTF8
,
overwriteEncoding
:
true
}).
then
(()
=>
{
return
pfs
.
readFile
(
resource
.
fsPath
).
then
(
data
=>
{
assert
.
equal
(
encodingLib
.
detectEncodingByBOMFromBuffer
(
data
,
512
),
null
);
// Update content: BOM comes not back
model
.
setValue
(
'
Do not come back Bom
'
);
return
_service
.
updateContent
(
resource
,
model
.
createSnapshot
(),
{
encoding
:
encodingLib
.
UTF8
}).
then
(()
=>
{
return
pfs
.
readFile
(
resource
.
fsPath
).
then
(
data
=>
{
assert
.
equal
(
encodingLib
.
detectEncodingByBOMFromBuffer
(
data
,
512
),
null
);
model
.
dispose
();
_service
.
dispose
();
});
});
});
});
});
});
});
});
});
});
});
test
(
'
resolveContent - large file
'
,
function
()
{
const
resource
=
uri
.
file
(
path
.
join
(
testDir
,
'
lorem.txt
'
));
...
...
@@ -263,17 +115,17 @@ suite('LegacyFileService', () => {
});
});
test
(
'
resolveContent - FILE_MODIFIED_SINCE
'
,
function
()
{
const
resource
=
uri
.
file
(
path
.
join
(
testDir
,
'
index.html
'
));
//
test('resolveContent - FILE_MODIFIED_SINCE', function () {
//
const resource = uri.file(path.join(testDir, 'index.html'));
return
service
.
resolveContent
(
resource
).
then
(
c
=>
{
fs
.
writeFileSync
(
resource
.
fsPath
,
'
Updates Incoming!
'
);
//
return service.resolveContent(resource).then(c => {
//
fs.writeFileSync(resource.fsPath, 'Updates Incoming!');
return
service
.
updateContent
(
resource
,
c
.
value
,
{
etag
:
c
.
etag
,
mtime
:
c
.
mtime
-
1000
}).
then
(
undefined
,
(
e
:
FileOperationError
)
=>
{
assert
.
equal
(
e
.
fileOperationResult
,
FileOperationResult
.
FILE_MODIFIED_SINCE
);
});
});
});
//
return service.updateContent(resource, c.value, { etag: c.etag, mtime: c.mtime - 1000 }).then(undefined, (e: FileOperationError) => {
//
assert.equal(e.fileOperationResult, FileOperationResult.FILE_MODIFIED_SINCE);
//
});
//
});
//
});
test
(
'
resolveContent - encoding picked up
'
,
function
()
{
const
resource
=
uri
.
file
(
path
.
join
(
testDir
,
'
index.html
'
));
...
...
src/vs/workbench/services/files2/common/fileService2.ts
浏览文件 @
782fadfe
...
...
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import
{
Disposable
,
IDisposable
,
toDisposable
,
combinedDisposable
,
dispose
}
from
'
vs/base/common/lifecycle
'
;
import
{
IFileService
,
IResolveFileOptions
,
IResourceEncodings
,
FileChangesEvent
,
FileOperationEvent
,
IFileSystemProviderRegistrationEvent
,
IFileSystemProvider
,
IFileStat
,
IResolveFileResult
,
IResolveContentOptions
,
IContent
,
IStreamContent
,
I
TextSnapshot
,
IWriteTextFileOptions
,
I
CreateFileOptions
,
IFileSystemProviderActivationEvent
,
FileOperationError
,
FileOperationResult
,
FileOperation
,
FileSystemProviderCapabilities
,
FileType
,
toFileSystemProviderErrorCode
,
FileSystemProviderErrorCode
,
IStat
,
IFileStatWithMetadata
,
IResolveMetadataFileOptions
,
etag
,
hasReadWriteCapability
,
hasFileFolderCopyCapability
,
hasOpenReadWriteCloseCapability
,
toFileOperationResult
,
IFileSystemProviderWithOpenReadWriteCloseCapability
,
IFileSystemProviderWithFileReadWriteCapability
,
IResolveFileResultWithMetadata
,
IWatchOptions
,
ILegacyFileService
,
IWriteFileOptions
}
from
'
vs/platform/files/common/files
'
;
import
{
IFileService
,
IResolveFileOptions
,
IResourceEncodings
,
FileChangesEvent
,
FileOperationEvent
,
IFileSystemProviderRegistrationEvent
,
IFileSystemProvider
,
IFileStat
,
IResolveFileResult
,
IResolveContentOptions
,
IContent
,
IStreamContent
,
ICreateFileOptions
,
IFileSystemProviderActivationEvent
,
FileOperationError
,
FileOperationResult
,
FileOperation
,
FileSystemProviderCapabilities
,
FileType
,
toFileSystemProviderErrorCode
,
FileSystemProviderErrorCode
,
IStat
,
IFileStatWithMetadata
,
IResolveMetadataFileOptions
,
etag
,
hasReadWriteCapability
,
hasFileFolderCopyCapability
,
hasOpenReadWriteCloseCapability
,
toFileOperationResult
,
IFileSystemProviderWithOpenReadWriteCloseCapability
,
IFileSystemProviderWithFileReadWriteCapability
,
IResolveFileResultWithMetadata
,
IWatchOptions
,
ILegacyFileService
,
IWriteFileOptions
}
from
'
vs/platform/files/common/files
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
Event
,
Emitter
}
from
'
vs/base/common/event
'
;
import
{
ServiceIdentifier
}
from
'
vs/platform/instantiation/common/instantiation
'
;
...
...
@@ -295,7 +295,7 @@ export class FileService2 extends Disposable implements IFileService {
return
this
.
_legacy
.
encoding
;
}
async
createFile
2
(
resource
:
URI
,
bufferOrReadable
:
VSBuffer
|
VSBufferReadable
=
VSBuffer
.
fromString
(
''
),
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
async
createFile
(
resource
:
URI
,
bufferOrReadable
:
VSBuffer
|
VSBufferReadable
=
VSBuffer
.
fromString
(
''
),
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
// validate overwrite
const
overwrite
=
!!
(
options
&&
options
.
overwrite
);
...
...
@@ -370,10 +370,6 @@ export class FileService2 extends Disposable implements IFileService {
return
this
.
resolve
(
resource
,
{
resolveMetadata
:
true
});
}
createFile
(
resource
:
URI
,
content
?:
string
,
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
return
this
.
joinOnLegacy
.
then
(
legacy
=>
legacy
.
createFile
(
resource
,
content
,
options
));
}
resolveContent
(
resource
:
URI
,
options
?:
IResolveContentOptions
):
Promise
<
IContent
>
{
return
this
.
joinOnLegacy
.
then
(
legacy
=>
legacy
.
resolveContent
(
resource
,
options
));
}
...
...
@@ -382,10 +378,6 @@ export class FileService2 extends Disposable implements IFileService {
return
this
.
joinOnLegacy
.
then
(
legacy
=>
legacy
.
resolveStreamContent
(
resource
,
options
));
}
updateContent
(
resource
:
URI
,
value
:
string
|
ITextSnapshot
,
options
?:
IWriteTextFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
return
this
.
joinOnLegacy
.
then
(
legacy
=>
legacy
.
updateContent
(
resource
,
value
,
options
));
}
//#endregion
//#region Move/Copy/Delete/Create Folder
...
...
src/vs/workbench/services/files2/node/diskFileSystemProvider.ts
浏览文件 @
782fadfe
...
...
@@ -3,14 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
mkdir
,
open
,
close
,
read
,
write
}
from
'
fs
'
;
import
{
mkdir
,
open
,
close
,
read
,
write
,
fdatasync
}
from
'
fs
'
;
import
{
promisify
}
from
'
util
'
;
import
{
IDisposable
,
Disposable
,
toDisposable
,
dispose
}
from
'
vs/base/common/lifecycle
'
;
import
{
IFileSystemProvider
,
FileSystemProviderCapabilities
,
IFileChange
,
IWatchOptions
,
IStat
,
FileType
,
FileDeleteOptions
,
FileOverwriteOptions
,
FileWriteOptions
,
FileOpenOptions
,
FileSystemProviderErrorCode
,
createFileSystemProviderError
,
FileSystemProviderError
}
from
'
vs/platform/files/common/files
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
Event
,
Emitter
}
from
'
vs/base/common/event
'
;
import
{
isLinux
,
isWindows
}
from
'
vs/base/common/platform
'
;
import
{
statLink
,
readdir
,
unlink
,
move
,
copy
,
readFile
,
writeFile
,
fileExists
,
truncate
,
rimraf
,
RimRafMode
}
from
'
vs/base/node/pfs
'
;
import
{
statLink
,
readdir
,
unlink
,
move
,
copy
,
readFile
,
writeFile
,
truncate
,
rimraf
,
RimRafMode
,
exists
}
from
'
vs/base/node/pfs
'
;
import
{
normalize
,
basename
,
dirname
}
from
'
vs/base/common/path
'
;
import
{
joinPath
}
from
'
vs/base/common/resources
'
;
import
{
isEqual
}
from
'
vs/base/common/extpath
'
;
...
...
@@ -116,14 +116,14 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
const
filePath
=
this
.
toFilePath
(
resource
);
// Validate target
const
exists
=
await
fileE
xists
(
filePath
);
if
(
e
xists
&&
!
opts
.
overwrite
)
{
const
fileExists
=
await
e
xists
(
filePath
);
if
(
fileE
xists
&&
!
opts
.
overwrite
)
{
throw
createFileSystemProviderError
(
new
Error
(
localize
(
'
fileExists
'
,
"
File already exists
"
)),
FileSystemProviderErrorCode
.
FileExists
);
}
else
if
(
!
e
xists
&&
!
opts
.
create
)
{
}
else
if
(
!
fileE
xists
&&
!
opts
.
create
)
{
throw
createFileSystemProviderError
(
new
Error
(
localize
(
'
fileNotExists
'
,
"
File does not exist
"
)),
FileSystemProviderErrorCode
.
FileNotFound
);
}
if
(
e
xists
&&
isWindows
)
{
if
(
fileE
xists
&&
isWindows
)
{
try
{
// On Windows and if the file exists, we use a different strategy of saving the file
// by first truncating the file and then writing with r+ flag. This helps to save hidden files on Windows
...
...
@@ -154,16 +154,36 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
}
}
private
writeHandles
:
Set
<
number
>
=
new
Set
();
private
canFlush
:
boolean
=
true
;
async
open
(
resource
:
URI
,
opts
:
FileOpenOptions
):
Promise
<
number
>
{
try
{
const
filePath
=
this
.
toFilePath
(
resource
);
let
flags
:
string
;
let
flags
:
string
|
undefined
=
undefined
;
if
(
opts
.
create
)
{
// we take this as a hint that the file is opened for writing
if
(
isWindows
&&
await
exists
(
filePath
))
{
try
{
// On Windows and if the file exists, we use a different strategy of saving the file
// by first truncating the file and then writing with r+ flag. This helps to save hidden files on Windows
// (see https://github.com/Microsoft/vscode/issues/931) and prevent removing alternate data streams
// (see https://github.com/Microsoft/vscode/issues/6363)
await
truncate
(
filePath
,
0
);
// After a successful truncate() the flag can be set to 'r+' which will not truncate.
flags
=
'
r+
'
;
}
catch
(
error
)
{
this
.
logService
.
trace
(
error
);
}
}
// we take opts.create as a hint that the file is opened for writing
// as such we use 'w' to truncate an existing or create the
// file otherwise. we do not allow reading.
flags
=
'
w
'
;
if
(
!
flags
)
{
flags
=
'
w
'
;
}
}
else
{
// otherwise we assume the file is opened for reading
// as such we use 'r' to neither truncate, nor create
...
...
@@ -171,7 +191,14 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
flags
=
'
r
'
;
}
return
await
promisify
(
open
)(
filePath
,
flags
);
const
handle
=
await
promisify
(
open
)(
filePath
,
flags
);
// remember that this handle was used for writing
if
(
opts
.
create
)
{
this
.
writeHandles
.
add
(
handle
);
}
return
handle
;
}
catch
(
error
)
{
throw
this
.
toFileSystemProviderError
(
error
);
}
...
...
@@ -179,6 +206,19 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
async
close
(
fd
:
number
):
Promise
<
void
>
{
try
{
// if a handle is closed that was used for writing, ensure
// to flush the contents to disk if possible.
if
(
this
.
writeHandles
.
delete
(
fd
)
&&
this
.
canFlush
)
{
try
{
await
promisify
(
fdatasync
)(
fd
);
}
catch
(
error
)
{
// In some exotic setups it is well possible that node fails to sync
// In that case we disable flushing and log the error to our logger
this
.
canFlush
=
false
;
this
.
logService
.
error
(
error
);
}
}
return
await
promisify
(
close
)(
fd
);
}
catch
(
error
)
{
throw
this
.
toFileSystemProviderError
(
error
);
...
...
@@ -199,6 +239,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
}
async
write
(
fd
:
number
,
pos
:
number
,
data
:
Uint8Array
,
offset
:
number
,
length
:
number
):
Promise
<
number
>
{
// we know at this point that the file to write to is truncated and thus empty
// if the write now fails, the file remains empty. as such we really try hard
// to ensure the write succeeds by retrying up to three times.
return
retry
(()
=>
this
.
doWrite
(
fd
,
pos
,
data
,
offset
,
length
),
100
/* ms delay */
,
3
/* retries */
);
}
private
async
doWrite
(
fd
:
number
,
pos
:
number
,
data
:
Uint8Array
,
offset
:
number
,
length
:
number
):
Promise
<
number
>
{
try
{
const
result
=
await
promisify
(
write
)(
fd
,
data
,
offset
,
length
,
pos
);
if
(
typeof
result
===
'
number
'
)
{
...
...
@@ -295,7 +342,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
const
isCaseChange
=
isPathCaseSensitive
?
false
:
isEqual
(
fromFilePath
,
toFilePath
,
true
/* ignore case */
);
// handle existing target (unless this is a case change)
if
(
!
isCaseChange
&&
await
fileE
xists
(
toFilePath
))
{
if
(
!
isCaseChange
&&
await
e
xists
(
toFilePath
))
{
if
(
!
overwrite
)
{
throw
createFileSystemProviderError
(
new
Error
(
'
File at target already exists
'
),
FileSystemProviderErrorCode
.
FileExists
);
}
...
...
src/vs/workbench/services/files2/test/node/diskFileService.test.ts
浏览文件 @
782fadfe
...
...
@@ -800,13 +800,13 @@ suite('Disk File Service', () => {
}
});
test
(
'
createFile
2
'
,
async
()
=>
{
test
(
'
createFile
'
,
async
()
=>
{
let
event
:
FileOperationEvent
;
disposables
.
push
(
service
.
onAfterOperation
(
e
=>
event
=
e
));
const
contents
=
'
Hello World
'
;
const
resource
=
URI
.
file
(
join
(
testDir
,
'
test.txt
'
));
const
fileStat
=
await
service
.
createFile
2
(
resource
,
VSBuffer
.
fromString
(
contents
));
const
fileStat
=
await
service
.
createFile
(
resource
,
VSBuffer
.
fromString
(
contents
));
assert
.
equal
(
fileStat
.
name
,
'
test.txt
'
);
assert
.
equal
(
existsSync
(
fileStat
.
resource
.
fsPath
),
true
);
assert
.
equal
(
readFileSync
(
fileStat
.
resource
.
fsPath
),
contents
);
...
...
@@ -817,21 +817,21 @@ suite('Disk File Service', () => {
assert
.
equal
(
event
!
.
target
!
.
resource
.
fsPath
,
resource
.
fsPath
);
});
test
(
'
createFile
2
(does not overwrite by default)
'
,
async
()
=>
{
test
(
'
createFile (does not overwrite by default)
'
,
async
()
=>
{
const
contents
=
'
Hello World
'
;
const
resource
=
URI
.
file
(
join
(
testDir
,
'
test.txt
'
));
writeFileSync
(
resource
.
fsPath
,
''
);
// create file
try
{
await
service
.
createFile
2
(
resource
,
VSBuffer
.
fromString
(
contents
));
await
service
.
createFile
(
resource
,
VSBuffer
.
fromString
(
contents
));
}
catch
(
error
)
{
assert
.
ok
(
error
);
}
});
test
(
'
createFile
2
(allows to overwrite existing)
'
,
async
()
=>
{
test
(
'
createFile (allows to overwrite existing)
'
,
async
()
=>
{
let
event
:
FileOperationEvent
;
disposables
.
push
(
service
.
onAfterOperation
(
e
=>
event
=
e
));
...
...
@@ -840,7 +840,7 @@ suite('Disk File Service', () => {
writeFileSync
(
resource
.
fsPath
,
''
);
// create file
const
fileStat
=
await
service
.
createFile
2
(
resource
,
VSBuffer
.
fromString
(
contents
),
{
overwrite
:
true
});
const
fileStat
=
await
service
.
createFile
(
resource
,
VSBuffer
.
fromString
(
contents
),
{
overwrite
:
true
});
assert
.
equal
(
fileStat
.
name
,
'
test.txt
'
);
assert
.
equal
(
existsSync
(
fileStat
.
resource
.
fsPath
),
true
);
assert
.
equal
(
readFileSync
(
fileStat
.
resource
.
fsPath
),
contents
);
...
...
src/vs/workbench/services/textfile/common/textFileService.ts
浏览文件 @
782fadfe
...
...
@@ -15,7 +15,7 @@ import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, I
import
{
ConfirmResult
,
IRevertOptions
}
from
'
vs/workbench/common/editor
'
;
import
{
ILifecycleService
,
ShutdownReason
,
LifecyclePhase
}
from
'
vs/platform/lifecycle/common/lifecycle
'
;
import
{
IWorkspaceContextService
,
WorkbenchState
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
IFileService
,
IResolveContentOptions
,
IFilesConfiguration
,
FileOperationError
,
FileOperationResult
,
AutoSaveConfiguration
,
HotExitConfiguration
,
ITextSnapshot
,
IWriteTextFileOptions
,
IFileStatWithMetadata
}
from
'
vs/platform/files/common/files
'
;
import
{
IFileService
,
IResolveContentOptions
,
IFilesConfiguration
,
FileOperationError
,
FileOperationResult
,
AutoSaveConfiguration
,
HotExitConfiguration
,
ITextSnapshot
,
IWriteTextFileOptions
,
IFileStatWithMetadata
,
toBufferOrReadable
,
ICreateFileOptions
}
from
'
vs/platform/files/common/files
'
;
import
{
IConfigurationService
}
from
'
vs/platform/configuration/common/configuration
'
;
import
{
Disposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
IWorkbenchEnvironmentService
}
from
'
vs/workbench/services/environment/common/environmentService
'
;
...
...
@@ -69,7 +69,7 @@ export class TextFileService extends Disposable implements ITextFileService {
@
IFileService
protected
readonly
fileService
:
IFileService
,
@
IUntitledEditorService
private
readonly
untitledEditorService
:
IUntitledEditorService
,
@
ILifecycleService
private
readonly
lifecycleService
:
ILifecycleService
,
@
IInstantiationService
instantiationService
:
IInstantiationService
,
@
IInstantiationService
protected
instantiationService
:
IInstantiationService
,
@
IConfigurationService
private
readonly
configurationService
:
IConfigurationService
,
@
IModeService
private
readonly
modeService
:
IModeService
,
@
IModelService
private
readonly
modelService
:
IModelService
,
...
...
@@ -382,15 +382,14 @@ export class TextFileService extends Disposable implements ITextFileService {
};
}
async
create
(
resource
:
URI
,
contents
?:
string
,
options
?:
{
overwrite
?:
boolean
}):
Promise
<
IFileStatWithMetadata
>
{
const
existingModel
=
this
.
models
.
get
(
resource
);
const
stat
=
await
this
.
fileService
.
createFile
(
resource
,
contents
,
options
);
async
create
(
resource
:
URI
,
value
?:
string
,
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
const
stat
=
await
this
.
doCreate
(
resource
,
value
,
options
);
// If we had an existing model for the given resource, load
// it again to make sure it is up to date with the contents
// we just wrote into the underlying resource by calling
// revert()
const
existingModel
=
this
.
models
.
get
(
resource
);
if
(
existingModel
&&
!
existingModel
.
isDisposed
())
{
await
existingModel
.
revert
();
}
...
...
@@ -398,10 +397,12 @@ export class TextFileService extends Disposable implements ITextFileService {
return
stat
;
}
async
write
(
resource
:
URI
,
value
:
string
|
ITextSnapshot
,
options
?:
IWriteTextFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
const
stat
=
await
this
.
fileService
.
updateContent
(
resource
,
value
,
options
);
protected
doCreate
(
resource
:
URI
,
value
?:
string
,
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
return
this
.
fileService
.
createFile
(
resource
,
toBufferOrReadable
(
value
),
options
);
}
return
stat
;
async
write
(
resource
:
URI
,
value
:
string
|
ITextSnapshot
,
options
?:
IWriteTextFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
return
this
.
fileService
.
writeFile
(
resource
,
toBufferOrReadable
(
value
),
options
);
}
async
delete
(
resource
:
URI
,
options
?:
{
useTrash
?:
boolean
,
recursive
?:
boolean
}):
Promise
<
void
>
{
...
...
src/vs/workbench/services/textfile/node/textFileService.ts
浏览文件 @
782fadfe
...
...
@@ -8,15 +8,57 @@ import { TextFileService } from 'vs/workbench/services/textfile/common/textFileS
import
{
ITextFileService
}
from
'
vs/workbench/services/textfile/common/textfiles
'
;
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
ITextSnapshot
,
IWriteTextFileOptions
,
IFileStatWithMetadata
}
from
'
vs/platform/files/common/files
'
;
import
{
ITextSnapshot
,
IWriteTextFileOptions
,
IFileStatWithMetadata
,
IResourceEncoding
,
IResolveContentOptions
,
IFileService
,
stringToSnapshot
,
ICreateFileOptions
}
from
'
vs/platform/files/common/files
'
;
import
{
Schemas
}
from
'
vs/base/common/network
'
;
import
{
exists
,
stat
,
chmod
,
rimraf
}
from
'
vs/base/node/pfs
'
;
import
{
join
,
dirname
}
from
'
vs/base/common/path
'
;
import
{
isMacintosh
}
from
'
vs/base/common/platform
'
;
import
{
isMacintosh
,
isLinux
}
from
'
vs/base/common/platform
'
;
import
product
from
'
vs/platform/product/node/product
'
;
import
{
ITextResourceConfigurationService
}
from
'
vs/editor/common/services/resourceConfiguration
'
;
import
{
IWorkspaceContextService
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
UTF8
,
UTF8_with_bom
,
UTF16be
,
UTF16le
,
encodingExists
,
IDetectedEncodingResult
,
detectEncodingByBOM
,
encodeStream
}
from
'
vs/base/node/encoding
'
;
import
{
WORKSPACE_EXTENSION
}
from
'
vs/platform/workspaces/common/workspaces
'
;
import
{
joinPath
,
extname
,
isEqualOrParent
}
from
'
vs/base/common/resources
'
;
import
{
Disposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
IEnvironmentService
}
from
'
vs/platform/environment/common/environment
'
;
import
{
VSBufferReadable
,
VSBuffer
}
from
'
vs/base/common/buffer
'
;
import
{
Readable
}
from
'
stream
'
;
import
{
isUndefinedOrNull
}
from
'
vs/base/common/types
'
;
export
class
NodeTextFileService
extends
TextFileService
{
private
_encoding
:
EncodingOracle
;
protected
get
encoding
():
EncodingOracle
{
if
(
!
this
.
_encoding
)
{
this
.
_encoding
=
this
.
_register
(
this
.
instantiationService
.
createInstance
(
EncodingOracle
,
[]));
}
return
this
.
_encoding
;
}
protected
async
doCreate
(
resource
:
URI
,
value
?:
string
,
options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
// check for encoding
const
{
encoding
,
addBOM
}
=
await
this
.
encoding
.
getWriteEncoding
(
resource
);
// return to parent when encoding is standard
if
(
encoding
===
UTF8
&&
!
addBOM
)
{
return
super
.
doCreate
(
resource
,
value
,
options
);
}
// otherwise create with encoding
return
this
.
fileService
.
createFile
(
resource
,
this
.
getEncodedReadable
(
value
||
''
,
encoding
,
addBOM
),
options
);
}
private
getEncodedReadable
(
value
:
string
|
ITextSnapshot
,
encoding
:
string
,
addBOM
:
boolean
):
VSBufferReadable
{
const
readable
=
this
.
toNodeReadable
(
value
);
const
encoder
=
encodeStream
(
encoding
,
{
addBOM
});
const
encodedReadable
=
readable
.
pipe
(
encoder
);
return
this
.
toBufferReadable
(
encodedReadable
);
}
async
write
(
resource
:
URI
,
value
:
string
|
ITextSnapshot
,
options
?:
IWriteTextFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
// check for overwriteReadonly property (only supported for local file://)
...
...
@@ -36,14 +78,73 @@ export class NodeTextFileService extends TextFileService {
return
this
.
writeElevated
(
resource
,
value
,
options
);
}
return
super
.
write
(
resource
,
value
,
options
);
// check for encoding
const
{
encoding
,
addBOM
}
=
await
this
.
encoding
.
getWriteEncoding
(
resource
,
options
);
// return to parent when encoding is standard
if
(
encoding
===
UTF8
&&
!
addBOM
)
{
return
super
.
write
(
resource
,
value
,
options
);
}
// otherwise save with encoding
return
this
.
fileService
.
writeFile
(
resource
,
this
.
getEncodedReadable
(
value
,
encoding
,
addBOM
),
options
);
}
private
toNodeReadable
(
value
:
string
|
ITextSnapshot
):
Readable
{
let
snapshot
:
ITextSnapshot
;
if
(
typeof
value
===
'
string
'
)
{
snapshot
=
stringToSnapshot
(
value
);
}
else
{
snapshot
=
value
;
}
return
new
Readable
({
read
:
function
()
{
try
{
let
chunk
:
string
|
null
=
null
;
let
canPush
=
true
;
// Push all chunks as long as we can push and as long as
// the underlying snapshot returns strings to us
while
(
canPush
&&
typeof
(
chunk
=
snapshot
.
read
())
===
'
string
'
)
{
canPush
=
this
.
push
(
chunk
);
}
// Signal EOS by pushing NULL
if
(
typeof
chunk
!==
'
string
'
)
{
this
.
push
(
null
);
}
}
catch
(
error
)
{
this
.
emit
(
'
error
'
,
error
);
}
},
encoding
:
UTF8
// very important, so that strings are passed around and not buffers!
});
}
private
toBufferReadable
(
stream
:
NodeJS
.
ReadWriteStream
):
VSBufferReadable
{
return
{
read
():
VSBuffer
|
null
{
const
res
=
stream
.
read
();
if
(
isUndefinedOrNull
(
res
))
{
return
null
;
}
if
(
typeof
res
===
'
string
'
)
{
return
VSBuffer
.
fromString
(
res
);
}
return
VSBuffer
.
wrap
(
res
);
}
};
}
private
async
writeElevated
(
resource
:
URI
,
value
:
string
|
ITextSnapshot
,
options
?:
IWriteTextFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
// write into a tmp file first
const
tmpPath
=
join
(
tmpdir
(),
`code-elevated-
${
Math
.
random
().
toString
(
36
).
replace
(
/
[^
a-z
]
+/g
,
''
).
substr
(
0
,
6
)}
`
);
await
this
.
write
(
URI
.
file
(
tmpPath
),
value
,
{
encoding
:
this
.
fileService
.
encoding
.
getWriteEncoding
(
resource
,
options
?
options
.
encoding
:
undefined
).
encoding
});
const
{
encoding
,
addBOM
}
=
await
this
.
encoding
.
getWriteEncoding
(
resource
,
options
);
await
this
.
write
(
URI
.
file
(
tmpPath
),
value
,
{
encoding
:
encoding
===
UTF8
&&
addBOM
?
UTF8_with_bom
:
encoding
});
// sudo prompt copy
await
this
.
sudoPromptCopy
(
tmpPath
,
resource
.
fsPath
,
options
);
...
...
@@ -83,4 +184,152 @@ export class NodeTextFileService extends TextFileService {
}
}
export
interface
IEncodingOverride
{
parent
?:
URI
;
extension
?:
string
;
encoding
:
string
;
}
export
class
EncodingOracle
extends
Disposable
{
private
encodingOverrides
:
IEncodingOverride
[];
constructor
(
encodingOverrides
:
IEncodingOverride
[],
@
ITextResourceConfigurationService
private
textResourceConfigurationService
:
ITextResourceConfigurationService
,
@
IEnvironmentService
private
environmentService
:
IEnvironmentService
,
@
IWorkspaceContextService
private
contextService
:
IWorkspaceContextService
,
@
IFileService
private
fileService
:
IFileService
)
{
super
();
this
.
encodingOverrides
=
encodingOverrides
||
this
.
getDefaultEncodingOverrides
();
this
.
registerListeners
();
}
private
registerListeners
():
void
{
// Workspace Folder Change
this
.
_register
(
this
.
contextService
.
onDidChangeWorkspaceFolders
(()
=>
this
.
encodingOverrides
=
this
.
getDefaultEncodingOverrides
()));
}
private
getDefaultEncodingOverrides
():
IEncodingOverride
[]
{
const
defaultEncodingOverrides
:
IEncodingOverride
[]
=
[];
// Global settings
defaultEncodingOverrides
.
push
({
parent
:
URI
.
file
(
this
.
environmentService
.
appSettingsHome
),
encoding
:
UTF8
});
// Workspace files
defaultEncodingOverrides
.
push
({
extension
:
WORKSPACE_EXTENSION
,
encoding
:
UTF8
});
// Folder Settings
this
.
contextService
.
getWorkspace
().
folders
.
forEach
(
folder
=>
{
defaultEncodingOverrides
.
push
({
parent
:
joinPath
(
folder
.
uri
,
'
.vscode
'
),
encoding
:
UTF8
});
});
return
defaultEncodingOverrides
;
}
async
getWriteEncoding
(
resource
:
URI
,
options
?:
IWriteTextFileOptions
):
Promise
<
{
encoding
:
string
,
addBOM
:
boolean
}
>
{
const
{
encoding
,
hasBOM
}
=
this
.
doGetWriteEncoding
(
resource
,
options
?
options
.
encoding
:
undefined
);
// Some encodings come with a BOM automatically
if
(
hasBOM
)
{
return
{
encoding
,
addBOM
:
true
};
}
// Existing UTF-8 file: check for options regarding BOM
if
(
encoding
===
UTF8
&&
await
this
.
fileService
.
exists
(
resource
))
{
// if we are to overwrite the encoding, we do not preserve it if found
if
(
options
&&
options
.
overwriteEncoding
)
{
return
{
encoding
,
addBOM
:
false
};
}
// otherwise preserve it if found
if
(
resource
.
scheme
===
Schemas
.
file
&&
await
detectEncodingByBOM
(
resource
.
fsPath
)
===
UTF8
)
{
return
{
encoding
,
addBOM
:
true
};
}
}
return
{
encoding
,
addBOM
:
false
};
}
private
doGetWriteEncoding
(
resource
:
URI
,
preferredEncoding
?:
string
):
IResourceEncoding
{
const
resourceEncoding
=
this
.
getEncodingForResource
(
resource
,
preferredEncoding
);
return
{
encoding
:
resourceEncoding
,
hasBOM
:
resourceEncoding
===
UTF16be
||
resourceEncoding
===
UTF16le
||
resourceEncoding
===
UTF8_with_bom
// enforce BOM for certain encodings
};
}
getReadEncoding
(
resource
:
URI
,
options
:
IResolveContentOptions
|
undefined
,
detected
:
IDetectedEncodingResult
):
string
{
let
preferredEncoding
:
string
|
undefined
;
// Encoding passed in as option
if
(
options
&&
options
.
encoding
)
{
if
(
detected
.
encoding
===
UTF8
&&
options
.
encoding
===
UTF8
)
{
preferredEncoding
=
UTF8_with_bom
;
// indicate the file has BOM if we are to resolve with UTF 8
}
else
{
preferredEncoding
=
options
.
encoding
;
// give passed in encoding highest priority
}
}
// Encoding detected
else
if
(
detected
.
encoding
)
{
if
(
detected
.
encoding
===
UTF8
)
{
preferredEncoding
=
UTF8_with_bom
;
// if we detected UTF-8, it can only be because of a BOM
}
else
{
preferredEncoding
=
detected
.
encoding
;
}
}
// Encoding configured
else
if
(
this
.
textResourceConfigurationService
.
getValue
(
resource
,
'
files.encoding
'
)
===
UTF8_with_bom
)
{
preferredEncoding
=
UTF8
;
// if we did not detect UTF 8 BOM before, this can only be UTF 8 then
}
return
this
.
getEncodingForResource
(
resource
,
preferredEncoding
);
}
private
getEncodingForResource
(
resource
:
URI
,
preferredEncoding
?:
string
):
string
{
let
fileEncoding
:
string
;
const
override
=
this
.
getEncodingOverride
(
resource
);
if
(
override
)
{
fileEncoding
=
override
;
// encoding override always wins
}
else
if
(
preferredEncoding
)
{
fileEncoding
=
preferredEncoding
;
// preferred encoding comes second
}
else
{
fileEncoding
=
this
.
textResourceConfigurationService
.
getValue
(
resource
,
'
files.encoding
'
);
// and last we check for settings
}
if
(
!
fileEncoding
||
!
encodingExists
(
fileEncoding
))
{
fileEncoding
=
UTF8
;
// the default is UTF 8
}
return
fileEncoding
;
}
private
getEncodingOverride
(
resource
:
URI
):
string
|
undefined
{
if
(
this
.
encodingOverrides
&&
this
.
encodingOverrides
.
length
)
{
for
(
const
override
of
this
.
encodingOverrides
)
{
// check if the resource is child of encoding override path
if
(
override
.
parent
&&
isEqualOrParent
(
resource
,
override
.
parent
,
!
isLinux
/* ignorecase */
))
{
return
override
.
encoding
;
}
// check if the resource extension is equal to encoding override
if
(
override
.
extension
&&
extname
(
resource
)
===
`.
${
override
.
extension
}
`
)
{
return
override
.
encoding
;
}
}
}
return
undefined
;
}
}
registerSingleton
(
ITextFileService
,
NodeTextFileService
);
\ No newline at end of file
src/vs/workbench/services/textfile/test/fixtures/index.html
0 → 100644
浏览文件 @
782fadfe
<!DOCTYPE html>
<html>
<head
id=
'headID'
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge"
/>
<title>
Strada
</title>
<link
href=
"site.css"
rel=
"stylesheet"
type=
"text/css"
/>
<script
src=
"jquery-1.4.1.js"
></script>
<script
src=
"../compiler/dtree.js"
type=
"text/javascript"
></script>
<script
src=
"../compiler/typescript.js"
type=
"text/javascript"
></script>
<script
type=
"text/javascript"
>
// Compile strada source into resulting javascript
function
compile
(
prog
,
libText
)
{
var
outfile
=
{
source
:
""
,
Write
:
function
(
s
)
{
this
.
source
+=
s
;
},
WriteLine
:
function
(
s
)
{
this
.
source
+=
s
+
"
\r
"
;
},
}
var
parseErrors
=
[]
var
compiler
=
new
Tools
.
TypeScriptCompiler
(
outfile
,
true
);
compiler
.
setErrorCallback
(
function
(
start
,
len
,
message
)
{
parseErrors
.
push
({
start
:
start
,
len
:
len
,
message
:
message
});
});
compiler
.
addUnit
(
libText
,
"
lib.ts
"
);
compiler
.
addUnit
(
prog
,
"
input.ts
"
);
compiler
.
typeCheck
();
compiler
.
emit
();
if
(
parseErrors
.
length
>
0
)
{
//throw new Error(parseErrors);
}
while
(
outfile
.
source
[
0
]
==
'
/
'
&&
outfile
.
source
[
1
]
==
'
/
'
&&
outfile
.
source
[
2
]
==
'
'
)
{
outfile
.
source
=
outfile
.
source
.
slice
(
outfile
.
source
.
indexOf
(
'
\r
'
)
+
1
);
}
var
errorPrefix
=
""
;
for
(
var
i
=
0
;
i
<
parseErrors
.
length
;
i
++
)
{
errorPrefix
+=
"
// Error: (
"
+
parseErrors
[
i
].
start
+
"
,
"
+
parseErrors
[
i
].
len
+
"
)
"
+
parseErrors
[
i
].
message
+
"
\r
"
;
}
return
errorPrefix
+
outfile
.
source
;
}
</script>
<script
type=
"text/javascript"
>
var
libText
=
""
;
$
.
get
(
"
../compiler/lib.ts
"
,
function
(
newLibText
)
{
libText
=
newLibText
;
});
// execute the javascript in the compiledOutput pane
function
execute
()
{
$
(
'
#compilation
'
).
text
(
"
Running...
"
);
var
txt
=
$
(
'
#compiledOutput
'
).
val
();
var
res
;
try
{
var
ret
=
eval
(
txt
);
res
=
"
Ran successfully!
"
;
}
catch
(
e
)
{
res
=
"
Exception thrown:
"
+
e
;
}
$
(
'
#compilation
'
).
text
(
String
(
res
));
}
// recompile the stradaSrc and populate the compiledOutput pane
function
srcUpdated
()
{
var
newText
=
$
(
'
#stradaSrc
'
).
val
();
var
compiledSource
;
try
{
compiledSource
=
compile
(
newText
,
libText
);
}
catch
(
e
)
{
compiledSource
=
"
//Parse error
"
for
(
var
i
in
e
)
compiledSource
+=
"
\r
//
"
+
e
[
i
];
}
$
(
'
#compiledOutput
'
).
val
(
compiledSource
);
}
// Populate the stradaSrc pane with one of the built in samples
function
exampleSelectionChanged
()
{
var
examples
=
document
.
getElementById
(
'
examples
'
);
var
selectedExample
=
examples
.
options
[
examples
.
selectedIndex
].
value
;
if
(
selectedExample
!=
""
)
{
$
.
get
(
'
examples/
'
+
selectedExample
,
function
(
srcText
)
{
$
(
'
#stradaSrc
'
).
val
(
srcText
);
setTimeout
(
srcUpdated
,
100
);
},
function
(
err
)
{
console
.
log
(
err
);
});
}
}
</script>
</head>
<body>
<h1>
TypeScript
</h1>
<br
/>
<select
id=
"examples"
onchange=
'exampleSelectionChanged()'
>
<option
value=
""
>
Select...
</option>
<option
value=
"small.ts"
>
Small
</option>
<option
value=
"employee.ts"
>
Employees
</option>
<option
value=
"conway.ts"
>
Conway Game of Life
</option>
<option
value=
"typescript.ts"
>
TypeScript Compiler
</option>
</select>
<div>
<textarea
id=
'stradaSrc'
rows=
'40'
cols=
'80'
onchange=
'srcUpdated()'
onkeyup=
'srcUpdated()'
spellcheck=
"false"
>
//Type your TypeScript here...
</textarea>
<textarea
id=
'compiledOutput'
rows=
'40'
cols=
'80'
spellcheck=
"false"
>
//Compiled code will show up here...
</textarea>
<br
/>
<button
onclick=
'execute()'
/>
Run
</button>
<div
id=
'compilation'
>
Press 'run' to execute code...
</div>
<div
id=
'results'
>
...write your results into #results...
</div>
</div>
<div
id=
'bod'
style=
'display:none'
></div>
</body>
</html>
src/vs/workbench/services/textfile/test/fixtures/lorem.txt
0 → 100644
浏览文件 @
782fadfe
此差异已折叠。
点击以展开。
src/vs/workbench/services/textfile/test/fixtures/small.txt
0 → 100644
浏览文件 @
782fadfe
Small File
\ No newline at end of file
src/vs/workbench/services/textfile/test/fixtures/small_umlaut.txt
0 → 100644
浏览文件 @
782fadfe
Small File with Ümlaut
\ No newline at end of file
src/vs/workbench/services/textfile/test/fixtures/some_utf16le.css
0 → 100644
浏览文件 @
782fadfe
B
/*----------------------------------------------------------
src/vs/workbench/services/textfile/test/fixtures/some_utf8_bom.txt
0 → 100644
浏览文件 @
782fadfe
This is some UTF 8 with BOM file.
\ No newline at end of file
src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts
浏览文件 @
782fadfe
...
...
@@ -44,21 +44,21 @@ suite('Files - TextFileEditorModel', () => {
accessor
.
fileService
.
setContent
(
content
);
});
test
(
'
Save
'
,
function
()
{
test
(
'
Save
'
,
async
function
()
{
const
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
bar
'
);
assert
.
ok
(
getLastModifiedTime
(
model
)
<=
Date
.
now
());
await
model
.
load
();
return
model
.
save
().
then
(()
=>
{
assert
.
ok
(
model
.
getLastSaveAttemptTime
()
<=
Date
.
now
());
assert
.
ok
(
!
model
.
isDirty
());
model
.
textEditorModel
!
.
setValue
(
'
bar
'
);
assert
.
ok
(
getLastModifiedTime
(
model
)
<=
Date
.
now
());
model
.
dispose
();
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
model
.
getResource
()));
});
});
await
model
.
save
();
assert
.
ok
(
model
.
getLastSaveAttemptTime
()
<=
Date
.
now
());
assert
.
ok
(
!
model
.
isDirty
());
model
.
dispose
();
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
model
.
getResource
()));
});
test
(
'
setEncoding - encode
'
,
function
()
{
...
...
@@ -74,29 +74,26 @@ suite('Files - TextFileEditorModel', () => {
model
.
dispose
();
});
test
(
'
setEncoding - decode
'
,
function
()
{
test
(
'
setEncoding - decode
'
,
async
function
()
{
const
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
model
.
setEncoding
(
'
utf16
'
,
EncodingMode
.
Decode
);
return
timeout
(
0
).
then
(()
=>
{
// due to model updating async
assert
.
ok
(
model
.
isResolved
());
// model got loaded due to decoding
model
.
dispose
();
});
await
timeout
(
0
);
assert
.
ok
(
model
.
isResolved
());
// model got loaded due to decoding
model
.
dispose
();
});
test
(
'
disposes when underlying model is destroyed
'
,
function
()
{
test
(
'
disposes when underlying model is destroyed
'
,
async
function
()
{
const
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
dispose
();
await
model
.
load
();
assert
.
ok
(
model
.
isDisposed
()
);
}
);
model
.
textEditorModel
!
.
dispose
(
);
assert
.
ok
(
model
.
isDisposed
()
);
});
test
(
'
Load does not trigger save
'
,
function
()
{
test
(
'
Load does not trigger save
'
,
async
function
()
{
const
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index.txt
'
),
'
utf8
'
);
assert
.
ok
(
model
.
hasState
(
ModelState
.
SAVED
));
...
...
@@ -104,32 +101,26 @@ suite('Files - TextFileEditorModel', () => {
assert
.
ok
(
e
!==
StateChange
.
DIRTY
&&
e
!==
StateChange
.
SAVED
);
});
return
model
.
load
().
then
(()
=>
{
assert
.
ok
(
model
.
isResolved
());
model
.
dispose
();
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
model
.
getResource
()));
});
await
model
.
load
();
assert
.
ok
(
model
.
isResolved
());
model
.
dispose
();
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
model
.
getResource
()));
});
test
(
'
Load returns dirty model as long as model is dirty
'
,
function
()
{
test
(
'
Load returns dirty model as long as model is dirty
'
,
async
function
()
{
const
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
model
.
isDirty
());
assert
.
ok
(
model
.
hasState
(
ModelState
.
DIRTY
));
return
model
.
load
().
then
(()
=>
{
assert
.
ok
(
model
.
isDirty
());
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
model
.
isDirty
());
assert
.
ok
(
model
.
hasState
(
ModelState
.
DIRTY
));
model
.
dispose
();
}
);
}
);
await
model
.
load
();
assert
.
ok
(
model
.
isDirty
()
);
model
.
dispose
(
);
});
test
(
'
Revert
'
,
function
()
{
test
(
'
Revert
'
,
async
function
()
{
let
eventCounter
=
0
;
const
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
...
...
@@ -140,22 +131,18 @@ suite('Files - TextFileEditorModel', () => {
}
});
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
model
.
isDirty
());
return
model
.
revert
().
then
(()
=>
{
assert
.
ok
(
!
model
.
isDirty
());
assert
.
equal
(
model
.
textEditorModel
!
.
getValue
(),
'
Hello Html
'
);
assert
.
equal
(
eventCounter
,
1
);
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
model
.
isDirty
());
model
.
dispose
();
});
});
await
model
.
revert
();
assert
.
ok
(
!
model
.
isDirty
());
assert
.
equal
(
model
.
textEditorModel
!
.
getValue
(),
'
Hello Html
'
);
assert
.
equal
(
eventCounter
,
1
);
model
.
dispose
();
});
test
(
'
Revert (soft)
'
,
function
()
{
test
(
'
Revert (soft)
'
,
async
function
()
{
let
eventCounter
=
0
;
const
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
...
...
@@ -166,99 +153,88 @@ suite('Files - TextFileEditorModel', () => {
}
});
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
model
.
isDirty
());
assert
.
ok
(
model
.
isDirty
());
return
model
.
revert
(
true
/* soft revert */
).
then
(()
=>
{
assert
.
ok
(
!
model
.
isDirty
());
assert
.
equal
(
model
.
textEditorModel
!
.
getValue
(),
'
foo
'
);
assert
.
equal
(
eventCounter
,
1
);
model
.
dispose
();
});
});
await
model
.
revert
(
true
/* soft revert */
);
assert
.
ok
(
!
model
.
isDirty
());
assert
.
equal
(
model
.
textEditorModel
!
.
getValue
(),
'
foo
'
);
assert
.
equal
(
eventCounter
,
1
);
model
.
dispose
();
});
test
(
'
Load and undo turns model dirty
'
,
function
()
{
test
(
'
Load and undo turns model dirty
'
,
async
function
()
{
const
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
return
model
.
load
().
then
(()
=>
{
accessor
.
fileService
.
setContent
(
'
Hello Change
'
);
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
undo
();
await
model
.
load
();
accessor
.
fileService
.
setContent
(
'
Hello Change
'
);
assert
.
ok
(
model
.
isDirty
()
);
}
);
}
);
await
model
.
load
(
);
model
.
textEditorModel
!
.
undo
(
);
assert
.
ok
(
model
.
isDirty
()
);
});
test
(
'
File not modified error is handled gracefully
'
,
function
()
{
cons
t
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
test
(
'
File not modified error is handled gracefully
'
,
async
function
()
{
le
t
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
return
model
.
load
().
then
(()
=>
{
const
mtime
=
getLastModifiedTime
(
model
);
accessor
.
textFileService
.
setResolveTextContentErrorOnce
(
new
FileOperationError
(
'
error
'
,
FileOperationResult
.
FILE_NOT_MODIFIED_SINCE
));
await
model
.
load
();
return
model
.
load
().
then
((
model
:
TextFileEditorModel
)
=>
{
assert
.
ok
(
model
);
assert
.
equal
(
getLastModifiedTime
(
model
),
mtime
);
model
.
dispose
();
});
});
const
mtime
=
getLastModifiedTime
(
model
);
accessor
.
textFileService
.
setResolveTextContentErrorOnce
(
new
FileOperationError
(
'
error
'
,
FileOperationResult
.
FILE_NOT_MODIFIED_SINCE
));
model
=
await
model
.
load
()
as
TextFileEditorModel
;
assert
.
ok
(
model
);
assert
.
equal
(
getLastModifiedTime
(
model
),
mtime
);
model
.
dispose
();
});
test
(
'
Load error is handled gracefully if model already exists
'
,
function
()
{
cons
t
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
test
(
'
Load error is handled gracefully if model already exists
'
,
async
function
()
{
le
t
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
return
model
.
load
().
then
(()
=>
{
accessor
.
textFileService
.
setResolveTextContentErrorOnce
(
new
FileOperationError
(
'
error
'
,
FileOperationResult
.
FILE_NOT_FOUND
));
await
model
.
load
();
accessor
.
textFileService
.
setResolveTextContentErrorOnce
(
new
FileOperationError
(
'
error
'
,
FileOperationResult
.
FILE_NOT_FOUND
));
return
model
.
load
().
then
((
model
:
TextFileEditorModel
)
=>
{
assert
.
ok
(
model
);
model
.
dispose
();
});
});
model
=
await
model
.
load
()
as
TextFileEditorModel
;
assert
.
ok
(
model
);
model
.
dispose
();
});
test
(
'
save() and isDirty() - proper with check for mtimes
'
,
function
()
{
test
(
'
save() and isDirty() - proper with check for mtimes
'
,
async
function
()
{
const
input1
=
createFileInput
(
instantiationService
,
toResource
.
call
(
this
,
'
/path/index_async2.txt
'
));
const
input2
=
createFileInput
(
instantiationService
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
));
return
input1
.
resolve
().
then
((
model1
:
TextFileEditorModel
)
=>
{
return
input2
.
resolve
().
then
((
model2
:
TextFileEditorModel
)
=>
{
model1
.
textEditorModel
!
.
setValue
(
'
foo
'
);
const
m1Mtime
=
model1
.
getStat
().
mtime
;
const
m2Mtime
=
model2
.
getStat
().
mtime
;
assert
.
ok
(
m1Mtime
>
0
);
assert
.
ok
(
m2Mtime
>
0
);
assert
.
ok
(
accessor
.
textFileService
.
isDirty
());
assert
.
ok
(
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async2.txt
'
)));
assert
.
ok
(
!
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async.txt
'
)));
model2
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async.txt
'
)));
return
timeout
(
10
).
then
(()
=>
{
accessor
.
textFileService
.
saveAll
().
then
(()
=>
{
assert
.
ok
(
!
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async.txt
'
)));
assert
.
ok
(
!
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async2.txt
'
)));
assert
.
ok
(
model1
.
getStat
().
mtime
>
m1Mtime
);
assert
.
ok
(
model2
.
getStat
().
mtime
>
m2Mtime
);
assert
.
ok
(
model1
.
getLastSaveAttemptTime
()
>
m1Mtime
);
assert
.
ok
(
model2
.
getLastSaveAttemptTime
()
>
m2Mtime
);
model1
.
dispose
();
model2
.
dispose
();
});
});
});
});
const
model1
=
await
input1
.
resolve
()
as
TextFileEditorModel
;
const
model2
=
await
input2
.
resolve
()
as
TextFileEditorModel
;
model1
.
textEditorModel
!
.
setValue
(
'
foo
'
);
const
m1Mtime
=
model1
.
getStat
().
mtime
;
const
m2Mtime
=
model2
.
getStat
().
mtime
;
assert
.
ok
(
m1Mtime
>
0
);
assert
.
ok
(
m2Mtime
>
0
);
assert
.
ok
(
accessor
.
textFileService
.
isDirty
());
assert
.
ok
(
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async2.txt
'
)));
assert
.
ok
(
!
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async.txt
'
)));
model2
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async.txt
'
)));
await
timeout
(
10
);
await
accessor
.
textFileService
.
saveAll
();
assert
.
ok
(
!
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async.txt
'
)));
assert
.
ok
(
!
accessor
.
textFileService
.
isDirty
(
toResource
.
call
(
this
,
'
/path/index_async2.txt
'
)));
assert
.
ok
(
model1
.
getStat
().
mtime
>
m1Mtime
);
assert
.
ok
(
model2
.
getStat
().
mtime
>
m2Mtime
);
assert
.
ok
(
model1
.
getLastSaveAttemptTime
()
>
m1Mtime
);
assert
.
ok
(
model2
.
getLastSaveAttemptTime
()
>
m2Mtime
);
model1
.
dispose
();
model2
.
dispose
();
});
test
(
'
Save Participant
'
,
function
()
{
test
(
'
Save Participant
'
,
async
function
()
{
let
eventCounter
=
0
;
const
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
...
...
@@ -280,18 +256,15 @@ suite('Files - TextFileEditorModel', () => {
}
});
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
return
model
.
save
().
then
(()
=>
{
model
.
dispose
();
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
eventCounter
,
2
);
}
);
}
);
await
model
.
save
(
);
model
.
dispose
(
);
assert
.
equal
(
eventCounter
,
2
);
});
test
(
'
Save Participant, async participant
'
,
function
()
{
test
(
'
Save Participant, async participant
'
,
async
function
()
{
const
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
...
...
@@ -301,18 +274,16 @@ suite('Files - TextFileEditorModel', () => {
}
});
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
const
now
=
Date
.
now
();
return
model
.
save
().
then
(()
=>
{
assert
.
ok
(
Date
.
now
()
-
now
>=
10
);
model
.
dispose
();
});
});
const
now
=
Date
.
now
();
await
model
.
save
();
assert
.
ok
(
Date
.
now
()
-
now
>=
10
);
model
.
dispose
();
});
test
(
'
Save Participant, bad participant
'
,
function
()
{
test
(
'
Save Participant, bad participant
'
,
async
function
()
{
const
model
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/index_async.txt
'
),
'
utf8
'
);
TextFileEditorModel
.
setSaveParticipant
({
...
...
@@ -321,15 +292,14 @@ suite('Files - TextFileEditorModel', () => {
}
});
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
return
model
.
save
().
then
(()
=>
{
model
.
dispose
();
});
});
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
await
model
.
save
();
model
.
dispose
();
});
test
(
'
SaveSequentializer - pending basics
'
,
function
()
{
test
(
'
SaveSequentializer - pending basics
'
,
async
function
()
{
const
sequentializer
=
new
SaveSequentializer
();
assert
.
ok
(
!
sequentializer
.
hasPendingSave
());
...
...
@@ -337,27 +307,25 @@ suite('Files - TextFileEditorModel', () => {
assert
.
ok
(
!
sequentializer
.
pendingSave
);
// pending removes itself after done
return
sequentializer
.
setPending
(
1
,
Promise
.
resolve
()).
then
(()
=>
{
assert
.
ok
(
!
sequentializer
.
hasPendingSave
());
assert
.
ok
(
!
sequentializer
.
hasPendingSave
(
1
));
assert
.
ok
(
!
sequentializer
.
pendingSave
);
// pending removes itself after done (use timeout)
sequentializer
.
setPending
(
2
,
timeout
(
1
));
assert
.
ok
(
sequentializer
.
hasPendingSave
());
assert
.
ok
(
sequentializer
.
hasPendingSave
(
2
));
assert
.
ok
(
!
sequentializer
.
hasPendingSave
(
1
));
assert
.
ok
(
sequentializer
.
pendingSave
);
return
timeout
(
2
).
then
(()
=>
{
assert
.
ok
(
!
sequentializer
.
hasPendingSave
());
assert
.
ok
(
!
sequentializer
.
hasPendingSave
(
2
));
assert
.
ok
(
!
sequentializer
.
pendingSave
);
});
});
await
sequentializer
.
setPending
(
1
,
Promise
.
resolve
());
assert
.
ok
(
!
sequentializer
.
hasPendingSave
());
assert
.
ok
(
!
sequentializer
.
hasPendingSave
(
1
));
assert
.
ok
(
!
sequentializer
.
pendingSave
);
// pending removes itself after done (use timeout)
sequentializer
.
setPending
(
2
,
timeout
(
1
));
assert
.
ok
(
sequentializer
.
hasPendingSave
());
assert
.
ok
(
sequentializer
.
hasPendingSave
(
2
));
assert
.
ok
(
!
sequentializer
.
hasPendingSave
(
1
));
assert
.
ok
(
sequentializer
.
pendingSave
);
await
timeout
(
2
);
assert
.
ok
(
!
sequentializer
.
hasPendingSave
());
assert
.
ok
(
!
sequentializer
.
hasPendingSave
(
2
));
assert
.
ok
(
!
sequentializer
.
pendingSave
);
});
test
(
'
SaveSequentializer - pending and next (finishes instantly)
'
,
function
()
{
test
(
'
SaveSequentializer - pending and next (finishes instantly)
'
,
async
function
()
{
const
sequentializer
=
new
SaveSequentializer
();
let
pendingDone
=
false
;
...
...
@@ -367,13 +335,12 @@ suite('Files - TextFileEditorModel', () => {
let
nextDone
=
false
;
const
res
=
sequentializer
.
setNext
(()
=>
Promise
.
resolve
(
null
).
then
(()
=>
{
nextDone
=
true
;
return
;
}));
return
res
.
then
(()
=>
{
assert
.
ok
(
pendingDone
);
assert
.
ok
(
nextDone
);
});
await
res
;
assert
.
ok
(
pendingDone
);
assert
.
ok
(
nextDone
);
});
test
(
'
SaveSequentializer - pending and next (finishes after timeout)
'
,
function
()
{
test
(
'
SaveSequentializer - pending and next (finishes after timeout)
'
,
async
function
()
{
const
sequentializer
=
new
SaveSequentializer
();
let
pendingDone
=
false
;
...
...
@@ -383,13 +350,12 @@ suite('Files - TextFileEditorModel', () => {
let
nextDone
=
false
;
const
res
=
sequentializer
.
setNext
(()
=>
timeout
(
1
).
then
(()
=>
{
nextDone
=
true
;
return
;
}));
return
res
.
then
(()
=>
{
assert
.
ok
(
pendingDone
);
assert
.
ok
(
nextDone
);
});
await
res
;
assert
.
ok
(
pendingDone
);
assert
.
ok
(
nextDone
);
});
test
(
'
SaveSequentializer - pending and multiple next (last one wins)
'
,
function
()
{
test
(
'
SaveSequentializer - pending and multiple next (last one wins)
'
,
async
function
()
{
const
sequentializer
=
new
SaveSequentializer
();
let
pendingDone
=
false
;
...
...
@@ -405,11 +371,10 @@ suite('Files - TextFileEditorModel', () => {
let
thirdDone
=
false
;
let
thirdRes
=
sequentializer
.
setNext
(()
=>
timeout
(
4
).
then
(()
=>
{
thirdDone
=
true
;
return
;
}));
return
Promise
.
all
([
firstRes
,
secondRes
,
thirdRes
]).
then
(()
=>
{
assert
.
ok
(
pendingDone
);
assert
.
ok
(
!
firstDone
);
assert
.
ok
(
!
secondDone
);
assert
.
ok
(
thirdDone
);
});
await
Promise
.
all
([
firstRes
,
secondRes
,
thirdRes
]);
assert
.
ok
(
pendingDone
);
assert
.
ok
(
!
firstDone
);
assert
.
ok
(
!
secondDone
);
assert
.
ok
(
thirdDone
);
});
});
src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts
浏览文件 @
782fadfe
...
...
@@ -94,29 +94,24 @@ suite('Files - TextFileEditorModelManager', () => {
model3
.
dispose
();
});
test
(
'
loadOrCreate
'
,
()
=>
{
test
(
'
loadOrCreate
'
,
async
()
=>
{
const
manager
:
TestTextFileEditorModelManager
=
instantiationService
.
createInstance
(
TestTextFileEditorModelManager
);
const
resource
=
URI
.
file
(
'
/test.html
'
);
const
encoding
=
'
utf8
'
;
return
manager
.
loadOrCreate
(
resource
,
{
encoding
}).
then
(
model
=>
{
assert
.
ok
(
model
);
assert
.
equal
(
model
.
getEncoding
(),
encoding
);
assert
.
equal
(
manager
.
get
(
resource
),
model
);
const
model
=
await
manager
.
loadOrCreate
(
resource
,
{
encoding
});
assert
.
ok
(
model
);
assert
.
equal
(
model
.
getEncoding
(),
encoding
);
assert
.
equal
(
manager
.
get
(
resource
),
model
);
return
manager
.
loadOrCreate
(
resource
,
{
encoding
}).
then
(
model2
=>
{
assert
.
equal
(
model2
,
model
);
const
model2
=
await
manager
.
loadOrCreate
(
resource
,
{
encoding
});
assert
.
equal
(
model2
,
model
);
model
.
dispose
();
model
.
dispose
();
return
manager
.
loadOrCreate
(
resource
,
{
encoding
}).
then
(
model3
=>
{
assert
.
notEqual
(
model3
,
model2
);
assert
.
equal
(
manager
.
get
(
resource
),
model3
);
model3
.
dispose
();
});
});
});
const
model3
=
await
manager
.
loadOrCreate
(
resource
,
{
encoding
});
assert
.
notEqual
(
model3
,
model2
);
assert
.
equal
(
manager
.
get
(
resource
),
model3
);
model3
.
dispose
();
});
test
(
'
removed from cache when model disposed
'
,
function
()
{
...
...
@@ -139,7 +134,7 @@ suite('Files - TextFileEditorModelManager', () => {
model3
.
dispose
();
});
test
(
'
events
'
,
function
()
{
test
(
'
events
'
,
async
function
()
{
TextFileEditorModel
.
DEFAULT_CONTENT_CHANGE_BUFFER_DELAY
=
0
;
TextFileEditorModel
.
DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY
=
0
;
...
...
@@ -189,46 +184,37 @@ suite('Files - TextFileEditorModelManager', () => {
disposeCounter
++
;
});
return
manager
.
loadOrCreate
(
resource1
,
{
encoding
:
'
utf8
'
}).
then
(
model1
=>
{
accessor
.
fileService
.
fireFileChanges
(
new
FileChangesEvent
([{
resource
:
resource1
,
type
:
FileChangeType
.
DELETED
}]));
accessor
.
fileService
.
fireFileChanges
(
new
FileChangesEvent
([{
resource
:
resource1
,
type
:
FileChangeType
.
ADDED
}]));
return
manager
.
loadOrCreate
(
resource2
,
{
encoding
:
'
utf8
'
}).
then
(
model2
=>
{
model1
.
textEditorModel
!
.
setValue
(
'
changed
'
);
model1
.
updatePreferredEncoding
(
'
utf16
'
);
return
model1
.
revert
().
then
(()
=>
{
model1
.
textEditorModel
!
.
setValue
(
'
changed again
'
);
return
model1
.
save
().
then
(()
=>
{
model1
.
dispose
();
model2
.
dispose
();
assert
.
equal
(
disposeCounter
,
2
);
return
model1
.
revert
().
then
(()
=>
{
// should not trigger another event if disposed
assert
.
equal
(
dirtyCounter
,
2
);
assert
.
equal
(
revertedCounter
,
1
);
assert
.
equal
(
savedCounter
,
1
);
assert
.
equal
(
encodingCounter
,
2
);
// content change event if done async
return
timeout
(
10
).
then
(()
=>
{
assert
.
equal
(
contentCounter
,
2
);
model1
.
dispose
();
model2
.
dispose
();
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
resource1
));
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
resource2
));
});
});
});
});
});
});
const
model1
=
await
manager
.
loadOrCreate
(
resource1
,
{
encoding
:
'
utf8
'
});
accessor
.
fileService
.
fireFileChanges
(
new
FileChangesEvent
([{
resource
:
resource1
,
type
:
FileChangeType
.
DELETED
}]));
accessor
.
fileService
.
fireFileChanges
(
new
FileChangesEvent
([{
resource
:
resource1
,
type
:
FileChangeType
.
ADDED
}]));
const
model2
=
await
manager
.
loadOrCreate
(
resource2
,
{
encoding
:
'
utf8
'
});
model1
.
textEditorModel
!
.
setValue
(
'
changed
'
);
model1
.
updatePreferredEncoding
(
'
utf16
'
);
await
model1
.
revert
();
model1
.
textEditorModel
!
.
setValue
(
'
changed again
'
);
await
model1
.
save
();
model1
.
dispose
();
model2
.
dispose
();
assert
.
equal
(
disposeCounter
,
2
);
await
model1
.
revert
();
assert
.
equal
(
dirtyCounter
,
2
);
assert
.
equal
(
revertedCounter
,
1
);
assert
.
equal
(
savedCounter
,
1
);
assert
.
equal
(
encodingCounter
,
2
);
await
timeout
(
10
);
assert
.
equal
(
contentCounter
,
2
);
model1
.
dispose
();
model2
.
dispose
();
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
resource1
));
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
resource2
));
});
test
(
'
events debounced
'
,
function
()
{
test
(
'
events debounced
'
,
async
function
()
{
const
manager
:
TestTextFileEditorModelManager
=
instantiationService
.
createInstance
(
TestTextFileEditorModelManager
);
const
resource1
=
toResource
.
call
(
this
,
'
/path/index.txt
'
);
...
...
@@ -255,69 +241,53 @@ suite('Files - TextFileEditorModelManager', () => {
assert
.
equal
(
e
[
0
].
resource
.
toString
(),
resource1
.
toString
());
});
return
manager
.
loadOrCreate
(
resource1
,
{
encoding
:
'
utf8
'
}).
then
(
model1
=>
{
return
manager
.
loadOrCreate
(
resource2
,
{
encoding
:
'
utf8
'
}).
then
(
model2
=>
{
model1
.
textEditorModel
!
.
setValue
(
'
changed
'
);
model1
.
updatePreferredEncoding
(
'
utf16
'
);
return
model1
.
revert
().
then
(()
=>
{
model1
.
textEditorModel
!
.
setValue
(
'
changed again
'
);
return
model1
.
save
().
then
(()
=>
{
model1
.
dispose
();
model2
.
dispose
();
return
model1
.
revert
().
then
(()
=>
{
// should not trigger another event if disposed
return
timeout
(
20
).
then
(()
=>
{
assert
.
equal
(
dirtyCounter
,
2
);
assert
.
equal
(
revertedCounter
,
1
);
assert
.
equal
(
savedCounter
,
1
);
model1
.
dispose
();
model2
.
dispose
();
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
resource1
));
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
resource2
));
});
});
});
});
});
});
});
test
(
'
disposing model takes it out of the manager
'
,
function
()
{
const
manager
:
TestTextFileEditorModelManager
=
instantiationService
.
createInstance
(
TestTextFileEditorModelManager
);
const
resource
=
toResource
.
call
(
this
,
'
/path/index_something.txt
'
);
const
model1
=
await
manager
.
loadOrCreate
(
resource1
,
{
encoding
:
'
utf8
'
});
const
model2
=
await
manager
.
loadOrCreate
(
resource2
,
{
encoding
:
'
utf8
'
});
model1
.
textEditorModel
!
.
setValue
(
'
changed
'
);
model1
.
updatePreferredEncoding
(
'
utf16
'
);
return
manager
.
loadOrCreate
(
resource
,
{
encoding
:
'
utf8
'
}).
then
(
model
=>
{
model
.
dispose
(
);
await
model1
.
revert
();
model1
.
textEditorModel
!
.
setValue
(
'
changed again
'
);
assert
.
ok
(
!
manager
.
get
(
resource
));
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
model
.
getResource
()));
await
model1
.
save
();
model1
.
dispose
();
model2
.
dispose
();
manager
.
dispose
();
});
await
model1
.
revert
();
await
timeout
(
20
);
assert
.
equal
(
dirtyCounter
,
2
);
assert
.
equal
(
revertedCounter
,
1
);
assert
.
equal
(
savedCounter
,
1
);
model1
.
dispose
();
model2
.
dispose
();
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
resource1
));
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
resource2
));
});
test
(
'
dispos
e prevents dirty model from getting disposed
'
,
function
()
{
test
(
'
dispos
ing model takes it out of the manager
'
,
async
function
()
{
const
manager
:
TestTextFileEditorModelManager
=
instantiationService
.
createInstance
(
TestTextFileEditorModelManager
);
const
resource
=
toResource
.
call
(
this
,
'
/path/index_something.txt
'
);
return
manager
.
loadOrCreate
(
resource
,
{
encoding
:
'
utf8
'
}).
then
(
model
=>
{
model
.
textEditorModel
!
.
setValue
(
'
make dirty
'
);
manager
.
disposeModel
(
model
as
TextFileEditorModel
);
assert
.
ok
(
!
model
.
isDisposed
());
const
model
=
await
manager
.
loadOrCreate
(
resource
,
{
encoding
:
'
utf8
'
});
model
.
dispose
();
assert
.
ok
(
!
manager
.
get
(
resource
));
assert
.
ok
(
!
accessor
.
modelService
.
getModel
(
model
.
getResource
()));
manager
.
dispose
();
});
model
.
revert
(
true
);
test
(
'
dispose prevents dirty model from getting disposed
'
,
async
function
()
{
const
manager
:
TestTextFileEditorModelManager
=
instantiationService
.
createInstance
(
TestTextFileEditorModelManager
);
manager
.
disposeModel
(
model
as
TextFileEditorModel
);
assert
.
ok
(
model
.
isDisposed
());
const
resource
=
toResource
.
call
(
this
,
'
/path/index_something.txt
'
);
manager
.
dispose
();
});
const
model
=
await
manager
.
loadOrCreate
(
resource
,
{
encoding
:
'
utf8
'
});
model
.
textEditorModel
!
.
setValue
(
'
make dirty
'
);
manager
.
disposeModel
((
model
as
TextFileEditorModel
));
assert
.
ok
(
!
model
.
isDisposed
());
model
.
revert
(
true
);
manager
.
disposeModel
((
model
as
TextFileEditorModel
));
assert
.
ok
(
model
.
isDisposed
());
manager
.
dispose
();
});
});
\ No newline at end of file
src/vs/workbench/services/textfile/test/textFileService.io.test.ts
0 → 100644
浏览文件 @
782fadfe
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
*
as
assert
from
'
assert
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
ILifecycleService
}
from
'
vs/platform/lifecycle/common/lifecycle
'
;
import
{
workbenchInstantiationService
,
TestLifecycleService
,
TestTextFileService
,
TestWindowsService
,
TestContextService
,
TestFileService
,
TestEnvironmentService
,
TestTextResourceConfigurationService
}
from
'
vs/workbench/test/workbenchTestServices
'
;
import
{
IWindowsService
}
from
'
vs/platform/windows/common/windows
'
;
import
{
ITextFileService
}
from
'
vs/workbench/services/textfile/common/textfiles
'
;
import
{
IUntitledEditorService
}
from
'
vs/workbench/services/untitled/common/untitledEditorService
'
;
import
{
IFileService
,
ITextSnapshot
,
snapshotToString
}
from
'
vs/platform/files/common/files
'
;
import
{
TextFileEditorModelManager
}
from
'
vs/workbench/services/textfile/common/textFileEditorModelManager
'
;
import
{
IWorkspaceContextService
}
from
'
vs/platform/workspace/common/workspace
'
;
import
{
IModelService
}
from
'
vs/editor/common/services/modelService
'
;
import
{
ModelServiceImpl
}
from
'
vs/editor/common/services/modelServiceImpl
'
;
import
{
Schemas
}
from
'
vs/base/common/network
'
;
import
{
ServiceCollection
}
from
'
vs/platform/instantiation/common/serviceCollection
'
;
import
{
rimraf
,
RimRafMode
,
copy
,
readFile
,
exists
}
from
'
vs/base/node/pfs
'
;
import
{
dispose
,
IDisposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
FileService2
}
from
'
vs/workbench/services/files2/common/fileService2
'
;
import
{
NullLogService
}
from
'
vs/platform/log/common/log
'
;
import
{
getRandomTestPath
}
from
'
vs/base/test/node/testUtils
'
;
import
{
tmpdir
}
from
'
os
'
;
import
{
DiskFileSystemProvider
}
from
'
vs/workbench/services/files2/node/diskFileSystemProvider
'
;
import
{
generateUuid
}
from
'
vs/base/common/uuid
'
;
import
{
join
}
from
'
vs/base/common/path
'
;
import
{
getPathFromAmdModule
}
from
'
vs/base/common/amd
'
;
import
{
detectEncodingByBOM
,
UTF16be
,
UTF16le
,
UTF8_with_bom
,
UTF8
}
from
'
vs/base/node/encoding
'
;
import
{
NodeTextFileService
,
EncodingOracle
}
from
'
vs/workbench/services/textfile/node/textFileService
'
;
import
{
LegacyFileService
}
from
'
vs/workbench/services/files/node/fileService
'
;
import
{
DefaultEndOfLine
}
from
'
vs/editor/common/model
'
;
import
{
TextModel
}
from
'
vs/editor/common/model/textModel
'
;
class
ServiceAccessor
{
constructor
(
@
ILifecycleService
public
lifecycleService
:
TestLifecycleService
,
@
ITextFileService
public
textFileService
:
TestTextFileService
,
@
IUntitledEditorService
public
untitledEditorService
:
IUntitledEditorService
,
@
IWindowsService
public
windowsService
:
TestWindowsService
,
@
IWorkspaceContextService
public
contextService
:
TestContextService
,
@
IModelService
public
modelService
:
ModelServiceImpl
,
@
IFileService
public
fileService
:
TestFileService
)
{
}
}
class
TestNodeTextFileService
extends
NodeTextFileService
{
private
_testEncoding
:
EncodingOracle
;
protected
get
encoding
():
EncodingOracle
{
if
(
!
this
.
_testEncoding
)
{
this
.
_testEncoding
=
this
.
_register
(
this
.
instantiationService
.
createInstance
(
EncodingOracle
,
[
{
extension
:
'
utf16le
'
,
encoding
:
UTF16le
},
{
extension
:
'
utf8bom
'
,
encoding
:
UTF8_with_bom
}
]));
}
return
this
.
_testEncoding
;
}
}
suite
(
'
Files - TextFileService i/o
'
,
()
=>
{
const
parentDir
=
getRandomTestPath
(
tmpdir
(),
'
vsctests
'
,
'
textfileservice
'
);
let
accessor
:
ServiceAccessor
;
let
disposables
:
IDisposable
[]
=
[];
let
service
:
ITextFileService
;
let
testDir
:
string
;
setup
(
async
()
=>
{
const
instantiationService
=
workbenchInstantiationService
();
accessor
=
instantiationService
.
createInstance
(
ServiceAccessor
);
const
logService
=
new
NullLogService
();
const
fileService
=
new
FileService2
(
logService
);
const
fileProvider
=
new
DiskFileSystemProvider
(
logService
);
disposables
.
push
(
fileService
.
registerProvider
(
Schemas
.
file
,
fileProvider
));
disposables
.
push
(
fileProvider
);
fileService
.
setLegacyService
(
new
LegacyFileService
(
fileService
,
accessor
.
contextService
,
TestEnvironmentService
,
new
TestTextResourceConfigurationService
()
));
const
collection
=
new
ServiceCollection
();
collection
.
set
(
IFileService
,
fileService
);
service
=
instantiationService
.
createChild
(
collection
).
createInstance
(
TestNodeTextFileService
);
const
id
=
generateUuid
();
testDir
=
join
(
parentDir
,
id
);
const
sourceDir
=
getPathFromAmdModule
(
require
,
'
./fixtures
'
);
await
copy
(
sourceDir
,
testDir
);
});
teardown
(
async
()
=>
{
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
clear
();
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
dispose
();
accessor
.
untitledEditorService
.
revertAll
();
disposables
=
dispose
(
disposables
);
await
rimraf
(
parentDir
,
RimRafMode
.
MOVE
);
});
test
(
'
create - no encoding - content empty
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
small_new.txt
'
));
await
service
.
create
(
resource
);
assert
.
equal
(
await
exists
(
resource
.
fsPath
),
true
);
});
test
(
'
create - no encoding - content provided
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
small_new.txt
'
));
await
service
.
create
(
resource
,
'
Hello World
'
);
assert
.
equal
(
await
exists
(
resource
.
fsPath
),
true
);
assert
.
equal
((
await
readFile
(
resource
.
fsPath
)).
toString
(),
'
Hello World
'
);
});
test
(
'
create - UTF 16 LE - no content
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
small_new.utf16le
'
));
await
service
.
create
(
resource
);
assert
.
equal
(
await
exists
(
resource
.
fsPath
),
true
);
const
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF16le
);
});
test
(
'
create - UTF 16 LE - content provided
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
small_new.utf16le
'
));
await
service
.
create
(
resource
,
'
Hello World
'
);
assert
.
equal
(
await
exists
(
resource
.
fsPath
),
true
);
const
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF16le
);
});
test
(
'
create - UTF 8 BOM - no content
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
small_new.utf8bom
'
));
await
service
.
create
(
resource
);
assert
.
equal
(
await
exists
(
resource
.
fsPath
),
true
);
const
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF8
);
});
test
(
'
create - UTF 8 BOM - content provided
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
small_new.utf8bom
'
));
await
service
.
create
(
resource
,
'
Hello World
'
);
assert
.
equal
(
await
exists
(
resource
.
fsPath
),
true
);
const
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF8
);
});
test
(
'
write - use encoding (UTF 16 BE) - small content as string
'
,
async
()
=>
{
await
testEncoding
(
URI
.
file
(
join
(
testDir
,
'
small.txt
'
)),
UTF16be
,
'
Hello
\n
World
'
,
'
Hello
\n
World
'
);
});
test
(
'
write - use encoding (UTF 16 BE) - small content as snapshot
'
,
async
()
=>
{
await
testEncoding
(
URI
.
file
(
join
(
testDir
,
'
small.txt
'
)),
UTF16be
,
TextModel
.
createFromString
(
'
Hello
\n
World
'
).
createSnapshot
(),
'
Hello
\n
World
'
);
});
test
(
'
write - use encoding (UTF 16 BE) - large content as string
'
,
async
()
=>
{
await
testEncoding
(
URI
.
file
(
join
(
testDir
,
'
lorem.txt
'
)),
UTF16be
,
'
Hello
\n
World
'
,
'
Hello
\n
World
'
);
});
test
(
'
write - use encoding (UTF 16 BE) - large content as snapshot
'
,
async
()
=>
{
await
testEncoding
(
URI
.
file
(
join
(
testDir
,
'
lorem.txt
'
)),
UTF16be
,
TextModel
.
createFromString
(
'
Hello
\n
World
'
).
createSnapshot
(),
'
Hello
\n
World
'
);
});
async
function
testEncoding
(
resource
:
URI
,
encoding
:
string
,
content
:
string
|
ITextSnapshot
,
expectedContent
:
string
)
{
await
service
.
write
(
resource
,
content
,
{
encoding
});
const
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
encoding
);
const
resolved
=
await
service
.
resolve
(
resource
);
assert
.
equal
(
resolved
.
encoding
,
encoding
);
assert
.
equal
(
snapshotToString
(
resolved
.
value
.
create
(
DefaultEndOfLine
.
LF
).
createSnapshot
(
false
)),
expectedContent
);
}
test
(
'
write - no encoding - content as string
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
small.txt
'
));
const
content
=
(
await
readFile
(
resource
.
fsPath
)).
toString
();
await
service
.
write
(
resource
,
content
);
const
resolved
=
await
service
.
resolve
(
resource
);
assert
.
equal
(
snapshotToString
(
resolved
.
value
.
create
(
DefaultEndOfLine
.
LF
).
createSnapshot
(
false
)),
content
);
});
test
(
'
write - no encoding - content as snapshot
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
small.txt
'
));
const
content
=
(
await
readFile
(
resource
.
fsPath
)).
toString
();
await
service
.
write
(
resource
,
TextModel
.
createFromString
(
content
).
createSnapshot
());
const
resolved
=
await
service
.
resolve
(
resource
);
assert
.
equal
(
snapshotToString
(
resolved
.
value
.
create
(
DefaultEndOfLine
.
LF
).
createSnapshot
(
false
)),
content
);
});
test
(
'
write - encoding preserved (UTF 16 LE) - content as string
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
some_utf16le.css
'
));
const
resolved
=
await
service
.
resolve
(
resource
);
assert
.
equal
(
resolved
.
encoding
,
UTF16le
);
await
testEncoding
(
URI
.
file
(
join
(
testDir
,
'
some_utf16le.css
'
)),
UTF16le
,
'
Hello
\n
World
'
,
'
Hello
\n
World
'
);
});
test
(
'
write - encoding preserved (UTF 16 LE) - content as snapshot
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
some_utf16le.css
'
));
const
resolved
=
await
service
.
resolve
(
resource
);
assert
.
equal
(
resolved
.
encoding
,
UTF16le
);
await
testEncoding
(
URI
.
file
(
join
(
testDir
,
'
some_utf16le.css
'
)),
UTF16le
,
TextModel
.
createFromString
(
'
Hello
\n
World
'
).
createSnapshot
(),
'
Hello
\n
World
'
);
});
test
(
'
write - UTF8 variations - content as string
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
index.html
'
));
let
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
null
);
const
content
=
(
await
readFile
(
resource
.
fsPath
)).
toString
()
+
'
updates
'
;
await
service
.
write
(
resource
,
content
,
{
encoding
:
UTF8_with_bom
});
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF8
);
// ensure BOM preserved
await
service
.
write
(
resource
,
content
,
{
encoding
:
UTF8
});
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF8
);
// allow to remove BOM
await
service
.
write
(
resource
,
content
,
{
encoding
:
UTF8
,
overwriteEncoding
:
true
});
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
null
);
// BOM does not come back
await
service
.
write
(
resource
,
content
,
{
encoding
:
UTF8
});
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
null
);
});
test
(
'
write - UTF8 variations - content as snapshot
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
index.html
'
));
let
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
null
);
const
model
=
TextModel
.
createFromString
((
await
readFile
(
resource
.
fsPath
)).
toString
()
+
'
updates
'
);
await
service
.
write
(
resource
,
model
.
createSnapshot
(),
{
encoding
:
UTF8_with_bom
});
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF8
);
// ensure BOM preserved
await
service
.
write
(
resource
,
model
.
createSnapshot
(),
{
encoding
:
UTF8
});
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF8
);
// allow to remove BOM
await
service
.
write
(
resource
,
model
.
createSnapshot
(),
{
encoding
:
UTF8
,
overwriteEncoding
:
true
});
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
null
);
// BOM does not come back
await
service
.
write
(
resource
,
model
.
createSnapshot
(),
{
encoding
:
UTF8
});
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
null
);
});
test
(
'
write - preserve UTF8 BOM - content as string
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
some_utf8_bom.txt
'
));
let
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF8
);
await
service
.
write
(
resource
,
'
Hello World
'
);
detectedEncoding
=
await
detectEncodingByBOM
(
resource
.
fsPath
);
assert
.
equal
(
detectedEncoding
,
UTF8
);
});
test
(
'
write - CP1252 - content as string
'
,
async
()
=>
{
const
resource
=
URI
.
file
(
join
(
testDir
,
'
small_umlaut.txt
'
));
const
content
=
(
await
readFile
(
resource
.
fsPath
)).
toString
();
await
service
.
write
(
resource
,
content
,
{
encoding
:
'
cp1252
'
});
const
resolved
=
await
service
.
resolve
(
resource
,
{
encoding
:
'
cp1252
'
});
assert
.
equal
(
snapshotToString
(
resolved
.
value
.
create
(
DefaultEndOfLine
.
LF
).
createSnapshot
(
false
)),
content
);
});
});
src/vs/workbench/services/textfile/test/textFileService.test.ts
浏览文件 @
782fadfe
...
...
@@ -15,7 +15,6 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF
import
{
ITextFileService
}
from
'
vs/workbench/services/textfile/common/textfiles
'
;
import
{
ConfirmResult
}
from
'
vs/workbench/common/editor
'
;
import
{
IUntitledEditorService
}
from
'
vs/workbench/services/untitled/common/untitledEditorService
'
;
import
{
UntitledEditorModel
}
from
'
vs/workbench/common/editor/untitledEditorModel
'
;
import
{
HotExitConfiguration
,
IFileService
}
from
'
vs/platform/files/common/files
'
;
import
{
TextFileEditorModelManager
}
from
'
vs/workbench/services/textfile/common/textFileEditorModelManager
'
;
import
{
IWorkspaceContextService
,
Workspace
}
from
'
vs/platform/workspace/common/workspace
'
;
...
...
@@ -83,26 +82,23 @@ suite('Files - TextFileService', () => {
}
});
test
(
'
confirm onWillShutdown - veto if user cancels
'
,
function
()
{
test
(
'
confirm onWillShutdown - veto if user cancels
'
,
async
function
()
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
const
service
=
accessor
.
textFileService
;
service
.
setConfirmResult
(
ConfirmResult
.
CANCEL
);
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
const
event
=
new
BeforeShutdownEventImpl
();
accessor
.
lifecycleService
.
fireWillShutdown
(
event
);
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
assert
.
ok
(
event
.
value
);
});
const
event
=
new
BeforeShutdownEventImpl
();
accessor
.
lifecycleService
.
fireWillShutdown
(
event
);
assert
.
ok
(
event
.
value
);
});
test
(
'
confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)
'
,
function
()
{
test
(
'
confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)
'
,
async
function
()
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
...
...
@@ -110,30 +106,25 @@ suite('Files - TextFileService', () => {
service
.
setConfirmResult
(
ConfirmResult
.
DONT_SAVE
);
service
.
onFilesConfigurationChange
({
files
:
{
hotExit
:
'
off
'
}
});
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
const
event
=
new
BeforeShutdownEventImpl
();
accessor
.
lifecycleService
.
fireWillShutdown
(
event
);
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
const
event
=
new
BeforeShutdownEventImpl
();
accessor
.
lifecycleService
.
fireWillShutdown
(
event
);
const
veto
=
event
.
value
;
if
(
typeof
veto
===
'
boolean
'
)
{
assert
.
ok
(
service
.
cleanupBackupsBeforeShutdownCalled
);
assert
.
ok
(
!
veto
);
let
veto
=
event
.
value
;
if
(
typeof
veto
===
'
boolean
'
)
{
assert
.
ok
(
service
.
cleanupBackupsBeforeShutdownCalled
);
assert
.
ok
(
!
veto
);
return
;
}
return
undefined
;
}
else
{
return
veto
.
then
(
veto
=>
{
assert
.
ok
(
service
.
cleanupBackupsBeforeShutdownCalled
);
assert
.
ok
(
!
veto
);
});
}
});
veto
=
await
veto
;
assert
.
ok
(
service
.
cleanupBackupsBeforeShutdownCalled
);
assert
.
ok
(
!
veto
);
});
test
(
'
confirm onWillShutdown - save (hot.exit: off)
'
,
function
()
{
test
(
'
confirm onWillShutdown - save (hot.exit: off)
'
,
async
function
()
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
...
...
@@ -141,66 +132,60 @@ suite('Files - TextFileService', () => {
service
.
setConfirmResult
(
ConfirmResult
.
SAVE
);
service
.
onFilesConfigurationChange
({
files
:
{
hotExit
:
'
off
'
}
});
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
const
event
=
new
BeforeShutdownEventImpl
();
accessor
.
lifecycleService
.
fireWillShutdown
(
event
);
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
const
event
=
new
BeforeShutdownEventImpl
();
accessor
.
lifecycleService
.
fireWillShutdown
(
event
);
return
(
<
Promise
<
boolean
>>
event
.
value
).
then
(
veto
=>
{
assert
.
ok
(
!
veto
);
assert
.
ok
(
!
model
.
isDirty
());
});
});
const
veto
=
await
(
<
Promise
<
boolean
>>
event
.
value
);
assert
.
ok
(
!
veto
);
assert
.
ok
(
!
model
.
isDirty
());
});
test
(
'
isDirty/getDirty - files and untitled
'
,
function
()
{
test
(
'
isDirty/getDirty - files and untitled
'
,
async
function
()
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
const
service
=
accessor
.
textFileService
;
return
model
.
load
().
then
(()
=>
{
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
assert
.
equal
(
service
.
getDirty
([
model
.
getResource
()])[
0
].
toString
(),
model
.
getResource
().
toString
());
await
model
.
load
();
const
untitled
=
accessor
.
untitledEditorService
.
createOrGet
();
return
untitled
.
resolve
().
then
((
model
:
UntitledEditorModel
)
=>
{
assert
.
ok
(
!
service
.
isDirty
(
untitled
.
getResource
()));
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
model
.
textEditorModel
!
.
setValue
(
'
changed
'
);
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
untitled
.
getResource
()));
assert
.
equal
(
service
.
getDirty
().
length
,
2
);
assert
.
equal
(
service
.
getDirty
([
untitled
.
getResource
()])[
0
].
toString
(),
untitled
.
getResource
().
toString
());
});
});
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
assert
.
equal
(
service
.
getDirty
([
model
.
getResource
()])[
0
].
toString
(),
model
.
getResource
().
toString
());
const
untitled
=
accessor
.
untitledEditorService
.
createOrGet
();
const
untitledModel
=
await
untitled
.
resolve
();
assert
.
ok
(
!
service
.
isDirty
(
untitled
.
getResource
()));
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
untitledModel
.
textEditorModel
!
.
setValue
(
'
changed
'
);
assert
.
ok
(
service
.
isDirty
(
untitled
.
getResource
()));
assert
.
equal
(
service
.
getDirty
().
length
,
2
);
assert
.
equal
(
service
.
getDirty
([
untitled
.
getResource
()])[
0
].
toString
(),
untitled
.
getResource
().
toString
());
});
test
(
'
save - file
'
,
function
()
{
test
(
'
save - file
'
,
async
function
()
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
const
service
=
accessor
.
textFileService
;
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
return
service
.
save
(
model
.
getResource
()).
then
(
res
=>
{
assert
.
ok
(
res
);
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
});
});
const
res
=
await
service
.
save
(
model
.
getResource
());
assert
.
ok
(
res
);
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
});
test
(
'
save - UNC path
'
,
function
()
{
test
(
'
save - UNC path
'
,
async
function
()
{
const
untitledUncUri
=
URI
.
from
({
scheme
:
'
untitled
'
,
authority
:
'
server
'
,
path
:
'
/share/path/file.txt
'
});
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
untitledUncUri
,
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
...
...
@@ -213,97 +198,82 @@ suite('Files - TextFileService', () => {
sinon
.
stub
(
accessor
.
untitledEditorService
,
'
hasAssociatedFilePath
'
,
()
=>
true
);
sinon
.
stub
(
accessor
.
modelService
,
'
updateModel
'
,
()
=>
{
});
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
return
accessor
.
textFileService
.
saveAll
(
true
).
then
(
res
=>
{
assert
.
ok
(
loadOrCreateStub
.
calledOnce
);
assert
.
equal
(
res
.
results
.
length
,
1
);
assert
.
ok
(
res
.
results
[
0
].
success
);
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
res
.
results
[
0
].
target
!
.
scheme
,
Schemas
.
file
);
assert
.
equal
(
res
.
results
[
0
].
target
!
.
authority
,
untitledUncUri
.
authority
);
assert
.
equal
(
res
.
results
[
0
].
target
!
.
path
,
untitledUncUri
.
path
);
});
});
const
res
=
await
accessor
.
textFileService
.
saveAll
(
true
);
assert
.
ok
(
loadOrCreateStub
.
calledOnce
);
assert
.
equal
(
res
.
results
.
length
,
1
);
assert
.
ok
(
res
.
results
[
0
].
success
);
assert
.
equal
(
res
.
results
[
0
].
target
!
.
scheme
,
Schemas
.
file
);
assert
.
equal
(
res
.
results
[
0
].
target
!
.
authority
,
untitledUncUri
.
authority
);
assert
.
equal
(
res
.
results
[
0
].
target
!
.
path
,
untitledUncUri
.
path
);
});
test
(
'
saveAll - file
'
,
function
()
{
test
(
'
saveAll - file
'
,
async
function
()
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
const
service
=
accessor
.
textFileService
;
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
return
service
.
saveAll
([
model
.
getResource
()]).
then
(
res
=>
{
assert
.
ok
(
res
);
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
assert
.
equal
(
res
.
results
.
length
,
1
);
assert
.
equal
(
res
.
results
[
0
].
source
.
toString
(),
model
.
getResource
().
toString
());
});
});
const
res
=
await
service
.
saveAll
([
model
.
getResource
()]);
assert
.
ok
(
res
);
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
assert
.
equal
(
res
.
results
.
length
,
1
);
assert
.
equal
(
res
.
results
[
0
].
source
.
toString
(),
model
.
getResource
().
toString
());
});
test
(
'
saveAs - file
'
,
function
()
{
test
(
'
saveAs - file
'
,
async
function
()
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
const
service
=
accessor
.
textFileService
;
service
.
setPromptPath
(
model
.
getResource
());
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
return
service
.
saveAs
(
model
.
getResource
()).
then
(
res
=>
{
assert
.
equal
(
res
!
.
toString
(),
model
.
getResource
().
toString
());
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
});
});
const
res
=
await
service
.
saveAs
(
model
.
getResource
());
assert
.
equal
(
res
!
.
toString
(),
model
.
getResource
().
toString
());
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
});
test
(
'
revert - file
'
,
function
()
{
test
(
'
revert - file
'
,
async
function
()
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
const
service
=
accessor
.
textFileService
;
service
.
setPromptPath
(
model
.
getResource
());
return
model
.
load
().
then
(()
=>
{
model
!
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
await
model
.
load
();
model
!
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
return
service
.
revert
(
model
.
getResource
()).
then
(
res
=>
{
assert
.
ok
(
res
);
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
});
});
const
res
=
await
service
.
revert
(
model
.
getResource
());
assert
.
ok
(
res
);
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
});
test
(
'
delete - dirty file
'
,
function
()
{
test
(
'
delete - dirty file
'
,
async
function
()
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
const
service
=
accessor
.
textFileService
;
return
model
.
load
().
then
(()
=>
{
model
!
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
await
model
.
load
();
model
!
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
model
.
getResource
()));
return
service
.
delete
(
model
.
getResource
()).
then
(()
=>
{
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
});
});
await
service
.
delete
(
model
.
getResource
());
assert
.
ok
(
!
service
.
isDirty
(
model
.
getResource
()));
});
test
(
'
move - dirty file
'
,
function
()
{
test
(
'
move - dirty file
'
,
async
function
()
{
let
sourceModel
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
let
targetModel
:
TextFileEditorModel
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file_target.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
sourceModel
.
getResource
(),
sourceModel
);
...
...
@@ -311,18 +281,14 @@ suite('Files - TextFileService', () => {
const
service
=
accessor
.
textFileService
;
return
sourceModel
.
load
().
then
(()
=>
{
sourceModel
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
sourceModel
.
getResource
()));
return
service
.
move
(
sourceModel
.
getResource
(),
targetModel
.
getResource
(),
true
).
then
(()
=>
{
assert
.
ok
(
!
service
.
isDirty
(
sourceModel
.
getResource
()));
await
sourceModel
.
load
();
sourceModel
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
ok
(
service
.
isDirty
(
sourceModel
.
getResource
()));
sourceModel
.
dispose
(
);
targetModel
.
dispose
(
);
}
);
}
);
await
service
.
move
(
sourceModel
.
getResource
(),
targetModel
.
getResource
(),
true
);
assert
.
ok
(
!
service
.
isDirty
(
sourceModel
.
getResource
())
);
sourceModel
.
dispose
(
);
targetModel
.
dispose
(
);
});
suite
(
'
Hot Exit
'
,
()
=>
{
...
...
@@ -428,7 +394,7 @@ suite('Files - TextFileService', () => {
});
});
function
hotExitTest
(
this
:
any
,
setting
:
string
,
shutdownReason
:
ShutdownReason
,
multipleWindows
:
boolean
,
workspace
:
true
,
shouldVeto
:
boolean
):
Promise
<
void
>
{
async
function
hotExitTest
(
this
:
any
,
setting
:
string
,
shutdownReason
:
ShutdownReason
,
multipleWindows
:
boolean
,
workspace
:
true
,
shouldVeto
:
boolean
):
Promise
<
void
>
{
model
=
instantiationService
.
createInstance
(
TextFileEditorModel
,
toResource
.
call
(
this
,
'
/path/file.txt
'
),
'
utf8
'
);
(
<
TextFileEditorModelManager
>
accessor
.
textFileService
.
models
).
add
(
model
.
getResource
(),
model
);
...
...
@@ -446,21 +412,16 @@ suite('Files - TextFileService', () => {
// Set cancel to force a veto if hot exit does not trigger
service
.
setConfirmResult
(
ConfirmResult
.
CANCEL
);
return
model
.
load
().
then
(()
=>
{
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
const
event
=
new
BeforeShutdownEventImpl
();
event
.
reason
=
shutdownReason
;
accessor
.
lifecycleService
.
fireWillShutdown
(
event
);
await
model
.
load
();
model
.
textEditorModel
!
.
setValue
(
'
foo
'
);
assert
.
equal
(
service
.
getDirty
().
length
,
1
);
const
event
=
new
BeforeShutdownEventImpl
();
event
.
reason
=
shutdownReason
;
accessor
.
lifecycleService
.
fireWillShutdown
(
event
);
return
(
<
Promise
<
boolean
>>
event
.
value
).
then
(
veto
=>
{
// When hot exit is set, backups should never be cleaned since the confirm result is cancel
assert
.
ok
(
!
service
.
cleanupBackupsBeforeShutdownCalled
);
assert
.
equal
(
veto
,
shouldVeto
);
});
});
const
veto
=
await
(
<
Promise
<
boolean
>>
event
.
value
);
assert
.
ok
(
!
service
.
cleanupBackupsBeforeShutdownCalled
);
// When hot exit is set, backups should never be cleaned since the confirm result is cancel
assert
.
equal
(
veto
,
shouldVeto
);
}
});
});
src/vs/workbench/test/workbenchTestServices.ts
浏览文件 @
782fadfe
...
...
@@ -26,7 +26,7 @@ import { IWorkspaceContextService, IWorkspace as IWorkbenchWorkspace, WorkbenchS
import
{
ILifecycleService
,
BeforeShutdownEvent
,
ShutdownReason
,
StartupKind
,
LifecyclePhase
,
WillShutdownEvent
}
from
'
vs/platform/lifecycle/common/lifecycle
'
;
import
{
ServiceCollection
}
from
'
vs/platform/instantiation/common/serviceCollection
'
;
import
{
TextFileService
}
from
'
vs/workbench/services/textfile/common/textFileService
'
;
import
{
FileOperationEvent
,
IFileService
,
IResolveContentOptions
,
FileOperationError
,
IFileStat
,
IResolveFileResult
,
FileChangesEvent
,
IResolveFileOptions
,
IContent
,
I
WriteTextFileOptions
,
I
StreamContent
,
ICreateFileOptions
,
ITextSnapshot
,
IResourceEncodings
,
IResourceEncoding
,
IFileSystemProvider
,
FileSystemProviderCapabilities
,
IFileChange
,
IWatchOptions
,
IStat
,
FileType
,
FileDeleteOptions
,
FileOverwriteOptions
,
FileWriteOptions
,
FileOpenOptions
,
IFileStatWithMetadata
,
IResolveMetadataFileOptions
,
IWriteFileOptions
}
from
'
vs/platform/files/common/files
'
;
import
{
FileOperationEvent
,
IFileService
,
IResolveContentOptions
,
FileOperationError
,
IFileStat
,
IResolveFileResult
,
FileChangesEvent
,
IResolveFileOptions
,
IContent
,
IStreamContent
,
ICreateFileOptions
,
ITextSnapshot
,
IResourceEncodings
,
IResourceEncoding
,
IFileSystemProvider
,
FileSystemProviderCapabilities
,
IFileChange
,
IWatchOptions
,
IStat
,
FileType
,
FileDeleteOptions
,
FileOverwriteOptions
,
FileWriteOptions
,
FileOpenOptions
,
IFileStatWithMetadata
,
IResolveMetadataFileOptions
,
IWriteFileOptions
}
from
'
vs/platform/files/common/files
'
;
import
{
IModelService
}
from
'
vs/editor/common/services/modelService
'
;
import
{
ModeServiceImpl
}
from
'
vs/editor/common/services/modeServiceImpl
'
;
import
{
ModelServiceImpl
}
from
'
vs/editor/common/services/modelServiceImpl
'
;
...
...
@@ -82,7 +82,7 @@ import { IBadge } from 'vs/workbench/services/activity/common/activity';
import
{
ISharedProcessService
}
from
'
vs/platform/ipc/electron-browser/sharedProcessService
'
;
import
{
IWorkbenchEnvironmentService
}
from
'
vs/workbench/services/environment/common/environmentService
'
;
import
{
WorkbenchEnvironmentService
}
from
'
vs/workbench/services/environment/node/environmentService
'
;
import
{
VSBuffer
}
from
'
vs/base/common/buffer
'
;
import
{
VSBuffer
,
VSBufferReadable
}
from
'
vs/base/common/buffer
'
;
export
function
createFileInput
(
instantiationService
:
IInstantiationService
,
resource
:
URI
):
FileEditorInput
{
return
instantiationService
.
createInstance
(
FileEditorInput
,
resource
,
undefined
);
...
...
@@ -984,7 +984,7 @@ export class TestFileService implements IFileService {
});
}
updateContent
(
resource
:
URI
,
_value
:
string
|
ITextSnapshot
,
_options
?:
IWriteText
FileOptions
):
Promise
<
IFileStatWithMetadata
>
{
writeFile
(
resource
:
URI
,
bufferOrReadable
:
VSBuffer
|
VSBufferReadable
,
options
?:
IWrite
FileOptions
):
Promise
<
IFileStatWithMetadata
>
{
return
timeout
(
0
).
then
(()
=>
({
resource
,
etag
:
'
index.txt
'
,
...
...
@@ -996,10 +996,6 @@ export class TestFileService implements IFileService {
}));
}
writeFile
(
resource
:
URI
,
bufferOrReadable
:
VSBuffer
,
options
?:
IWriteFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
return
this
.
updateContent
(
resource
,
bufferOrReadable
.
toString
(),
options
);
}
move
(
_source
:
URI
,
_target
:
URI
,
_overwrite
?:
boolean
):
Promise
<
IFileStatWithMetadata
>
{
return
Promise
.
resolve
(
null
!
);
}
...
...
@@ -1008,11 +1004,7 @@ export class TestFileService implements IFileService {
throw
new
Error
(
'
not implemented
'
);
}
createFile
(
_resource
:
URI
,
_content
?:
string
,
_options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
throw
new
Error
(
'
not implemented
'
);
}
createFile2
(
_resource
:
URI
,
_content
?:
VSBuffer
,
_options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
createFile
(
_resource
:
URI
,
_content
?:
VSBuffer
|
VSBufferReadable
,
_options
?:
ICreateFileOptions
):
Promise
<
IFileStatWithMetadata
>
{
throw
new
Error
(
'
not implemented
'
);
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录