add nodefs-provider-sample

This commit is contained in:
Benjamin Pasero
2018-04-25 12:59:43 +02:00
parent 80e996b077
commit 97f8f7492b
15 changed files with 501 additions and 0 deletions

4
nodefs-provider-sample/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
out
node_modules
.vscode-test/
*.vsix

View File

@ -0,0 +1,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"eg2.tslint"
]
}

View File

@ -0,0 +1,37 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"${workspaceFolder}/test.code-workspace"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "npm: watch"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "npm: watch"
}
]
}

View File

@ -0,0 +1,9 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
}
}

View File

@ -0,0 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,9 @@
.vscode/**
.vscode-test/**
out/test/**
out/**/*.map
src/**
.gitignore
tsconfig.json
vsc-extension-quickstart.md
tslint.json

View File

@ -0,0 +1,3 @@
# remote-fs
A sample extension that implements a file system provider for the scheme `datei://` using node.js FS APIs.

View File

@ -0,0 +1,38 @@
{
"name": "nodefs-provider-sample",
"displayName": "nodefs-provider-sample",
"description": "",
"version": "0.0.1",
"publisher": "benjpas",
"engines": {
"vscode": "^1.22.0"
},
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"main": "./out/extension",
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run compile && node ./node_modules/vscode/bin/test"
},
"dependencies": {
"mkdirp": "^0.5.1",
"rimraf": "^2.6.2"
},
"devDependencies": {
"@types/mkdirp": "^0.5.2",
"@types/mocha": "^2.2.42",
"@types/node": "^7.0.43",
"@types/promisify-node": "^0.4.0",
"@types/rimraf": "^2.0.2",
"tslint": "^5.8.0",
"typescript": "^2.6.1",
"vscode": "^1.1.6"
}
}

View File

@ -0,0 +1,281 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import * as mkdirp from 'mkdirp';
import * as rimraf from 'rimraf';
export function activate(context: vscode.ExtensionContext) {
vscode.workspace.registerFileSystemProvider('datei', new DateiFileSystemProvider(), {
isCaseSensitive: process.platform === 'linux'
});
}
class DateiFileSystemProvider implements vscode.FileSystemProvider {
private _onDidChangeFile: vscode.EventEmitter<vscode.FileChangeEvent[]>;
constructor() {
this._onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
}
get onDidChangeFile(): vscode.Event<vscode.FileChangeEvent[]> {
return this._onDidChangeFile.event;
}
stat(uri: vscode.Uri, options: {}, token: vscode.CancellationToken): vscode.FileStat | Thenable<vscode.FileStat> {
return this._stat(uri.fsPath);
}
async _stat(path: string): Promise<vscode.FileStat> {
return new FileStat(await _.stat(path));
}
readFile(uri: vscode.Uri, options: vscode.FileOptions, token: vscode.CancellationToken): Uint8Array | Thenable<Uint8Array> {
return _.readfile(uri.fsPath);
}
readDirectory(uri: vscode.Uri, options: {}, token: vscode.CancellationToken): Thenable<[string, vscode.FileStat][]> {
return this._readDirectory(uri, token);
}
async _readDirectory(uri: vscode.Uri, token: vscode.CancellationToken): Promise<[string, vscode.FileStat][]> {
const children = await _.readdir(uri.fsPath);
const stats: [string, vscode.FileStat][] = [];
for (let i = 0; i < children.length; i++) {
_.checkCancellation(token);
const child = children[i];
const stat = await this._stat(path.join(uri.fsPath, child));
stats.push([child, stat]);
}
return Promise.resolve(stats);
}
createDirectory(uri: vscode.Uri, options: {}, token: vscode.CancellationToken): vscode.FileStat | Thenable<vscode.FileStat> {
return this._createDirectory(uri, token);
}
async _createDirectory(uri: vscode.Uri, token: vscode.CancellationToken): Promise<vscode.FileStat> {
await _.mkdir(uri.fsPath); // TODO support cancellation
_.checkCancellation(token);
return this._stat(uri.fsPath);
}
writeFile(uri: vscode.Uri, content: Uint8Array, options: vscode.FileOptions, token: vscode.CancellationToken): void | Thenable<void> {
return this._writeFile(uri, content, options, token);
}
async _writeFile(uri: vscode.Uri, content: Uint8Array, options: vscode.FileOptions, token: vscode.CancellationToken): Promise<void> {
const exists = await _.exists(uri.fsPath);
if (!exists) {
_.checkCancellation(token);
if (!options.create) {
throw vscode.FileSystemError.FileNotFound();
}
await _.mkdir(path.dirname(uri.fsPath));
} else {
if (options.exclusive) {
throw vscode.FileSystemError.FileExists();
}
}
_.checkCancellation(token);
return _.writefile(uri.fsPath, content as Buffer);
}
delete(uri: vscode.Uri, options: {}, token: vscode.CancellationToken): void | Thenable<void> {
return _.rmrf(uri.fsPath); // TODO support cancellation
}
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: vscode.FileOptions, token: vscode.CancellationToken): vscode.FileStat | Thenable<vscode.FileStat> {
return this._rename(oldUri, newUri, options, token);
}
async _rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: vscode.FileOptions, token: vscode.CancellationToken): Promise<vscode.FileStat> {
const exists = await _.exists(newUri.fsPath);
if (exists) {
if (options.exclusive) {
throw vscode.FileSystemError.FileExists();
} else {
await _.rmrf(newUri.fsPath);
}
}
_.checkCancellation(token);
const parentExists = await _.exists(path.dirname(newUri.fsPath));
if (!parentExists && !options.create) {
throw vscode.FileSystemError.FileNotFound();
}
_.checkCancellation(token);
if (!parentExists) {
await _.mkdir(path.dirname(newUri.fsPath));
}
_.checkCancellation(token);
await _.rename(oldUri.fsPath, newUri.fsPath);
_.checkCancellation(token);
return this._stat(newUri.fsPath);
}
watch(uri: vscode.Uri, options: { recursive?: boolean | undefined; excludes?: string[] | undefined; }): vscode.Disposable {
const watcher = fs.watch(uri.fsPath, { recursive: options.recursive }, async (event: string, filename: string | Buffer) => {
const filepath = path.join(uri.fsPath, _.normalizeNFC(filename.toString()));
// TODO support excludes (using minimatch library?)
this._onDidChangeFile.fire([{
type: event === 'change' ? vscode.FileChangeType.Changed : await _.exists(filepath) ? vscode.FileChangeType.Created : vscode.FileChangeType.Deleted,
uri: uri.with({ path: filepath })
} as vscode.FileChangeEvent]);
});
return { dispose: () => watcher.close() };
}
// TODO can implement a fast copy() method with node.js 8.x new fs.copy method
}
export function deactivate() { }
//#region Utilities
namespace _ {
function handleResult<T>(resolve: (result: T) => void, reject: (error: Error) => void, error: Error | null | undefined, result: T): void {
if (error) {
reject(massageError(error));
} else {
resolve(result);
}
}
function massageError(error: Error & { code?: string }): Error {
if (error.code === 'ENOENT') {
return vscode.FileSystemError.FileNotFound();
}
if (error.code === 'EISDIR') {
return vscode.FileSystemError.FileIsADirectory();
}
if (error.code === 'EEXIST') {
return vscode.FileSystemError.FileExists();
}
return error;
}
export function checkCancellation(token: vscode.CancellationToken): void {
if (token.isCancellationRequested) {
throw new Error('Operation cancelled');
}
}
export function normalizeNFC(items: string): string;
export function normalizeNFC(items: string[]): string[];
export function normalizeNFC(items: string | string[]): string | string[] {
if (process.platform !== 'darwin') {
return items;
}
if (Array.isArray(items)) {
return items.map(item => item.normalize('NFC'));
}
return items.normalize('NFC');
}
export function readdir(path: string): Promise<string[]> {
return new Promise<string[]>((resolve, reject) => {
fs.readdir(path, (error, children) => handleResult(resolve, reject, error, normalizeNFC(children)));
});
}
export function stat(path: string): Promise<fs.Stats> {
return new Promise<fs.Stats>((resolve, reject) => {
fs.stat(path, (error, stat) => handleResult(resolve, reject, error, stat));
});
}
export function readfile(path: string): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
fs.readFile(path, (error, buffer) => handleResult(resolve, reject, error, buffer));
});
}
export function writefile(path: string, content: Buffer): Promise<void> {
return new Promise<void>((resolve, reject) => {
fs.writeFile(path, content, error => handleResult(resolve, reject, error, void 0));
});
}
export function exists(path: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
fs.exists(path, exists => handleResult(resolve, reject, null, exists));
});
}
export function rmrf(path: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
rimraf(path, error => handleResult(resolve, reject, error, void 0));
});
}
export function mkdir(path: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
mkdirp(path, error => handleResult(resolve, reject, error, void 0));
});
}
export function rename(oldPath: string, newPath: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
fs.rename(oldPath, newPath, error => handleResult(resolve, reject, error, void 0));
});
}
}
export class FileStat implements vscode.FileStat {
constructor(private fsStat: fs.Stats) { }
get isFile(): boolean | undefined {
return this.fsStat.isFile();
}
get isDirectory(): boolean | undefined {
return this.fsStat.isDirectory();
}
get isSymbolicLink(): boolean | undefined {
return this.fsStat.isSymbolicLink();
}
get size(): number {
return this.fsStat.size;
}
get mtime(): number {
return this.fsStat.mtime.getTime();
}
}
//#endregion

View File

@ -0,0 +1,22 @@
//
// Note: This example test is leveraging the Mocha test framework.
// Please refer to their documentation on https://mochajs.org/ for help.
//
// The module 'assert' provides assertion methods from node
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
// import * as vscode from 'vscode';
// import * as myExtension from '../extension';
// Defines a Mocha test suite to group tests of similar kind together
suite("Extension Tests", function () {
// Defines a Mocha unit test
test("Something 1", function() {
assert.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});
});

View File

@ -0,0 +1,22 @@
//
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
//
// This file is providing the test runner to use when running extension tests.
// By default the test runner in use is Mocha based.
//
// You can provide your own test runner if you want to override it by exporting
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
// host can call to run the tests. The test runner is expected to use console.log
// to report the results back to the caller. When the tests are finished, return
// a possible error to the callback or null if none.
import * as testRunner from 'vscode/lib/testrunner';
// You can directly control Mocha options by uncommenting the following lines
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
testRunner.configure({
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
useColors: true // colored output from test results
});
module.exports = testRunner;

View File

@ -0,0 +1,8 @@
{
"folders": [
{
"uri": "datei:.",
"name": "Test Folder"
}
]
}

View File

@ -0,0 +1,23 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"es6"
],
"sourceMap": true,
"rootDir": "src",
/* Strict Type-Checking Option */
"strict": true, /* enable all strict type-checking options */
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
},
"exclude": [
"node_modules",
".vscode-test"
]
}

View File

@ -0,0 +1,15 @@
{
"rules": {
"no-string-throw": true,
"no-unused-expression": true,
"no-duplicate-variable": true,
"curly": true,
"class-name": true,
"semicolon": [
true,
"always"
],
"triple-equals": true
},
"defaultSeverity": "warning"
}

View File

@ -62,6 +62,9 @@
},
{
"path": "extension-deps-sample"
},
{
"path": "nodefs-provider-sample"
}
]
}