Mecanismo de implementación del espacio de trabajo del código de Vscode

El espacio de trabajo es una parte importante del editor, lleva la conexión entre el editor y los archivos locales, y agrega, elimina, modifica y verifica archivos. A continuación, presentaré la creación del espacio de trabajo de vscode. Del mismo modo, sabemos que cuando se abre el software vscode, no hay un espacio de trabajo predeterminado. Aquí lo he modificado para que apunte al espacio de trabajo predeterminado cuando se inicia el software.

Creación del directorio del espacio de trabajo

Primero veamos el directorio de almacenamiento de datos de vscode en la computadora local.
inserte la descripción de la imagen aquí
Todos los datos almacenados en caché están aquí. También podemos crear los datos que deben almacenarse en caché en el proyecto.
Entonces, ¿cómo obtener y configurar el directorio de almacenamiento de datos del software? Electron proporciona api oficial

  • app.getPath()
  • app.setPath()

Dirección del documento oficial: https://www.electronjs.org/zh/docs/latest/api/app#appgetpathname

inserte la descripción de la imagen aquí
En correspondencia con el método setPath,
inserte la descripción de la imagen aquí
veamos cómo vscode crea
el src/main.js del directorio de almacenamiento de datos del software

const userDataPath = getUserDataPath(args);
app.setPath('userData', userDataPath);

Saltar al método getUserDataPath

function factory(path, os, productName, cwd) {
    
    

		/**
		 * @param {NativeParsedArgs} cliArgs
		 *
		 * @returns {string}
		 */
		function getUserDataPath(cliArgs) {
    
    
			const userDataPath = doGetUserDataPath(cliArgs);
			const pathsToResolve = [userDataPath];
			if (!path.isAbsolute(userDataPath)) {
    
    
				pathsToResolve.unshift(cwd);
			}

			return path.resolve(...pathsToResolve);
		}

		/**
		 * @param {NativeParsedArgs} cliArgs
		 *
		 * @returns {string}
		 */
		function doGetUserDataPath(cliArgs) {
    
    

			// 1. Support portable mode
			const portablePath = process.env['VSCODE_PORTABLE'];
			if (portablePath) {
    
    
				return path.join(portablePath, 'user-data');
			}

			// 2. Support global VSCODE_APPDATA environment variable
			let appDataPath = process.env['VSCODE_APPDATA'];
			if (appDataPath) {
    
    
				return path.join(appDataPath, productName);
			}
			// 3. Support explicit --user-data-dir
			const cliPath = cliArgs['user-data-dir'];
			if (cliPath) {
    
    
				return cliPath;
			}

			// 4. Otherwise check per platform
			switch (process.platform) {
    
    
				case 'win32':
					appDataPath = process.env['APPDATA'];
					if (!appDataPath) {
    
    
						const userProfile = process.env['USERPROFILE'];
						if (typeof userProfile !== 'string') {
    
    
							throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable');
						}

						appDataPath = path.join(userProfile, 'AppData', 'Roaming');
					}
					break;
				case 'darwin':
					appDataPath = path.join(os.homedir(), 'Library', 'Application Support');
					break;
				case 'linux':
					appDataPath = process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
					break;
				default:
					throw new Error('Platform not supported');
			}

			return path.join(appDataPath, productName);
		}

		return {
    
    
			getUserDataPath
		};
	}

El método es la interfaz de programación de aplicaciones integrada del módulo os.homedir()nodejs , que se utiliza para obtener la ruta del directorio de inicio del usuario actual .os

En este punto, se ha creado el directorio general de datos de caché de software.

A continuación creamos un directorio bajo el directorio general workspace-contentcomo directorio del espacio de trabajo para almacenar archivos.

src/vs/platform/environment/common/environment.ts
agrega el nombre del espacio de trabajo en el servicio de entorno vscode

export interface IEnvironmentService {
    
    
...
    workspaceContent: URI;
   ... 
}    

Implemente la interfaz
src/vs/platform/environment/common/environmentService.ts

export abstract class AbstractNativeEnvironmentService implements INativeEnvironmentService {
    
    
...
    @memoize
    get workspaceContent(): URI {
    
     return URI.file(join(this.userDataPath, 'workspace-content')); }
...
}

