mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-04-27 16:55:44 +08:00
Switches all samples to use eslint 9 with flat configs. I've tried to migrate existing settings as much as possible. However our eslint configs were also inconsistent so I've tried to align these too
377 lines
14 KiB
TypeScript
377 lines
14 KiB
TypeScript
// The module 'vscode' contains the VS Code extensibility API
|
|
// Import the module and reference it with the alias vscode in your code below
|
|
import * as vscode from 'vscode';
|
|
import { JSFIDDLE_SCHEME } from './fiddleRepository';
|
|
import { FiddleSourceControl, CONFIGURATION_FILE } from './fiddleSourceControl';
|
|
import { JSFiddleDocumentContentProvider } from './fiddleDocumentContentProvider';
|
|
import * as path from 'path';
|
|
import * as afs from './afs';
|
|
import { FiddleConfiguration, parseFiddleId } from './fiddleConfiguration';
|
|
import { firstIndex, UTF8 } from './util';
|
|
|
|
const SOURCE_CONTROL_OPEN_COMMAND = 'extension.source-control.open';
|
|
let jsFiddleDocumentContentProvider: JSFiddleDocumentContentProvider;
|
|
const fiddleSourceControlRegister = new Map<vscode.Uri, FiddleSourceControl>();
|
|
|
|
// this method is called when your extension is activated
|
|
// your extension is activated the very first time the command is executed
|
|
export async function activate(context: vscode.ExtensionContext) {
|
|
console.log('Congratulations, your extension "source-control-sample" is now active!');
|
|
|
|
jsFiddleDocumentContentProvider = new JSFiddleDocumentContentProvider();
|
|
|
|
try {
|
|
await initializeFromConfigurationFile(context);
|
|
} catch (err) {
|
|
console.log('Failed to initialize a Fiddle workspace.');
|
|
vscode.window.showErrorMessage(err);
|
|
}
|
|
|
|
const openCommand = vscode.commands.registerCommand(SOURCE_CONTROL_OPEN_COMMAND,
|
|
(fiddleId?: string, workspaceUri?: vscode.Uri) => {
|
|
tryOpenFiddle(context, fiddleId, workspaceUri);
|
|
});
|
|
context.subscriptions.push(openCommand);
|
|
|
|
context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(JSFIDDLE_SCHEME, jsFiddleDocumentContentProvider));
|
|
|
|
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.refresh",
|
|
async (sourceControlPane: vscode.SourceControl) => {
|
|
const sourceControl = await pickSourceControl(sourceControlPane);
|
|
if (sourceControl) {
|
|
sourceControl.refresh();
|
|
}
|
|
}));
|
|
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.discard",
|
|
async (sourceControlPane: vscode.SourceControl) => {
|
|
const sourceControl = await pickSourceControl(sourceControlPane);
|
|
if (sourceControl) {
|
|
sourceControl.resetFilesToCheckedOutVersion();
|
|
}
|
|
}));
|
|
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.commit",
|
|
async (sourceControlPane: vscode.SourceControl) => {
|
|
const sourceControl = await pickSourceControl(sourceControlPane);
|
|
if (sourceControl) {
|
|
sourceControl.commitAll();
|
|
}
|
|
}));
|
|
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.checkout",
|
|
async (sourceControl: FiddleSourceControl, newVersion?: number) => {
|
|
sourceControl = sourceControl || await pickSourceControl(null);
|
|
if (sourceControl) {
|
|
sourceControl.tryCheckout(newVersion);
|
|
}
|
|
}));
|
|
context.subscriptions.push(vscode.commands.registerCommand("extension.source-control.browse",
|
|
async (sourceControlPane: vscode.SourceControl) => {
|
|
const sourceControl = await pickSourceControl(sourceControlPane);
|
|
if (sourceControl) {
|
|
sourceControl.openInBrowser();
|
|
}
|
|
}));
|
|
|
|
|
|
context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(e => {
|
|
try {
|
|
// initialize new source control for manually added workspace folders
|
|
e.added.forEach(wf => {
|
|
initializeFolderFromConfigurationFile(wf, context);
|
|
});
|
|
} catch (ex) {
|
|
vscode.window.showErrorMessage(ex.message);
|
|
} finally {
|
|
// dispose source control for removed workspace folders
|
|
e.removed.forEach(wf => {
|
|
unregisterFiddleSourceControl(wf.uri);
|
|
});
|
|
}
|
|
}));
|
|
}
|
|
|
|
async function pickSourceControl(sourceControlPane: vscode.SourceControl): Promise<FiddleSourceControl | undefined> {
|
|
if (sourceControlPane) {
|
|
return fiddleSourceControlRegister.get(sourceControlPane.rootUri);
|
|
}
|
|
|
|
// todo: when/if the SourceControl exposes a 'selected' property, use that instead
|
|
|
|
if (fiddleSourceControlRegister.size === 0) {
|
|
return undefined;
|
|
} else if (fiddleSourceControlRegister.size === 1) {
|
|
return [...fiddleSourceControlRegister.values()][0];
|
|
} else {
|
|
|
|
const picks = [...fiddleSourceControlRegister.values()].map(fsc => new RepositoryPick(fsc));
|
|
|
|
if (vscode.window.activeTextEditor) {
|
|
const activeWorkspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.window.activeTextEditor.document.uri);
|
|
const activeSourceControl = activeWorkspaceFolder && fiddleSourceControlRegister.get(activeWorkspaceFolder.uri);
|
|
const activeIndex = firstIndex(picks, pick => pick.fiddleSourceControl === activeSourceControl);
|
|
|
|
// if there is an active editor, move its folder to be the first in the pick list
|
|
if (activeIndex > -1) {
|
|
picks.unshift(...picks.splice(activeIndex, 1));
|
|
}
|
|
}
|
|
|
|
const pick = await vscode.window.showQuickPick(picks, { placeHolder: 'Select repository' });
|
|
return pick && pick.fiddleSourceControl;
|
|
}
|
|
}
|
|
|
|
async function tryOpenFiddle(context: vscode.ExtensionContext, fiddleId?: string, workspaceUri?: vscode.Uri): Promise<void> {
|
|
try {
|
|
await openFiddle(context, fiddleId, workspaceUri);
|
|
} catch (ex) {
|
|
vscode.window.showErrorMessage(ex);
|
|
console.log(ex);
|
|
}
|
|
}
|
|
|
|
async function openFiddle(context: vscode.ExtensionContext, fiddleId?: string, workspaceUri?: vscode.Uri) {
|
|
if (workspaceUri && fiddleSourceControlRegister.has(workspaceUri)) {
|
|
vscode.window.showErrorMessage("Another Fiddle was already open in this workspace. Open a new workspace first.");
|
|
}
|
|
|
|
if (!fiddleId) {
|
|
fiddleId = (await vscode.window.showInputBox({ prompt: 'Paste JSFiddle ID and optionally version', placeHolder: 'slug or slug/version, e.g. u8B29/1', value: 'demo' })) || '';
|
|
}
|
|
|
|
const workspaceFolder =
|
|
workspaceUri ?
|
|
vscode.workspace.getWorkspaceFolder(workspaceUri) :
|
|
await selectWorkspaceFolder(context, fiddleId);
|
|
|
|
if (! await clearWorkspaceFolder(workspaceFolder.uri)) {
|
|
return;
|
|
}
|
|
|
|
// show the file explorer with the three new files
|
|
vscode.commands.executeCommand("workbench.view.explorer");
|
|
|
|
// register source control
|
|
const fiddleSourceControl = await FiddleSourceControl.fromFiddleId(fiddleId, context, workspaceFolder, true);
|
|
|
|
registerFiddleSourceControl(fiddleSourceControl, context);
|
|
showFiddleInEditor(fiddleSourceControl);
|
|
}
|
|
|
|
async function showFiddleInEditor(fiddleSourceControl: FiddleSourceControl): Promise<void> {
|
|
// open the 3 fiddle parts in 3 view columns
|
|
await openDocumentInColumn(fiddleSourceControl.getRepository().createLocalResourcePath('html'), vscode.ViewColumn.One);
|
|
await openDocumentInColumn(fiddleSourceControl.getRepository().createLocalResourcePath('js'), vscode.ViewColumn.Two);
|
|
await openDocumentInColumn(fiddleSourceControl.getRepository().createLocalResourcePath('css'), vscode.ViewColumn.Three);
|
|
}
|
|
|
|
function registerFiddleSourceControl(fiddleSourceControl: FiddleSourceControl, context: vscode.ExtensionContext) {
|
|
// update the fiddle document content provider with the latest content
|
|
jsFiddleDocumentContentProvider.updated(fiddleSourceControl.getFiddle());
|
|
|
|
// every time the repository is updated with new fiddle version, notify the content provider
|
|
fiddleSourceControl.onRepositoryChange(fiddle => jsFiddleDocumentContentProvider.updated(fiddle));
|
|
|
|
if (fiddleSourceControlRegister.has(fiddleSourceControl.getWorkspaceFolder().uri)) {
|
|
// the folder was already under source control
|
|
const previousSourceControl = fiddleSourceControlRegister.get(fiddleSourceControl.getWorkspaceFolder().uri)!;
|
|
previousSourceControl.dispose();
|
|
}
|
|
|
|
fiddleSourceControlRegister.set(fiddleSourceControl.getWorkspaceFolder().uri, fiddleSourceControl);
|
|
|
|
context.subscriptions.push(fiddleSourceControl);
|
|
}
|
|
|
|
function unregisterFiddleSourceControl(folderUri: vscode.Uri): void {
|
|
if (fiddleSourceControlRegister.has(folderUri)) {
|
|
const previousSourceControl = fiddleSourceControlRegister.get(folderUri)!;
|
|
previousSourceControl.dispose();
|
|
|
|
fiddleSourceControlRegister.delete(folderUri);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When the extension starts up, it must visit all workspace folders to see if any of them are fiddles.
|
|
* @param context extension context
|
|
*/
|
|
async function initializeFromConfigurationFile(context: vscode.ExtensionContext): Promise<void> {
|
|
if (!vscode.workspace.workspaceFolders) {
|
|
return;
|
|
}
|
|
|
|
const folderPromises = vscode.workspace.workspaceFolders.map(async (folder) => await initializeFolderFromConfigurationFile(folder, context));
|
|
await Promise.all(folderPromises);
|
|
}
|
|
|
|
async function initializeFolderFromConfigurationFile(folder: vscode.WorkspaceFolder, context: vscode.ExtensionContext): Promise<void> {
|
|
const configurationPath = path.join(folder.uri.fsPath, CONFIGURATION_FILE);
|
|
|
|
const configFileExists = await afs.exists(configurationPath);
|
|
|
|
if (configFileExists) {
|
|
const data = await afs.readFile(configurationPath);
|
|
const fiddleConfiguration: FiddleConfiguration = JSON.parse(data.toString(UTF8));
|
|
const fiddleSourceControl = await FiddleSourceControl.fromConfiguration(fiddleConfiguration, folder, context, !fiddleConfiguration.downloaded);
|
|
registerFiddleSourceControl(fiddleSourceControl, context);
|
|
if (!fiddleConfiguration.downloaded) {
|
|
// the fiddle was not downloaded before the extension restart, so let's show it now
|
|
showFiddleInEditor(fiddleSourceControl);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function selectWorkspaceFolder(context: vscode.ExtensionContext, fiddleId: string): Promise<vscode.WorkspaceFolder | undefined> {
|
|
let selectedFolder: vscode.WorkspaceFolder | undefined;
|
|
let workspaceFolderUri: vscode.Uri | undefined;
|
|
let workspaceFolderIndex: number | undefined;
|
|
let folderOpeningMode: FolderOpeningMode;
|
|
|
|
const folderPicks: WorkspaceFolderPick[] = [newFolderPick];
|
|
|
|
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
|
|
folderPicks.push(newWorkspaceFolderPick);
|
|
|
|
for (const wf of vscode.workspace.workspaceFolders) {
|
|
const content = await afs.readdir(wf.uri.fsPath);
|
|
folderPicks.push(new ExistingWorkspaceFolderPick(wf, content));
|
|
}
|
|
}
|
|
|
|
const selectedFolderPick: WorkspaceFolderPick =
|
|
folderPicks.length === 1 ?
|
|
folderPicks[0] :
|
|
await vscode.window.showQuickPick(folderPicks, {
|
|
canPickMany: false, ignoreFocusOut: true, placeHolder: 'Pick workspace folder to create files in.'
|
|
});
|
|
|
|
if (!selectedFolderPick) {
|
|
return null;
|
|
}
|
|
|
|
if (selectedFolderPick instanceof ExistingWorkspaceFolderPick) {
|
|
selectedFolder = selectedFolderPick.workspaceFolder;
|
|
workspaceFolderIndex = selectedFolder.index;
|
|
workspaceFolderUri = selectedFolder.uri;
|
|
}
|
|
|
|
// eslint-disable-next-line prefer-const
|
|
folderOpeningMode = selectedFolderPick.folderOpeningMode;
|
|
|
|
if (!workspaceFolderUri && !selectedFolder) {
|
|
const folderUris = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false, canSelectMany: false, openLabel: 'Select folder' });
|
|
if (!folderUris) {
|
|
return null;
|
|
}
|
|
|
|
workspaceFolderUri = folderUris[0];
|
|
// was such workspace folder already open?
|
|
workspaceFolderIndex = vscode.workspace.workspaceFolders && firstIndex(vscode.workspace.workspaceFolders, (folder1) => folder1.uri.toString() === workspaceFolderUri!.toString());
|
|
}
|
|
|
|
if (! await clearWorkspaceFolder(workspaceFolderUri)) {
|
|
return null;
|
|
}
|
|
|
|
const fiddleConfiguration = parseFiddleId(fiddleId);
|
|
|
|
// save folder configuration
|
|
FiddleSourceControl.saveConfiguration(workspaceFolderUri, fiddleConfiguration);
|
|
|
|
if (folderOpeningMode === FolderOpeningMode.AddToWorkspace || folderOpeningMode === undefined) {
|
|
const workSpacesToReplace = typeof workspaceFolderIndex === 'number' && workspaceFolderIndex > -1 ? 1 : 0;
|
|
if (workspaceFolderIndex === undefined || workspaceFolderIndex < 0) {
|
|
workspaceFolderIndex = 0;
|
|
}
|
|
|
|
// replace or insert the workspace
|
|
if (workspaceFolderUri) {
|
|
vscode.workspace.updateWorkspaceFolders(workspaceFolderIndex, workSpacesToReplace, { uri: workspaceFolderUri });
|
|
}
|
|
} else if (folderOpeningMode === FolderOpeningMode.OpenFolder) {
|
|
vscode.commands.executeCommand("vscode.openFolder", workspaceFolderUri);
|
|
}
|
|
|
|
return selectedFolder;
|
|
}
|
|
|
|
async function clearWorkspaceFolder(workspaceFolderUri: vscode.Uri): Promise<boolean> {
|
|
|
|
if (!workspaceFolderUri) {
|
|
return undefined;
|
|
}
|
|
|
|
// check if the workspace is empty, or clear it
|
|
const existingWorkspaceFiles: string[] = await afs.readdir(workspaceFolderUri.fsPath);
|
|
if (existingWorkspaceFiles.length > 0) {
|
|
const answer = await vscode.window.showQuickPick(["Yes", "No"],
|
|
{ placeHolder: `Remove ${existingWorkspaceFiles.length} file(s) from the folder ${workspaceFolderUri.fsPath} before cloning the remote repository?` });
|
|
if (!answer) {
|
|
return false;
|
|
}
|
|
|
|
if (answer === "Yes") {
|
|
existingWorkspaceFiles
|
|
.forEach(async filename =>
|
|
await afs.unlink(path.join(workspaceFolderUri.fsPath, filename)));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
class RepositoryPick implements vscode.QuickPickItem {
|
|
|
|
constructor(public readonly fiddleSourceControl: FiddleSourceControl) { }
|
|
|
|
get label(): string {
|
|
return this.fiddleSourceControl.getSourceControl().label;
|
|
}
|
|
}
|
|
|
|
async function openDocumentInColumn(fileName: string, column: vscode.ViewColumn): Promise<void> {
|
|
const uri = vscode.Uri.file(fileName);
|
|
|
|
// assuming the file was saved, let's open it in a view column
|
|
const doc = await vscode.workspace.openTextDocument(uri);
|
|
|
|
await vscode.window.showTextDocument(doc, { viewColumn: column });
|
|
}
|
|
|
|
abstract class WorkspaceFolderPick implements vscode.QuickPickItem {
|
|
abstract get label(): string;
|
|
constructor(public folderOpeningMode: FolderOpeningMode) { }
|
|
}
|
|
|
|
class ExistingWorkspaceFolderPick extends WorkspaceFolderPick {
|
|
|
|
constructor(public readonly workspaceFolder: vscode.WorkspaceFolder, private content: string[]) {
|
|
super(FolderOpeningMode.AddToWorkspace);
|
|
}
|
|
|
|
get label(): string {
|
|
return this.workspaceFolder.name;
|
|
}
|
|
|
|
get description(): string {
|
|
return this.workspaceFolder.uri.fsPath;
|
|
}
|
|
|
|
get detail(): string {
|
|
return this.content.length ? `${this.content.length} files/directories may need to be removed..` : null;
|
|
}
|
|
}
|
|
|
|
class NewWorkspaceFolderPick extends WorkspaceFolderPick {
|
|
constructor(public label: string, folderOpeningMode: FolderOpeningMode) {
|
|
super(folderOpeningMode);
|
|
}
|
|
}
|
|
|
|
enum FolderOpeningMode {
|
|
AddToWorkspace, OpenFolder
|
|
}
|
|
|
|
const newWorkspaceFolderPick = new NewWorkspaceFolderPick("Select/create a local folder to add to this workspace...", FolderOpeningMode.AddToWorkspace);
|
|
const newFolderPick = new NewWorkspaceFolderPick("Select/create a local folder...", FolderOpeningMode.OpenFolder);
|