Files
Matt Bierner 73e249b3c1 Migrate all samples to eslint 9
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
2024-10-26 17:44:03 -07:00

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);