El directorio del espacio de trabajo predeterminado crea
src/vs/code/electron-main/main.ts

Promise.all<string | undefined>([
    environmentMainService.extensionsPath,
    environmentMainService.codeCachePath,
    environmentMainService.logsPath,
    environmentMainService.globalStorageHome.fsPath,
    environmentMainService.workspaceStorageHome.fsPath,
    environmentMainService.localHistoryHome.fsPath,
    environmentMainService.backupHome,

    environmentMainService.workspaceTemplate.fsPath,
].map(path => path ? FSPromises.mkdir(path, {
    
     recursive: true }) : undefined)),

mkdirCada directorio de almacenamiento de datos se crea llamando al método aquí .

Sabemos que no hay un espacio de trabajo predeterminado cuando se abre el software vscode, aquí lo he modificado para que apunte al espacio de trabajo predeterminado cuando se inicia el software.

src/vs/plataforma/windows/electron-main/windowsMainService.ts

const workspaceContentUri = this.environmentService.workspaceContent;
const workspacePath: IPathToOpen[] = [
    {
    
    
        workspace: {
    
    
            id: '1',
            uri: workspaceContentUri
        },
        type: 2, // 2 File is a directory.
        exists: true
    }
];
// Identify things to open from open config

// const pathsToOpen = this.getPathsToOpen(openConfig); //愿逻辑
const pathsToOpen = workspacePath;
...

Aquí la entrada del espacio de trabajo de carga está escrita hasta la saciedad.

creación de información de archivos

¿Cómo lee vscode el uri del espacio de trabajo del archivo para crear una lista de espacios de trabajo? Veamos
src/vs/workbench/services/configuration/browser/configurationService.ts

private async toValidWorkspaceFolders(workspaceFolders: WorkspaceFolder[]): Promise<WorkspaceFolder[]> {
    
    
		const validWorkspaceFolders: WorkspaceFolder[] = [];
		for (const workspaceFolder of workspaceFolders) {
    
    
			try {
    
    
				const result = await this.fileService.stat(workspaceFolder.uri);
				if (!result.isDirectory) {
    
    
					continue;
				}
			} catch (e) {
    
    
				this.logService.warn(`Ignoring the error while validating workspace folder ${
      
      workspaceFolder.uri.toString()} - ${
      
      toErrorMessage(e)}`);
			}
			validWorkspaceFolders.push(workspaceFolder);
		}
		return validWorkspaceFolders;
	}

statLa información del archivo en el espacio de trabajo se lee aquí, y la información del archivo obtenida por el módulo node fs a través de stat se vuelve a encapsular aquí , y podemos modificar los datos en él según los requisitos. Veamos el lugar donde se definen los datos del archivo.

src/vs/plataforma/archivos/common/fileService.ts

async stat(resource: URI): Promise<IFileStatWithPartialMetadata> {
    
    
		const provider = await this.withProvider(resource);

		const stat = await provider.stat(resource);

		return this.toFileStat(provider, resource, stat, undefined, true, () => false /* Do not resolve any children */);
	}

siguiendo en

private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | {
    
     type: FileType } & Partial<IStat>, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise<IFileStat> {
    
    
		const {
    
     providerExtUri } = this.getExtUri(provider);
		let isAudioFolderFlag = await this.exists(providerExtUri.joinPath(resource, 'main.audio'));
		// convert to file stat
		const fileStat: IFileStat = {
    
    
			resource,
			name: providerExtUri.basename(resource),
			isFile: (stat.type & FileType.File) !== 0,
			isDirectory: (stat.type & FileType.Directory) !== 0,
			isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0,
			mtime: stat.mtime,
			ctime: stat.ctime,
			size: stat.size,
			readonly: Boolean((stat.permissions ?? 0) & FilePermission.Readonly) || Boolean(provider.capabilities & FileSystemProviderCapabilities.Readonly),
			etag: etag({
    
     mtime: stat.mtime, size: stat.size }),
			children: undefined,
			isAudioFolder: isAudioFolderFlag
		};
		// check to recurse for directories
		if (fileStat.isDirectory && recurse(fileStat, siblings)) {
    
    
			try {
    
    
				const entries = await provider.readdir(resource);
				// console.log('entries---', entries);
				const resolvedEntries = await Promises.settled(entries.map(async ([name, type]) => {
    
    
					try {
    
    
						const childResource = providerExtUri.joinPath(resource, name);
						// const childStat = resolveMetadata ? await provider.stat(childResource) : { type };
						/**
						 * @todo 获取子文件数据,愿逻辑由resolveMetadata开关控制
						 */
						const childStat = await provider.stat(childResource)
						return await this.toFileStat(provider, childResource, childStat, entries.length, resolveMetadata, recurse);
					} catch (error) {
    
    
						this.logService.trace(error);

						return null; // can happen e.g. due to permission errors
					}
				}));
				// make sure to get rid of null values that signal a failure to resolve a particular entry
				fileStat.children = coalesce(resolvedEntries);
			} catch (error) {
    
    
				this.logService.trace(error);

				fileStat.children = []; // gracefully handle errors, we may not have permissions to read
			}

			return fileStat;
		}

		return fileStat;
	}

