mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-04-27 16:55:44 +08:00
Improve file explorer to use file system provider and reveal api
This commit is contained in:
88
tree-view-sample/package-lock.json
generated
88
tree-view-sample/package-lock.json
generated
@ -296,8 +296,7 @@
|
||||
},
|
||||
"duplexer": {
|
||||
"version": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
||||
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
|
||||
"dev": true
|
||||
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
|
||||
},
|
||||
"duplexer2": {
|
||||
"version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
|
||||
@ -552,6 +551,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ftp-response-parser": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ftp-response-parser/-/ftp-response-parser-1.0.1.tgz",
|
||||
"integrity": "sha1-O50z+O3V+45HALj3eMRi5bFYH4k=",
|
||||
"requires": {
|
||||
"readable-stream": "1.1.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
||||
"requires": {
|
||||
"core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "0.10.31"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
|
||||
"integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
|
||||
@ -1205,6 +1235,43 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"jsftp": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jsftp/-/jsftp-2.1.3.tgz",
|
||||
"integrity": "sha512-r79EVB8jaNAZbq8hvanL8e8JGu2ZNr2bXdHC4ZdQhRImpSPpnWwm5DYVzQ5QxJmtGtKhNNuvqGgbNaFl604fEQ==",
|
||||
"requires": {
|
||||
"debug": "3.1.0",
|
||||
"ftp-response-parser": "1.0.1",
|
||||
"once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"parse-listing": "1.1.3",
|
||||
"stream-combiner": "0.2.2",
|
||||
"unorm": "1.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"stream-combiner": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
|
||||
"integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
|
||||
"requires": {
|
||||
"duplexer": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
||||
"through": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"json-schema": {
|
||||
"version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
|
||||
@ -1590,7 +1657,6 @@
|
||||
"once": {
|
||||
"version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
}
|
||||
@ -1630,6 +1696,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"parse-listing": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/parse-listing/-/parse-listing-1.1.3.tgz",
|
||||
"integrity": "sha1-qlRvV/3BKc+/mUXNS3V7FLBhgt0="
|
||||
},
|
||||
"path-dirname": {
|
||||
"version": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
|
||||
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
|
||||
@ -1970,8 +2041,7 @@
|
||||
},
|
||||
"through": {
|
||||
"version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
|
||||
"dev": true
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
|
||||
},
|
||||
"through2": {
|
||||
"version": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
|
||||
@ -2037,6 +2107,11 @@
|
||||
"through2-filter": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz"
|
||||
}
|
||||
},
|
||||
"unorm": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz",
|
||||
"integrity": "sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA="
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
@ -2192,8 +2267,7 @@
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",
|
||||
|
||||
@ -63,9 +63,13 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "openFtpResource",
|
||||
"command": "ftpExplorer.openFtpResource",
|
||||
"title": "Open FTP Resource"
|
||||
},
|
||||
{
|
||||
"command": "ftpExplorer.revealResource",
|
||||
"title": "Reveal in FTP View"
|
||||
},
|
||||
{
|
||||
"command": "jsonOutline.refresh",
|
||||
"title": "Refresh",
|
||||
@ -88,6 +92,11 @@
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "ftpExplorer.revealResource"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "jsonOutline.refresh",
|
||||
@ -149,6 +158,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^0.4.2",
|
||||
"ftp": "^0.3.10"
|
||||
"ftp": "^0.3.10",
|
||||
"jsftp": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,14 +4,13 @@ import * as vscode from 'vscode';
|
||||
|
||||
import { DepNodeProvider } from './nodeDependencies'
|
||||
import { JsonOutlineProvider } from './jsonOutline'
|
||||
import { FtpTreeDataProvider, FtpNode } from './ftpExplorer'
|
||||
import { FtpTreeDataProvider, FtpNode, FtpExplorer } from './ftpExplorer'
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const rootPath = vscode.workspace.rootPath;
|
||||
|
||||
const nodeDependenciesProvider = new DepNodeProvider(rootPath);
|
||||
const jsonOutlineProvider = new JsonOutlineProvider(context);
|
||||
const ftpExplorerProvider = new FtpTreeDataProvider();
|
||||
|
||||
vscode.window.registerTreeDataProvider('nodeDependencies', nodeDependenciesProvider);
|
||||
vscode.commands.registerCommand('nodeDependencies.refreshEntry', () => nodeDependenciesProvider.refresh());
|
||||
@ -25,13 +24,5 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.registerCommand('jsonOutline.renameNode', offset => jsonOutlineProvider.rename(offset));
|
||||
vscode.commands.registerCommand('extension.openJsonSelection', range => jsonOutlineProvider.select(range));
|
||||
|
||||
vscode.window.registerTreeDataProvider('ftpExplorer', ftpExplorerProvider);
|
||||
vscode.commands.registerCommand('ftpExplorer.refresh', () => ftpExplorerProvider.refresh());
|
||||
vscode.commands.registerCommand('openFtpResource', (node: FtpNode) => {
|
||||
vscode.workspace.openTextDocument(node.resource).then(document => {
|
||||
vscode.window.showTextDocument(document);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
new FtpExplorer(context);
|
||||
}
|
||||
|
||||
280
tree-view-sample/src/ftpExplorer.fileSystemProvider.ts
Normal file
280
tree-view-sample/src/ftpExplorer.fileSystemProvider.ts
Normal file
@ -0,0 +1,280 @@
|
||||
import { ExtensionContext, TreeDataProvider, EventEmitter, TreeItem, Event, window, TreeItemCollapsibleState, Uri, commands, workspace, TextDocumentContentProvider, CancellationToken, ProviderResult, TreeView } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import * as Client from 'ftp';
|
||||
import { basename, dirname, join } from 'path';
|
||||
import { Socket } from 'net';
|
||||
import * as JSFtp from 'jsftp';
|
||||
|
||||
class FtpFileSystemProvider implements vscode.FileSystemProvider {
|
||||
|
||||
readonly root: vscode.Uri;
|
||||
|
||||
private readonly _user: string;
|
||||
private readonly _pass: string;
|
||||
private _connection: JSFtp;
|
||||
private _pending: { resolve: Function, reject: Function, func: keyof JSFtp, args: any[] }[] = [];
|
||||
|
||||
constructor(
|
||||
root: vscode.Uri,
|
||||
user: string,
|
||||
pass: string
|
||||
) {
|
||||
this.root = root;
|
||||
this._user = user;
|
||||
this._pass = pass;
|
||||
}
|
||||
|
||||
private _withConnection<T>(func: keyof JSFtp, ...args: any[]): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this._pending.push({ resolve, reject, func, args });
|
||||
this._nextRequest();
|
||||
});
|
||||
}
|
||||
|
||||
private _nextRequest(): void {
|
||||
if (this._pending.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._connection === void 0) {
|
||||
// ensure connection first
|
||||
const candidate = new JSFtp({
|
||||
host: this.root.authority
|
||||
});
|
||||
candidate.keepAlive(1000 * 5);
|
||||
candidate.auth(this._user, this._pass, (err) => {
|
||||
this._connection = err ? null : candidate;
|
||||
this._nextRequest();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._connection === null) {
|
||||
// permanently failed
|
||||
const request = this._pending.shift();
|
||||
request.reject(new Error('no connection'))
|
||||
|
||||
} else {
|
||||
// connected
|
||||
const { func, args, resolve, reject } = this._pending.shift();
|
||||
(<Function>this._connection[func]).apply(this._connection, args.concat([function (err, res) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
}]));
|
||||
}
|
||||
|
||||
this._nextRequest();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._withConnection('raw', 'QUIT');
|
||||
}
|
||||
|
||||
utimes(resource: vscode.Uri, mtime: number): Promise<vscode.FileStat> {
|
||||
return this._withConnection('raw', 'NOOP')
|
||||
.then(() => this.stat(resource));
|
||||
}
|
||||
|
||||
stat(resource: vscode.Uri): Promise<vscode.FileStat> {
|
||||
const { path } = resource;
|
||||
if (path === '/' || path === '') {
|
||||
// root directory
|
||||
return Promise.resolve(<vscode.FileStat>{
|
||||
type: vscode.FileType.Dir,
|
||||
id: null,
|
||||
mtime: 0,
|
||||
size: 0
|
||||
});
|
||||
}
|
||||
|
||||
const name = basename(path);
|
||||
const dir = dirname(path);
|
||||
return this._withConnection<JSFtp.Entry[]>('ls', dir).then(entries => {
|
||||
for (const entry of entries) {
|
||||
if (entry.name === name) {
|
||||
return {
|
||||
id: null,
|
||||
mtime: entry.time,
|
||||
size: entry.size,
|
||||
type: entry.type
|
||||
};
|
||||
}
|
||||
}
|
||||
return Promise.reject<vscode.FileStat>(new Error(`ENOENT, ${resource.toString(true)}`));
|
||||
}, err => {
|
||||
return Promise.reject<vscode.FileStat>(new Error(`ENOENT, ${resource.toString(true)}`));
|
||||
});
|
||||
}
|
||||
|
||||
readdir(dir: vscode.Uri): Promise<[vscode.Uri, vscode.FileStat][]> {
|
||||
return this._withConnection<JSFtp.Entry[]>('ls', dir.path).then(entries => {
|
||||
const result: [vscode.Uri, vscode.FileStat][] = [];
|
||||
for (let entry of entries) {
|
||||
const resource = dir.with({ path: join(dir.path, entry.name) });
|
||||
const stat: vscode.FileStat = {
|
||||
id: resource.toString(),
|
||||
mtime: entry.time,
|
||||
size: entry.size,
|
||||
type: entry.type
|
||||
}
|
||||
result.push([resource, stat]);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
read(resource: vscode.Uri, offset: number = 0, len: number, progress: vscode.Progress<Uint8Array>): Promise<number> {
|
||||
|
||||
return this._withConnection<void>('raw', 'REST', [offset]).then(() => {
|
||||
|
||||
return this._withConnection<Socket>('get', resource.path)
|
||||
|
||||
}).then(socket => {
|
||||
|
||||
let bytesRead = 0;
|
||||
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
socket.on('data', buffer => {
|
||||
progress.report(buffer);
|
||||
bytesRead += buffer.length;
|
||||
if (len > 0 && bytesRead > len) {
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
socket.on('close', hadErr => {
|
||||
if (hadErr) {
|
||||
reject(hadErr);
|
||||
} else {
|
||||
resolve(bytesRead);
|
||||
}
|
||||
});
|
||||
socket.resume();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
write(resource: vscode.Uri, content: Uint8Array): Promise<void> {
|
||||
return this._withConnection('put', content, resource.path);
|
||||
}
|
||||
|
||||
rmdir(resource: vscode.Uri): Promise<void> {
|
||||
return this._withConnection('raw', 'RMD', [resource.path]);
|
||||
}
|
||||
|
||||
mkdir(resource: vscode.Uri): Promise<vscode.FileStat> {
|
||||
return this._withConnection('raw', 'MKD', [resource.path])
|
||||
.then(() => this.stat(resource));
|
||||
}
|
||||
|
||||
unlink(resource: vscode.Uri): Promise<void> {
|
||||
return this._withConnection('raw', 'DELE', [resource.path]);
|
||||
}
|
||||
|
||||
move(resource: vscode.Uri, target: vscode.Uri): Promise<vscode.FileStat> {
|
||||
return this._withConnection<void>('raw', 'RNFR', [resource.path]).then(() => {
|
||||
return this._withConnection<void>('raw', 'RNTO', [target.path]);
|
||||
}).then(() => {
|
||||
return this.stat(target);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface FtpNode {
|
||||
|
||||
resource: vscode.Uri;
|
||||
isDirectory: boolean;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class FtpTreeDataProvider implements TreeDataProvider<FtpNode> {
|
||||
|
||||
private _onDidChange: vscode.EventEmitter<FtpNode> = new vscode.EventEmitter<FtpNode>();
|
||||
readonly onDidChangeTreeData = this._onDidChange.event;
|
||||
|
||||
constructor(private readonly fileSystemProvider: FtpFileSystemProvider) { }
|
||||
|
||||
refresh() {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
public getTreeItem(element: FtpNode): TreeItem {
|
||||
return {
|
||||
id: element.resource.fsPath,
|
||||
resourceUri: element.resource,
|
||||
collapsibleState: element.isDirectory ? TreeItemCollapsibleState.Collapsed : void 0,
|
||||
command: element.isDirectory ? void 0 : {
|
||||
command: 'ftpExplorer.openFtpResource',
|
||||
arguments: [element.resource],
|
||||
title: 'Open FTP Resource'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public getChildren(element?: FtpNode): FtpNode[] | Thenable<FtpNode[]> {
|
||||
return this.fileSystemProvider.readdir(element ? element.resource : this.fileSystemProvider.root)
|
||||
.then((result: [vscode.Uri, vscode.FileStat][]) => this.sort(result.map(r => ({ resource: r[0], isDirectory: r[1].type !== vscode.FileType.File }))));
|
||||
}
|
||||
|
||||
public getParent(element: FtpNode): FtpNode {
|
||||
const parent = vscode.Uri.parse(dirname(element.resource.fsPath));
|
||||
return parent.fsPath !== this.fileSystemProvider.root.fsPath ? { resource: parent, isDirectory: true } : null;
|
||||
}
|
||||
|
||||
private sort(nodes: FtpNode[]): FtpNode[] {
|
||||
return nodes.sort((n1, n2) => {
|
||||
if (n1.isDirectory && !n2.isDirectory) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!n1.isDirectory && n2.isDirectory) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return basename(n1.resource.fsPath).localeCompare(basename(n2.resource.fsPath));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class FtpExplorer {
|
||||
|
||||
private ftpViewer: TreeView<FtpNode>;
|
||||
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
const fileProvider = new FtpFileSystemProvider(vscode.Uri.parse('ftp://mirror.switch.ch/'), 'anonymous', 'anonymous@anonymous.de')
|
||||
const treeDataProvider = new FtpTreeDataProvider(fileProvider);
|
||||
context.subscriptions.push(vscode.workspace.registerFileSystemProvider('ftp', fileProvider));
|
||||
|
||||
this.ftpViewer = vscode.window.registerTreeDataProvider('ftpExplorer', treeDataProvider);
|
||||
|
||||
vscode.commands.registerCommand('ftpExplorer.refresh', () => treeDataProvider.refresh());
|
||||
vscode.commands.registerCommand('ftpExplorer.openFtpResource', resource => this.openResource(resource));
|
||||
vscode.commands.registerCommand('ftpExplorer.revealResource', () => this.reveal());
|
||||
}
|
||||
|
||||
private openResource(resource: vscode.Uri): void {
|
||||
vscode.workspace.openTextDocument(resource).then(document => vscode.window.showTextDocument(document));
|
||||
}
|
||||
|
||||
private reveal(): void {
|
||||
const node = this.getNode();
|
||||
if (node) {
|
||||
this.ftpViewer.reveal(node);
|
||||
}
|
||||
}
|
||||
|
||||
private getNode(): FtpNode {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
if (vscode.window.activeTextEditor.document.uri.scheme === 'ftp') {
|
||||
return { resource: vscode.window.activeTextEditor.document.uri, isDirectory: false };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
193
tree-view-sample/src/ftpExplorer.textDocumentContentProvider.ts
Normal file
193
tree-view-sample/src/ftpExplorer.textDocumentContentProvider.ts
Normal file
@ -0,0 +1,193 @@
|
||||
import { ExtensionContext, EventEmitter, TreeItem, Event, window, TreeItemCollapsibleState, Uri, commands, workspace, TextDocumentContentProvider, CancellationToken, ProviderResult, TreeView } from 'vscode';
|
||||
import * as vscode from 'vscode';
|
||||
import * as Client from 'ftp';
|
||||
import { basename, dirname, join } from 'path';
|
||||
import { Socket } from 'net';
|
||||
import * as JSFtp from 'jsftp';
|
||||
import { TreeDataProvider } from 'vscode';
|
||||
|
||||
interface IEntry {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface FtpNode {
|
||||
|
||||
resource: vscode.Uri;
|
||||
isDirectory: boolean;
|
||||
|
||||
}
|
||||
|
||||
export class FtpModel {
|
||||
|
||||
private nodes: Map<string, FtpNode> = new Map<string, FtpNode>();
|
||||
|
||||
constructor(readonly host: string, private user: string, private password: string) {
|
||||
}
|
||||
|
||||
public connect(): Thenable<Client> {
|
||||
return new Promise((c, e) => {
|
||||
const client = new Client();
|
||||
client.on('ready', () => {
|
||||
c(client);
|
||||
});
|
||||
|
||||
client.on('error', error => {
|
||||
e('Error while connecting: ' + error.message);
|
||||
})
|
||||
|
||||
client.connect({
|
||||
host: this.host,
|
||||
username: this.user,
|
||||
password: this.password
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public get roots(): Thenable<FtpNode[]> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.list((err, list) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
client.end();
|
||||
|
||||
return c(this.sort(list.map(entry => ({ resource: Uri.parse(`ftp://${this.host}///${entry.name}`), isDirectory: entry.type === 'd' }))));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getChildren(node: FtpNode): Thenable<FtpNode[]> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.list(node.resource.fsPath, (err, list) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
client.end();
|
||||
|
||||
return c(this.sort(list.map(entry => ({ resource: Uri.parse(`${node.resource.fsPath}/${entry.name}`), isDirectory: entry.type === 'd' }))));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private sort(nodes: FtpNode[]): FtpNode[] {
|
||||
return nodes.sort((n1, n2) => {
|
||||
if (n1.isDirectory && !n2.isDirectory) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!n1.isDirectory && n2.isDirectory) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return basename(n1.resource.fsPath).localeCompare(basename(n2.resource.fsPath));
|
||||
});
|
||||
}
|
||||
|
||||
public getContent(resource: Uri): Thenable<string> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.get(resource.path.substr(2), (err, stream) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
let string = ''
|
||||
stream.on('data', function (buffer) {
|
||||
if (buffer) {
|
||||
var part = buffer.toString();
|
||||
string += part;
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', function () {
|
||||
client.end();
|
||||
c(string);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FtpTreeDataProvider implements TreeDataProvider<FtpNode>, TextDocumentContentProvider {
|
||||
|
||||
private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>();
|
||||
readonly onDidChangeTreeData: Event<any> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(private readonly model: FtpModel) { }
|
||||
|
||||
public refresh(): any {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
|
||||
public getTreeItem(element: FtpNode): TreeItem {
|
||||
return {
|
||||
id: element.resource.fsPath,
|
||||
resourceUri: element.resource,
|
||||
collapsibleState: element.isDirectory ? TreeItemCollapsibleState.Collapsed : void 0,
|
||||
command: element.isDirectory ? void 0 : {
|
||||
command: 'ftpExplorer.openFtpResource',
|
||||
arguments: [element.resource],
|
||||
title: 'Open FTP Resource'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public getChildren(element?: FtpNode): FtpNode[] | Thenable<FtpNode[]> {
|
||||
return element ? this.model.getChildren(element) : this.model.roots;
|
||||
}
|
||||
|
||||
public getParent(element: FtpNode): FtpNode {
|
||||
const parent = vscode.Uri.parse(dirname(element.resource.fsPath));
|
||||
return parent.fsPath !== this.model.host ? { resource: parent, isDirectory: true } : null;
|
||||
}
|
||||
|
||||
public provideTextDocumentContent(uri: Uri, token: CancellationToken): ProviderResult<string> {
|
||||
return this.model.getContent(uri).then(content => content);
|
||||
}
|
||||
}
|
||||
|
||||
export class FtpExplorer {
|
||||
|
||||
private ftpViewer: TreeView<FtpNode>;
|
||||
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
const ftpModel = new FtpModel('mirror.switch.ch', 'anonymous', 'anonymous@anonymous.de');
|
||||
const treeDataProvider = new FtpTreeDataProvider(ftpModel);
|
||||
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider('ftp', treeDataProvider));
|
||||
|
||||
this.ftpViewer = vscode.window.registerTreeDataProvider('ftpExplorer', treeDataProvider);
|
||||
|
||||
vscode.commands.registerCommand('ftpExplorer.refresh', () => treeDataProvider.refresh());
|
||||
vscode.commands.registerCommand('ftpExplorer.openFtpResource', resource => this.openResource(resource));
|
||||
vscode.commands.registerCommand('ftpExplorer.revealResource', () => this.reveal());
|
||||
}
|
||||
|
||||
private openResource(resource: vscode.Uri): void {
|
||||
vscode.window.showTextDocument(resource);
|
||||
}
|
||||
|
||||
private reveal(): void {
|
||||
const node = this.getNode();
|
||||
if (node) {
|
||||
this.ftpViewer.reveal(node);
|
||||
}
|
||||
}
|
||||
|
||||
private getNode(): FtpNode {
|
||||
if (vscode.window.activeTextEditor) {
|
||||
if (vscode.window.activeTextEditor.document.uri.scheme === 'ftp') {
|
||||
return { resource: vscode.window.activeTextEditor.document.uri, isDirectory: false };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,168 +0,0 @@
|
||||
import { ExtensionContext, TreeDataProvider, EventEmitter, TreeItem, Event, window, TreeItemCollapsibleState, Uri, commands, workspace, TextDocumentContentProvider, CancellationToken, ProviderResult } from 'vscode';
|
||||
import * as Client from 'ftp';
|
||||
import * as path from 'path';
|
||||
|
||||
interface IEntry {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class FtpNode {
|
||||
private _resource: Uri;
|
||||
|
||||
constructor(private entry: IEntry, private host: string, private _parent: string) {
|
||||
this._resource = Uri.parse(`ftp://${host}/${_parent}/${entry.name}`);
|
||||
}
|
||||
|
||||
public get resource(): Uri {
|
||||
return this._resource;
|
||||
}
|
||||
|
||||
public get path(): string {
|
||||
return path.join(this._parent, this.name);
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.entry.name;
|
||||
}
|
||||
|
||||
public get isFolder(): boolean {
|
||||
return this.entry.type === 'd' || this.entry.type === 'l';
|
||||
}
|
||||
}
|
||||
|
||||
export class FtpModel {
|
||||
|
||||
constructor(private host: string, private user: string, private password: string) {
|
||||
}
|
||||
|
||||
public connect(): Thenable<Client> {
|
||||
return new Promise((c, e) => {
|
||||
const client = new Client();
|
||||
client.on('ready', () => {
|
||||
c(client);
|
||||
});
|
||||
|
||||
client.on('error', error => {
|
||||
e('Error while connecting: ' + error.message);
|
||||
})
|
||||
|
||||
client.connect({
|
||||
host: this.host,
|
||||
username: this.user,
|
||||
password: this.password
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public get roots(): Thenable<FtpNode[]> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.list((err, list) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
client.end();
|
||||
|
||||
return c(this.sort(list.map(entry => new FtpNode(entry, this.host, '/'))));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getChildren(node: FtpNode): Thenable<FtpNode[]> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.list(node.path, (err, list) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
client.end();
|
||||
|
||||
return c(this.sort(list.map(entry => new FtpNode(entry, this.host, node.path))));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private sort(nodes: FtpNode[]): FtpNode[] {
|
||||
return nodes.sort((n1, n2) => {
|
||||
if (n1.isFolder && !n2.isFolder) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!n1.isFolder && n2.isFolder) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return n1.name.localeCompare(n2.name);
|
||||
});
|
||||
}
|
||||
|
||||
public getContent(resource: Uri): Thenable<string> {
|
||||
return this.connect().then(client => {
|
||||
return new Promise((c, e) => {
|
||||
client.get(resource.path.substr(2), (err, stream) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
let string = ''
|
||||
stream.on('data', function (buffer) {
|
||||
if (buffer) {
|
||||
var part = buffer.toString();
|
||||
string += part;
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', function () {
|
||||
client.end();
|
||||
c(string);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FtpTreeDataProvider implements TreeDataProvider<FtpNode>, TextDocumentContentProvider {
|
||||
|
||||
private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>();
|
||||
readonly onDidChangeTreeData: Event<any> = this._onDidChangeTreeData.event;
|
||||
|
||||
private model: FtpModel;
|
||||
|
||||
refresh(): any {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
public getTreeItem(element: FtpNode): TreeItem {
|
||||
return {
|
||||
resourceUri: element.resource,
|
||||
collapsibleState: element.isFolder ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None,
|
||||
command: element.isFolder ? void 0 : {
|
||||
command: 'openFtpResource',
|
||||
arguments: [element.resource],
|
||||
title: 'Open FTP Resource'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public getChildren(element?: FtpNode): FtpNode[] | Thenable<FtpNode[]> {
|
||||
if (!element) {
|
||||
if (!this.model) {
|
||||
this.model = new FtpModel('mirror.switch.ch', 'anonymous', 'anonymous@anonymous.de');
|
||||
}
|
||||
|
||||
return this.model.roots;
|
||||
}
|
||||
|
||||
return this.model.getChildren(element);
|
||||
}
|
||||
|
||||
public provideTextDocumentContent(uri: Uri, token: CancellationToken): ProviderResult<string> {
|
||||
return this.model.getContent(uri);
|
||||
}
|
||||
}
|
||||
48
tree-view-sample/src/jsftp.d.ts
vendored
Normal file
48
tree-view-sample/src/jsftp.d.ts
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
declare namespace JSFtp {
|
||||
|
||||
|
||||
interface JSFtpOptions {
|
||||
host: string;
|
||||
port?: number | 21;
|
||||
user?: string | 'anonymous';
|
||||
pass?: string | '@anonymous';
|
||||
useList?: boolean
|
||||
}
|
||||
|
||||
interface Callback<T> {
|
||||
(err: any, result: T): void;
|
||||
}
|
||||
|
||||
|
||||
interface Entry {
|
||||
name: string;
|
||||
size: number;
|
||||
time: number;
|
||||
type: 0 | 1;
|
||||
}
|
||||
}
|
||||
|
||||
interface JSFtp extends EventEmitter {
|
||||
auth(user: string, password: string, callback: JSFtp.Callback<void>): void
|
||||
keepAlive(wait?: number): void;
|
||||
ls(path: string, callback: JSFtp.Callback<JSFtp.Entry[]>): void;
|
||||
list(path: string, callback: JSFtp.Callback<any>): void;
|
||||
put(buffer: Buffer, path: string, callback: JSFtp.Callback<void>): void;
|
||||
get(path: string, callback: JSFtp.Callback<Readable>): void;
|
||||
setType(type: 'A' | 'AN' | 'AT' | 'AC' | 'E' | 'I' | 'L', callback: JSFtp.Callback<any>): void;
|
||||
raw(command: string, args: any[], callback: JSFtp.Callback<void>): void;
|
||||
raw<T>(command: string, args: any[], callback: JSFtp.Callback<T>): void;
|
||||
}
|
||||
|
||||
interface JSFtpConstructor {
|
||||
new(options: JSFtp.JSFtpOptions): JSFtp;
|
||||
}
|
||||
|
||||
declare const JSFtp: JSFtpConstructor;
|
||||
|
||||
export = JSFtp;
|
||||
@ -46,23 +46,23 @@ export class DepNodeProvider implements vscode.TreeDataProvider<Dependency> {
|
||||
if (this.pathExists(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||
|
||||
const toDep = (moduleName: string): Dependency => {
|
||||
const toDep = (moduleName: string, version: string): Dependency => {
|
||||
if (this.pathExists(path.join(this.workspaceRoot, 'node_modules', moduleName))) {
|
||||
return new Dependency(moduleName, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
return new Dependency(moduleName, version, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
} else {
|
||||
return new Dependency(moduleName, vscode.TreeItemCollapsibleState.None, {
|
||||
return new Dependency(moduleName, version, vscode.TreeItemCollapsibleState.None, {
|
||||
command: 'extension.openPackageOnNpm',
|
||||
title: '',
|
||||
arguments: [moduleName],
|
||||
arguments: [moduleName]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const deps = packageJson.dependencies
|
||||
? Object.keys(packageJson.dependencies).map(toDep)
|
||||
? Object.keys(packageJson.dependencies).map(dep => toDep(dep, packageJson.dependencies[dep]))
|
||||
: [];
|
||||
const devDeps = packageJson.devDependencies
|
||||
? Object.keys(packageJson.devDependencies).map(toDep)
|
||||
? Object.keys(packageJson.devDependencies).map(dep => toDep(dep, packageJson.devDependencies[dep]))
|
||||
: [];
|
||||
return deps.concat(devDeps);
|
||||
} else {
|
||||
@ -85,12 +85,17 @@ class Dependency extends vscode.TreeItem {
|
||||
|
||||
constructor(
|
||||
public readonly label: string,
|
||||
private version: string,
|
||||
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
|
||||
public readonly command?: vscode.Command
|
||||
) {
|
||||
super(label, collapsibleState);
|
||||
}
|
||||
|
||||
get tooltip(): string {
|
||||
return `${this.label}-${this.version}`
|
||||
}
|
||||
|
||||
iconPath = {
|
||||
light: path.join(__filename, '..', '..', '..', 'resources', 'light', 'dependency.svg'),
|
||||
dark: path.join(__filename, '..', '..', '..', 'resources', 'dark', 'dependency.svg')
|
||||
|
||||
641
tree-view-sample/vscode.proposed.d.ts
vendored
Normal file
641
tree-view-sample/vscode.proposed.d.ts
vendored
Normal file
@ -0,0 +1,641 @@
|
||||
import { ProviderResult } from "vscode";
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// This is the place for API experiments and proposal.
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
export class FoldingRangeList {
|
||||
|
||||
/**
|
||||
* The folding ranges.
|
||||
*/
|
||||
ranges: FoldingRange[];
|
||||
|
||||
/**
|
||||
* Creates mew folding range list.
|
||||
*
|
||||
* @param ranges The folding ranges
|
||||
*/
|
||||
constructor(ranges: FoldingRange[]);
|
||||
}
|
||||
|
||||
|
||||
export class FoldingRange {
|
||||
|
||||
/**
|
||||
* The start line number (0-based)
|
||||
*/
|
||||
startLine: number;
|
||||
|
||||
/**
|
||||
* The end line number (0-based)
|
||||
*/
|
||||
endLine: number;
|
||||
|
||||
/**
|
||||
* The actual color value for this color range.
|
||||
*/
|
||||
type?: FoldingRangeType | string;
|
||||
|
||||
/**
|
||||
* Creates a new folding range.
|
||||
*
|
||||
* @param startLineNumber The first line of the fold
|
||||
* @param type The last line of the fold
|
||||
*/
|
||||
constructor(startLineNumber: number, endLineNumber: number, type?: FoldingRangeType);
|
||||
}
|
||||
|
||||
export enum FoldingRangeType {
|
||||
/**
|
||||
* Folding range for a comment
|
||||
*/
|
||||
Comment = 'comment',
|
||||
/**
|
||||
* Folding range for a imports or includes
|
||||
*/
|
||||
Imports = 'imports',
|
||||
/**
|
||||
* Folding range for a region (e.g. `#region`)
|
||||
*/
|
||||
Region = 'region'
|
||||
}
|
||||
|
||||
// export enum FileErrorCodes {
|
||||
// /**
|
||||
// * Not owner.
|
||||
// */
|
||||
// EPERM = 1,
|
||||
// /**
|
||||
// * No such file or directory.
|
||||
// */
|
||||
// ENOENT = 2,
|
||||
// /**
|
||||
// * I/O error.
|
||||
// */
|
||||
// EIO = 5,
|
||||
// /**
|
||||
// * Permission denied.
|
||||
// */
|
||||
// EACCES = 13,
|
||||
// /**
|
||||
// * File exists.
|
||||
// */
|
||||
// EEXIST = 17,
|
||||
// /**
|
||||
// * Not a directory.
|
||||
// */
|
||||
// ENOTDIR = 20,
|
||||
// /**
|
||||
// * Is a directory.
|
||||
// */
|
||||
// EISDIR = 21,
|
||||
// /**
|
||||
// * File too large.
|
||||
// */
|
||||
// EFBIG = 27,
|
||||
// /**
|
||||
// * No space left on device.
|
||||
// */
|
||||
// ENOSPC = 28,
|
||||
// /**
|
||||
// * Directory is not empty.
|
||||
// */
|
||||
// ENOTEMPTY = 66,
|
||||
// /**
|
||||
// * Invalid file handle.
|
||||
// */
|
||||
// ESTALE = 70,
|
||||
// /**
|
||||
// * Illegal NFS file handle.
|
||||
// */
|
||||
// EBADHANDLE = 10001,
|
||||
// }
|
||||
|
||||
export enum FileChangeType {
|
||||
Updated = 0,
|
||||
Added = 1,
|
||||
Deleted = 2
|
||||
}
|
||||
|
||||
export interface FileChange {
|
||||
type: FileChangeType;
|
||||
resource: Uri;
|
||||
}
|
||||
|
||||
export enum FileType {
|
||||
File = 0,
|
||||
Dir = 1,
|
||||
Symlink = 2
|
||||
}
|
||||
|
||||
export interface FileStat {
|
||||
id: number | string;
|
||||
mtime: number;
|
||||
// atime: number;
|
||||
size: number;
|
||||
type: FileType;
|
||||
}
|
||||
|
||||
export interface TextSearchQuery {
|
||||
pattern: string;
|
||||
isRegex?: boolean;
|
||||
isCaseSensitive?: boolean;
|
||||
isWordMatch?: boolean;
|
||||
}
|
||||
|
||||
export interface TextSearchOptions {
|
||||
includes: GlobPattern[];
|
||||
excludes: GlobPattern[];
|
||||
}
|
||||
|
||||
export interface TextSearchResult {
|
||||
uri: Uri;
|
||||
range: Range;
|
||||
preview: { leading: string, matching: string, trailing: string };
|
||||
}
|
||||
|
||||
// todo@joh discover files etc
|
||||
// todo@joh CancellationToken everywhere
|
||||
// todo@joh add open/close calls?
|
||||
export interface FileSystemProvider {
|
||||
|
||||
readonly onDidChange?: Event<FileChange[]>;
|
||||
|
||||
// todo@joh - remove this
|
||||
readonly root?: Uri;
|
||||
|
||||
// more...
|
||||
//
|
||||
utimes(resource: Uri, mtime: number, atime: number): Thenable<FileStat>;
|
||||
|
||||
stat(resource: Uri): Thenable<FileStat>;
|
||||
|
||||
read(resource: Uri, offset: number, length: number, progress: Progress<Uint8Array>): Thenable<number>;
|
||||
|
||||
// todo@joh - have an option to create iff not exist
|
||||
// todo@remote
|
||||
// offset - byte offset to start
|
||||
// count - number of bytes to write
|
||||
// Thenable<number> - number of bytes actually written
|
||||
write(resource: Uri, content: Uint8Array): Thenable<void>;
|
||||
|
||||
// todo@remote
|
||||
// Thenable<FileStat>
|
||||
move(resource: Uri, target: Uri): Thenable<FileStat>;
|
||||
|
||||
// todo@remote
|
||||
// helps with performance bigly
|
||||
// copy?(from: Uri, to: Uri): Thenable<void>;
|
||||
|
||||
// todo@remote
|
||||
// Thenable<FileStat>
|
||||
mkdir(resource: Uri): Thenable<FileStat>;
|
||||
|
||||
readdir(resource: Uri): Thenable<[Uri, FileStat][]>;
|
||||
|
||||
// todo@remote
|
||||
// ? merge both
|
||||
// ? recursive del
|
||||
rmdir(resource: Uri): Thenable<void>;
|
||||
unlink(resource: Uri): Thenable<void>;
|
||||
|
||||
// todo@remote
|
||||
// create(resource: Uri): Thenable<FileStat>;
|
||||
|
||||
// find files by names
|
||||
// todo@joh, move into its own provider
|
||||
findFiles?(query: string, progress: Progress<Uri>, token: CancellationToken): Thenable<void>;
|
||||
provideTextSearchResults?(query: TextSearchQuery, options: TextSearchOptions, progress: Progress<TextSearchResult>, token: CancellationToken): Thenable<void>;
|
||||
}
|
||||
|
||||
export namespace workspace {
|
||||
export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider): Disposable;
|
||||
|
||||
/**
|
||||
* This method replaces `deleteCount` [workspace folders](#workspace.workspaceFolders) starting at index `start`
|
||||
* by an optional set of `workspaceFoldersToAdd` on the `vscode.workspace.workspaceFolders` array. This "splice"
|
||||
* behavior can be used to add, remove and change workspace folders in a single operation.
|
||||
*
|
||||
* If the first workspace folder is added, removed or changed, the currently executing extensions (including the
|
||||
* one that called this method) will be terminated and restarted so that the (deprecated) `rootPath` property is
|
||||
* updated to point to the first workspace folder.
|
||||
*
|
||||
* Use the [`onDidChangeWorkspaceFolders()`](#onDidChangeWorkspaceFolders) event to get notified when the
|
||||
* workspace folders have been updated.
|
||||
*
|
||||
* **Example:** adding a new workspace folder at the end of workspace folders
|
||||
* ```typescript
|
||||
* workspace.updateWorkspaceFolders(workspace.workspaceFolders ? workspace.workspaceFolders.length : 0, null, { uri: ...});
|
||||
* ```
|
||||
*
|
||||
* **Example:** removing the first workspace folder
|
||||
* ```typescript
|
||||
* workspace.updateWorkspaceFolders(0, 1);
|
||||
* ```
|
||||
*
|
||||
* **Example:** replacing an existing workspace folder with a new one
|
||||
* ```typescript
|
||||
* workspace.updateWorkspaceFolders(0, 1, { uri: ...});
|
||||
* ```
|
||||
*
|
||||
* It is valid to remove an existing workspace folder and add it again with a different name
|
||||
* to rename that folder.
|
||||
*
|
||||
* **Note:** it is not valid to call [updateWorkspaceFolders()](#updateWorkspaceFolders) multiple times
|
||||
* without waiting for the [`onDidChangeWorkspaceFolders()`](#onDidChangeWorkspaceFolders) to fire.
|
||||
*
|
||||
* @param start the zero-based location in the list of currently opened [workspace folders](#WorkspaceFolder)
|
||||
* from which to start deleting workspace folders.
|
||||
* @param deleteCount the optional number of workspace folders to remove.
|
||||
* @param workspaceFoldersToAdd the optional variable set of workspace folders to add in place of the deleted ones.
|
||||
* Each workspace is identified with a mandatory URI and an optional name.
|
||||
* @return true if the operation was successfully started and false otherwise if arguments were used that would result
|
||||
* in invalid workspace folder state (e.g. 2 folders with the same URI).
|
||||
*/
|
||||
export function updateWorkspaceFolders(start: number, deleteCount: number, ...workspaceFoldersToAdd: { uri: Uri, name?: string }[]): boolean;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
|
||||
export function sampleFunction(): Thenable<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The contiguous set of modified lines in a diff.
|
||||
*/
|
||||
export interface LineChange {
|
||||
readonly originalStartLineNumber: number;
|
||||
readonly originalEndLineNumber: number;
|
||||
readonly modifiedStartLineNumber: number;
|
||||
readonly modifiedEndLineNumber: number;
|
||||
}
|
||||
|
||||
export namespace commands {
|
||||
|
||||
/**
|
||||
* Registers a diff information command that can be invoked via a keyboard shortcut,
|
||||
* a menu item, an action, or directly.
|
||||
*
|
||||
* Diff information commands are different from ordinary [commands](#commands.registerCommand) as
|
||||
* they only execute when there is an active diff editor when the command is called, and the diff
|
||||
* information has been computed. Also, the command handler of an editor command has access to
|
||||
* the diff information.
|
||||
*
|
||||
* @param command A unique identifier for the command.
|
||||
* @param callback A command handler function with access to the [diff information](#LineChange).
|
||||
* @param thisArg The `this` context used when invoking the handler function.
|
||||
* @return Disposable which unregisters this command on disposal.
|
||||
*/
|
||||
export function registerDiffInformationCommand(command: string, callback: (diff: LineChange[], ...args: any[]) => any, thisArg?: any): Disposable;
|
||||
}
|
||||
|
||||
//#region decorations
|
||||
|
||||
//todo@joh -> make class
|
||||
export interface DecorationData {
|
||||
priority?: number;
|
||||
title?: string;
|
||||
bubble?: boolean;
|
||||
abbreviation?: string;
|
||||
color?: ThemeColor;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export interface SourceControlResourceDecorations {
|
||||
source?: string;
|
||||
letter?: string;
|
||||
color?: ThemeColor;
|
||||
}
|
||||
|
||||
export interface DecorationProvider {
|
||||
onDidChangeDecorations: Event<undefined | Uri | Uri[]>;
|
||||
provideDecoration(uri: Uri, token: CancellationToken): ProviderResult<DecorationData>;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
export function registerDecorationProvider(provider: DecorationProvider): Disposable;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* Represents a debug adapter executable and optional arguments passed to it.
|
||||
*/
|
||||
export class DebugAdapterExecutable {
|
||||
/**
|
||||
* The command path of the debug adapter executable.
|
||||
* A command must be either an absolute path or the name of an executable looked up via the PATH environment variable.
|
||||
* The special value 'node' will be mapped to VS Code's built-in node runtime.
|
||||
*/
|
||||
readonly command: string;
|
||||
|
||||
/**
|
||||
* Optional arguments passed to the debug adapter executable.
|
||||
*/
|
||||
readonly args: string[];
|
||||
|
||||
/**
|
||||
* Create a new debug adapter specification.
|
||||
*/
|
||||
constructor(command: string, args?: string[]);
|
||||
}
|
||||
|
||||
export interface DebugConfigurationProvider {
|
||||
/**
|
||||
* This optional method is called just before a debug adapter is started to determine its excutable path and arguments.
|
||||
* Registering more than one debugAdapterExecutable for a type results in an error.
|
||||
* @param folder The workspace folder from which the configuration originates from or undefined for a folderless setup.
|
||||
* @param token A cancellation token.
|
||||
* @return a [debug adapter's executable and optional arguments](#DebugAdapterExecutable) or undefined.
|
||||
*/
|
||||
debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult<DebugAdapterExecutable>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The severity level of a log message
|
||||
*/
|
||||
export enum LogLevel {
|
||||
Trace = 1,
|
||||
Debug = 2,
|
||||
Info = 3,
|
||||
Warning = 4,
|
||||
Error = 5,
|
||||
Critical = 6,
|
||||
Off = 7
|
||||
}
|
||||
|
||||
/**
|
||||
* A logger for writing to an extension's log file, and accessing its dedicated log directory.
|
||||
*/
|
||||
export interface Logger {
|
||||
readonly onDidChangeLogLevel: Event<LogLevel>;
|
||||
readonly currentLevel: LogLevel;
|
||||
readonly logDirectory: Thenable<string>;
|
||||
|
||||
trace(message: string, ...args: any[]): void;
|
||||
debug(message: string, ...args: any[]): void;
|
||||
info(message: string, ...args: any[]): void;
|
||||
warn(message: string, ...args: any[]): void;
|
||||
error(message: string | Error, ...args: any[]): void;
|
||||
critical(message: string | Error, ...args: any[]): void;
|
||||
}
|
||||
|
||||
export interface ExtensionContext {
|
||||
/**
|
||||
* This extension's logger
|
||||
*/
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export interface RenameInitialValue {
|
||||
range: Range;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export namespace languages {
|
||||
|
||||
/**
|
||||
* Register a folding provider.
|
||||
*
|
||||
* Multiple folding can be registered for a language. In that case providers are sorted
|
||||
* by their [score](#languages.match) and the best-matching provider is used. Failure
|
||||
* of the selected provider will cause a failure of the whole operation.
|
||||
*
|
||||
* @param selector A selector that defines the documents this provider is applicable to.
|
||||
* @param provider A folding provider.
|
||||
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
|
||||
*/
|
||||
export function registerFoldingProvider(selector: DocumentSelector, provider: FoldingProvider): Disposable;
|
||||
|
||||
export interface RenameProvider2 extends RenameProvider {
|
||||
resolveInitialRenameValue?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<RenameInitialValue>;
|
||||
}
|
||||
}
|
||||
export interface FoldingProvider {
|
||||
provideFoldingRanges(document: TextDocument, token: CancellationToken): ProviderResult<FoldingRangeList>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the validation type of the Source Control input.
|
||||
*/
|
||||
export enum SourceControlInputBoxValidationType {
|
||||
|
||||
/**
|
||||
* Something not allowed by the rules of a language or other means.
|
||||
*/
|
||||
Error = 0,
|
||||
|
||||
/**
|
||||
* Something suspicious but allowed.
|
||||
*/
|
||||
Warning = 1,
|
||||
|
||||
/**
|
||||
* Something to inform about but not a problem.
|
||||
*/
|
||||
Information = 2
|
||||
}
|
||||
|
||||
export interface SourceControlInputBoxValidation {
|
||||
|
||||
/**
|
||||
* The validation message to display.
|
||||
*/
|
||||
readonly message: string;
|
||||
|
||||
/**
|
||||
* The validation type.
|
||||
*/
|
||||
readonly type: SourceControlInputBoxValidationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the input box in the Source Control viewlet.
|
||||
*/
|
||||
export interface SourceControlInputBox {
|
||||
|
||||
/**
|
||||
* A validation function for the input box. It's possible to change
|
||||
* the validation provider simply by setting this property to a different function.
|
||||
*/
|
||||
validateInput?(value: string, cursorPosition: number): ProviderResult<SourceControlInputBoxValidation | undefined | null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content settings for a webview.
|
||||
*/
|
||||
export interface WebviewOptions {
|
||||
/**
|
||||
* Should scripts be enabled in the webview content?
|
||||
*
|
||||
* Defaults to false (scripts-disabled).
|
||||
*/
|
||||
readonly enableScripts?: boolean;
|
||||
|
||||
/**
|
||||
* Should command uris be enabled in webview content?
|
||||
*
|
||||
* Defaults to false.
|
||||
*/
|
||||
readonly enableCommandUris?: boolean;
|
||||
|
||||
/**
|
||||
* Should the webview content be kept arount even when the webview is no longer visible?
|
||||
*
|
||||
* Normally a webview content is created when the webview becomes visible
|
||||
* and destroyed when the webview is hidden. Apps that have complex state
|
||||
* or UI can set the `keepAlive` property to make VS Code keep the webview
|
||||
* content around, even when the webview itself is no longer visible. When
|
||||
* the webview becomes visible again, the content is automatically restored
|
||||
* in the exact same state it was in originally
|
||||
*
|
||||
* `keepAlive` has a high memory overhead and should only be used if your
|
||||
* webview content cannot be quickly saved and restored.
|
||||
*/
|
||||
readonly keepAlive?: boolean;
|
||||
|
||||
/**
|
||||
* Root paths from which the webview can load local (filesystem) resources using the `vscode-workspace-resource:` scheme.
|
||||
*
|
||||
* Default to the root folders of the current workspace.
|
||||
*
|
||||
* Pass in an empty array to disallow access to any local resources.
|
||||
*/
|
||||
readonly localResourceRoots?: Uri[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A webview is an editor with html content, like an iframe.
|
||||
*/
|
||||
export interface Webview {
|
||||
/**
|
||||
* Title of the webview.
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* Contents of the webview.
|
||||
*/
|
||||
html: string;
|
||||
|
||||
/**
|
||||
* Content settings for the webview.
|
||||
*/
|
||||
options: WebviewOptions;
|
||||
|
||||
/**
|
||||
* The column in which the webview is showing.
|
||||
*/
|
||||
readonly viewColumn?: ViewColumn;
|
||||
|
||||
/**
|
||||
* Fired when the webview content posts a message.
|
||||
*/
|
||||
readonly onMessage: Event<any>;
|
||||
|
||||
/**
|
||||
* Fired when the webview becomes the active editor.
|
||||
*/
|
||||
readonly onBecameActive: Event<void>;
|
||||
|
||||
/**
|
||||
* Fired when the webview stops being the active editor
|
||||
*/
|
||||
readonly onBecameInactive: Event<void>;
|
||||
|
||||
/**
|
||||
* Post a message to the webview content.
|
||||
*
|
||||
* Messages are only develivered if the webview is visible.
|
||||
*
|
||||
* @param message Body of the message.
|
||||
*/
|
||||
postMessage(message: any): Thenable<any>;
|
||||
|
||||
/**
|
||||
* Dispose the webview.
|
||||
*/
|
||||
dispose(): any;
|
||||
}
|
||||
|
||||
namespace window {
|
||||
/**
|
||||
* Create and show a new webview.
|
||||
*
|
||||
* @param title Title of the webview.
|
||||
* @param column Editor column to show the new webview in.
|
||||
* @param options Webview content options.
|
||||
*/
|
||||
export function createWebview(title: string, column: ViewColumn, options: WebviewOptions): Webview;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
|
||||
/**
|
||||
* Register a [TreeDataProvider](#TreeDataProvider) for the view contributed using the extension point `views`.
|
||||
* @param viewId Id of the view contributed using the extension point `views`.
|
||||
* @param treeDataProvider A [TreeDataProvider](#TreeDataProvider) that provides tree data for the view
|
||||
* @return handle to the [treeview](#TreeView) that can be disposable.
|
||||
*/
|
||||
export function registerTreeDataProvider<T>(viewId: string, treeDataProvider: TreeDataProvider<T>): TreeView<T>;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Tree view
|
||||
*/
|
||||
export interface TreeView<T> extends Disposable {
|
||||
|
||||
/**
|
||||
* Reveal an element. By default revealed element is selected.
|
||||
*
|
||||
* In order to not to select, set the option `donotSelect` to `true`.
|
||||
*/
|
||||
reveal(element: T, options?: { donotSelect?: boolean }): Thenable<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A data provider that provides tree data
|
||||
*/
|
||||
export interface TreeDataProvider<T> {
|
||||
/**
|
||||
* An optional event to signal that an element or root has changed.
|
||||
* This will trigger the view to update the changed element/root and its children recursively (if shown).
|
||||
* To signal that root has changed, do not pass any argument or pass `undefined` or `null`.
|
||||
*/
|
||||
onDidChangeTreeData?: Event<T | undefined | null>;
|
||||
|
||||
/**
|
||||
* Get [TreeItem](#TreeItem) representation of the `element`
|
||||
*
|
||||
* @param element The element for which [TreeItem](#TreeItem) representation is asked for.
|
||||
* @return [TreeItem](#TreeItem) representation of the element
|
||||
*/
|
||||
getTreeItem(element: T): TreeItem | Thenable<TreeItem>;
|
||||
|
||||
/**
|
||||
* Get the children of `element` or root if no element is passed.
|
||||
*
|
||||
* @param element The element from which the provider gets children. Can be `undefined`.
|
||||
* @return Children of `element` or root if no element is passed.
|
||||
*/
|
||||
getChildren(element?: T): ProviderResult<T[]>;
|
||||
|
||||
/**
|
||||
* Optional method to return the parent of `element`.
|
||||
* Return `null` or `undefined` if `element` is a child of root.
|
||||
*
|
||||
* **NOTE:** This method should be implemented in order to use [TreeVie](#TreeView) API.
|
||||
*
|
||||
* @param element The element for which the parent has to be returned.
|
||||
* @return Parent of `element`.
|
||||
*/
|
||||
getParent?(element: T): ProviderResult<T>;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user