Se puede ver que la información del archivo generada aquí
src/vs/workbench/contrib/files/common/explorerModel.ts
es el modelo de datos del archivo generado por la capa Modelo. más tarde se utiliza para representar la página.

static create(fileService: IFileService, configService: IConfigurationService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem {
    
    

		let stat: ExplorerItem;
		if (raw.isAudioFolder) {
    
    
			stat = new ExplorerItem(raw.resource, fileService, configService, parent, false, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, false, raw.ctime);
		} else {
    
    
			stat = new ExplorerItem(raw.resource, fileService, configService, parent, raw.isDirectory, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, !raw.isFile && !raw.isDirectory, raw.ctime);
		}
		stat.isAudioFolder = (raw.isAudioFolder as boolean);
		if (stat.isDirectory &&!stat.isAudioFolder) {
    
    

			stat._isDirectoryResolved = !!raw.children || (!!resolveTo && resolveTo.some((r) => {
    
    
				return isEqualOrParent(r, stat.resource);
			}));

			if (raw.children) {
    
    
				for (let i = 0, len = raw.children.length; i < len; i++) {
    
    
					const child = ExplorerItem.create(fileService, configService, raw.children[i], stat, resolveTo);
					stat.addChild(child);
				}
			}
		}
		// 总 stat
		return stat;
	}

dibujar lista de archivos

src/vs/workbench/contrib/files/browser/views/explorerView.ts

const promise = this.tree.setInput(input, viewState).then(async () => {
    
    
			
				...
		});

la entrada es la información del archivo del espacio de trabajo total
inserte la descripción de la imagen aquí

Función de clasificación de archivos

src/vs/workbench/contrib/files/common/files.ts
接口

export const enum SortOrder {
    
    
    Default = 'default',
    Mixed = 'mixed',
    FilesFirst = 'filesFirst',
    Type = 'type',
    ModifiedLower = 'modifiedLower',
    ModifiedUp = 'modifiedUp',
    NameLower = 'nameLower',
    NameUp = 'nameUp',
    createTimeLower = 'createTimeLower',
    createTimeUp = 'createTimeUp',
    FoldersNestsFiles = 'foldersNestsFiles',
}

La configuración en el archivo de configuración
src/vs/workbench/contrib/files/browser/files.contribution.ts

'explorer.sortOrder': {
    
    
    'type': 'string',
    'enum': [SortOrder.Default, SortOrder.Mixed, SortOrder.FilesFirst, SortOrder.Type, SortOrder.ModifiedLower, SortOrder.ModifiedUp, SortOrder.NameLower, SortOrder.NameUp, SortOrder.createTimeLower, SortOrder.createTimeUp, SortOrder.FoldersNestsFiles],
    'default': SortOrder.Default,
    'enumDescriptions': [
        nls.localize('sortOrder.default', 'Files and folders are sorted by their names. Folders are displayed before files.'),
        nls.localize('sortOrder.mixed', 'Files and folders are sorted by their names. Files are interwoven with folders.'),
        nls.localize('sortOrder.filesFirst', 'Files and folders are sorted by their names. Files are displayed before folders.'),
        nls.localize('sortOrder.type', 'Files and folders are grouped by extension type then sorted by their names. Folders are displayed before files.'),
        nls.localize('sortOrder.ModifiedLower', 'Files and folders are sorted by last modified date in descending order. Folders are displayed before  files.'),
        nls.localize('sortOrder.ModifiedUp', 'Files and folders are sorted by latest modified date in descending order. Folders are displayed before  files.'),
        nls.localize('sortOrder.nameLower', 'Files and folders are sorted by name'),
        nls.localize('sortOrder.nameUp', 'Files and folders are sorted by name'),
        nls.localize('sortOrder.createTimeLower', 'Files and folders are sorted by create time'),
        nls.localize('sortOrder.createTimeUp', 'Files and folders are sorted by create time'),
        nls.localize('sortOrder.foldersNestsFiles', 'Files and folders are sorted by their names. Folders are displayed before files. Files with nested children are displayed before other files.')
    ],
    'markdownDescription': nls.localize('sortOrder', "Controls the property-based sorting of files and folders in the explorer. When `#explorer.experimental.fileNesting.enabled#` is enabled, also controls sorting of nested files.")
},

lograr

export class FileSorter implements ITreeSorter<ExplorerItem> {
    
    

	constructor(
		@IExplorerService private readonly explorerService: IExplorerService,
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
	) {
    
     }

	compare(statA: ExplorerItem, statB: ExplorerItem): number {
    
    
		// Do not sort roots
		if (statA.isRoot) {
    
    
			if (statB.isRoot) {
    
    
				const workspaceA = this.contextService.getWorkspaceFolder(statA.resource);
				const workspaceB = this.contextService.getWorkspaceFolder(statB.resource);
				return workspaceA && workspaceB ? (workspaceA.index - workspaceB.index) : -1;
			}

			return -1;
		}

		if (statB.isRoot) {
    
    
			return 1;
		}

		const sortOrder = this.explorerService.sortOrderConfiguration.sortOrder;
		const lexicographicOptions = this.explorerService.sortOrderConfiguration.lexicographicOptions;

		let compareFileNames;
		let compareFileExtensions;
		switch (lexicographicOptions) {
    
    
			case 'upper':
				compareFileNames = compareFileNamesUpper;
				compareFileExtensions = compareFileExtensionsUpper;
				break;
			case 'lower':
				compareFileNames = compareFileNamesLower;
				compareFileExtensions = compareFileExtensionsLower;
				break;
			case 'unicode':
				compareFileNames = compareFileNamesUnicode;
				compareFileExtensions = compareFileExtensionsUnicode;
				break;
			default:
				// 'default'
				compareFileNames = compareFileNamesDefault;
				compareFileExtensions = compareFileExtensionsDefault;
		}

		// Sort Directories
		switch (sortOrder) {
    
    
			case 'type':
				if (statA.isDirectory && !statB.isDirectory) {
    
    
					return -1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
    
    
					return 1;
				}

				if (statA.isDirectory && statB.isDirectory) {
    
    
					return compareFileNames(statA.name, statB.name);
				}

				break;

			case 'filesFirst':
				if (statA.isDirectory && !statB.isDirectory) {
    
    
					return 1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
    
    
					return -1;
				}

				break;

			case 'foldersNestsFiles':
				if (statA.isDirectory && !statB.isDirectory) {
    
    
					return -1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
    
    
					return 1;
				}

				if (statA.hasNests && !statB.hasNests) {
    
    
					return -1;
				}

				if (statB.hasNests && !statA.hasNests) {
    
    
					return 1;
				}

				break;

			case 'mixed':
				break; // not sorting when "mixed" is on

			default: /* 'default', 'modified' */
				if (statA.isDirectory && !statB.isDirectory) {
    
    
					return -1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
    
    
					return 1;
				}

				break;
		}

		// Sort Files
		switch (sortOrder) {
    
    
			case 'createTimeLower':
				return (statA.mcime && statB.mcime && statA.mcime < statB.mcime) ? -1 : 1;
			case 'createTimeUp':
				return (statA.mcime && statB.mcime && statA.mcime < statB.mcime) ? 1 : -1;
			case 'nameUp':
				return statB.name.localeCompare(statA.name);
			case 'nameLower':
				return statA.name.localeCompare(statB.name);
			case 'type':
				return compareFileExtensions(statA.name, statB.name);

			case 'modifiedLower':
				if (statA.mtime !== statB.mtime) {
    
    
					return (statA.mtime && statB.mtime && statA.mtime < statB.mtime) ? 1 : -1;
				}

				return compareFileNames(statA.name, statB.name);

			case 'modifiedUp':
				if (statA.mtime !== statB.mtime) {
    
    
					return (statA.mtime && statB.mtime && statA.mtime < statB.mtime) ? -1 : 1;
				}

				return compareFileNames(statA.name, statB.name);

			default: /* 'default', 'mixed', 'filesFirst' */
				return compareFileNames(statA.name, statB.name);
		}
	}
}

Registrar llamada de menú

// 按创建日期排序

// 一级菜单
const explorerSortByCreateTimeSubMenu = new MenuId('sortOrderByCreateTime');
MenuRegistry.appendMenuItem(MenuId.ViewTitle, <ISubmenuItem>{
    
    
    submenu: explorerSortByCreateTimeSubMenu,
    title: nls.localize('sortOrderByCreateTime', "按创建日期排序"),
    when: ContextKeyExpr.equals('view', VIEW_ID),
    group: 'group',
    order: 3,
});


// 二级菜单
registerAction2(class extends Action2 {
    
    
    constructor() {
    
    
        super({
    
    
            id: 'sortOrder.create.time.lower',
            title: nls.localize('sortOrder.create.time.lower', "最新至最旧"),
            menu: {
    
    
                id: explorerSortByCreateTimeSubMenu,
                order: 1,
            }
        });
    }

    async run(accessor: ServicesAccessor): Promise<void> {
    
    
        const config = accessor.get(IConfigurationService);
        const paneCompositeService = accessor.get(IPaneCompositePartService);
        const explorerService = accessor.get(IExplorerService);

        config.updateValue('explorer.sortOrder', SortOrder.createTimeLower);
        await paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);
        await explorerService.refresh();
    }
});

registerAction2(class extends Action2 {
    
    
    constructor() {
    
    
        super({
    
    
            id: 'sortOrder.create.time.up',
            title: nls.localize('sortOrder.create.time.up', "最旧至最新"),
            menu: {
    
    
                id: explorerSortByCreateTimeSubMenu,
                order: 2,
            }
        });
    }

    async run(accessor: ServicesAccessor): Promise<void> {
    
    
        const config = accessor.get(IConfigurationService);
        const paneCompositeService = accessor.get(IPaneCompositePartService);
        const explorerService = accessor.get(IExplorerService);

        config.updateValue('explorer.sortOrder', SortOrder.createTimeUp);
        await paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);
        await explorerService.refresh();
    }
});

registerAction2(class extends Action2 {
    
    
    constructor() {
    
    
        super({
    
    
            id: 'sortOrder.create.time.default',
            title: nls.localize('sortOrder.create.time.default', "默认排序"),
            menu: {
    
    
                id: explorerSortByCreateTimeSubMenu,
                order: 3,
            }
        });
    }

    async run(accessor: ServicesAccessor): Promise<void> {
    
    
        const config = accessor.get(IConfigurationService);
        const paneCompositeService = accessor.get(IPaneCompositePartService);
        const explorerService = accessor.get(IExplorerService);

        config.updateValue('explorer.sortOrder', SortOrder.Default);
        await paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);
        await explorerService.refresh();
    }
});

Mecanismo de creación del espacio de trabajo de la versión web de Vscode

https://insiders.vscode.dev/La
página web utiliza window.showOpenFilePickerla API del navegador
Dirección del documento oficial: https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API

src/vs/workbench/services/dialogs/browser/fileDialogService.ts

...
try {
    
    
	([fileHandle] = await window.showOpenFilePicker({
    
     multiple: false }));
} catch (error) {
    
    
	return; // `showOpenFilePicker` will throw an error when the user cancels
}
...

El resto del proceso es el mismo.

Supongo que te gusta

Origin blog.csdn.net/woyebuzhidao321/article/details/131495855
Recomendado
Clasificación