Path: blob/main/src/vs/workbench/contrib/files/common/files.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { URI } from '../../../../base/common/uri.js';6import { IEditorOptions } from '../../../../editor/common/config/editorOptions.js';7import { IWorkbenchEditorConfiguration, IEditorIdentifier, EditorResourceAccessor, SideBySideEditor } from '../../../common/editor.js';8import { EditorInput } from '../../../common/editor/editorInput.js';9import { IFilesConfiguration as PlatformIFilesConfiguration, FileChangeType, IFileService } from '../../../../platform/files/common/files.js';10import { ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';11import { ITextModelContentProvider } from '../../../../editor/common/services/resolverService.js';12import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';13import { ITextModel } from '../../../../editor/common/model.js';14import { IModelService } from '../../../../editor/common/services/model.js';15import { ILanguageService, ILanguageSelection } from '../../../../editor/common/languages/language.js';16import { ITextFileService } from '../../../services/textfile/common/textfiles.js';17import { InputFocusedContextKey } from '../../../../platform/contextkey/common/contextkeys.js';18import { IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';19import { Event } from '../../../../base/common/event.js';20import { ITextEditorOptions } from '../../../../platform/editor/common/editor.js';21import { IEditorService } from '../../../services/editor/common/editorService.js';22import { localize } from '../../../../nls.js';23import { IExpression } from '../../../../base/common/glob.js';2425/**26* Explorer viewlet id.27*/28export const VIEWLET_ID = 'workbench.view.explorer';2930/**31* Explorer file view id.32*/33export const VIEW_ID = 'workbench.explorer.fileView';3435/**36* Context Keys to use with keybindings for the Explorer and Open Editors view37*/38export const ExplorerViewletVisibleContext = new RawContextKey<boolean>('explorerViewletVisible', true, { type: 'boolean', description: localize('explorerViewletVisible', "True when the EXPLORER viewlet is visible.") });39export const FoldersViewVisibleContext = new RawContextKey<boolean>('foldersViewVisible', true, { type: 'boolean', description: localize('foldersViewVisible', "True when the FOLDERS view (the file tree within the explorer view container) is visible.") });40export const ExplorerFolderContext = new RawContextKey<boolean>('explorerResourceIsFolder', false, { type: 'boolean', description: localize('explorerResourceIsFolder', "True when the focused item in the EXPLORER is a folder.") });41export const ExplorerResourceReadonlyContext = new RawContextKey<boolean>('explorerResourceReadonly', false, { type: 'boolean', description: localize('explorerResourceReadonly', "True when the focused item in the EXPLORER is read-only.") });42export const ExplorerResourceWritableContext = ExplorerResourceReadonlyContext.toNegated();43export const ExplorerResourceParentReadOnlyContext = new RawContextKey<boolean>('explorerResourceParentReadonly', false, { type: 'boolean', description: localize('explorerResourceParentReadonly', "True when the focused item in the EXPLORER's parent is read-only.") });4445/**46* Comma separated list of editor ids that can be used for the selected explorer resource.47*/48export const ExplorerResourceAvailableEditorIdsContext = new RawContextKey<string>('explorerResourceAvailableEditorIds', '');49export const ExplorerRootContext = new RawContextKey<boolean>('explorerResourceIsRoot', false, { type: 'boolean', description: localize('explorerResourceIsRoot', "True when the focused item in the EXPLORER is a root folder.") });50export const ExplorerResourceCut = new RawContextKey<boolean>('explorerResourceCut', false, { type: 'boolean', description: localize('explorerResourceCut', "True when an item in the EXPLORER has been cut for cut and paste.") });51export const ExplorerResourceMoveableToTrash = new RawContextKey<boolean>('explorerResourceMoveableToTrash', false, { type: 'boolean', description: localize('explorerResourceMoveableToTrash', "True when the focused item in the EXPLORER can be moved to trash.") });52export const FilesExplorerFocusedContext = new RawContextKey<boolean>('filesExplorerFocus', true, { type: 'boolean', description: localize('filesExplorerFocus', "True when the focus is inside the EXPLORER view.") });53export const OpenEditorsFocusedContext = new RawContextKey<boolean>('openEditorsFocus', true, { type: 'boolean', description: localize('openEditorsFocus', "True when the focus is inside the OPEN EDITORS view.") });54export const ExplorerFocusedContext = new RawContextKey<boolean>('explorerViewletFocus', true, { type: 'boolean', description: localize('explorerViewletFocus', "True when the focus is inside the EXPLORER viewlet.") });55export const ExplorerFindProviderActive = new RawContextKey<boolean>('explorerFindProviderActive', false, { type: 'boolean', description: localize('explorerFindProviderActive', "True when the explorer tree is using the explorer find provider.") });5657// compressed nodes58export const ExplorerCompressedFocusContext = new RawContextKey<boolean>('explorerViewletCompressedFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedFocus', "True when the focused item in the EXPLORER view is a compact item.") });59export const ExplorerCompressedFirstFocusContext = new RawContextKey<boolean>('explorerViewletCompressedFirstFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedFirstFocus', "True when the focus is inside a compact item's first part in the EXPLORER view.") });60export const ExplorerCompressedLastFocusContext = new RawContextKey<boolean>('explorerViewletCompressedLastFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedLastFocus', "True when the focus is inside a compact item's last part in the EXPLORER view.") });6162export const ViewHasSomeCollapsibleRootItemContext = new RawContextKey<boolean>('viewHasSomeCollapsibleItem', false, { type: 'boolean', description: localize('viewHasSomeCollapsibleItem', "True when a workspace in the EXPLORER view has some collapsible root child.") });6364export const FilesExplorerFocusCondition = ContextKeyExpr.and(FoldersViewVisibleContext, FilesExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));65export const ExplorerFocusCondition = ContextKeyExpr.and(FoldersViewVisibleContext, ExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));6667/**68* Text file editor id.69*/70export const TEXT_FILE_EDITOR_ID = 'workbench.editors.files.textFileEditor';7172/**73* File editor input id.74*/75export const FILE_EDITOR_INPUT_ID = 'workbench.editors.files.fileEditorInput';7677/**78* Binary file editor id.79*/80export const BINARY_FILE_EDITOR_ID = 'workbench.editors.files.binaryFileEditor';8182/**83* Language identifier for binary files opened as text.84*/85export const BINARY_TEXT_FILE_MODE = 'code-text-binary';8687export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkbenchEditorConfiguration {88explorer: {89openEditors: {90visible: number;91sortOrder: 'editorOrder' | 'alphabetical' | 'fullPath';92};93autoReveal: boolean | 'focusNoScroll';94autoRevealExclude: IExpression;95enableDragAndDrop: boolean;96confirmDelete: boolean;97enableUndo: boolean;98confirmUndo: UndoConfirmLevel;99expandSingleFolderWorkspaces: boolean;100sortOrder: SortOrder;101sortOrderLexicographicOptions: LexicographicOptions;102sortOrderReverse: boolean;103decorations: {104colors: boolean;105badges: boolean;106};107incrementalNaming: 'simple' | 'smart' | 'disabled';108excludeGitIgnore: boolean;109fileNesting: {110enabled: boolean;111expand: boolean;112patterns: { [parent: string]: string };113};114autoOpenDroppedFile: boolean;115};116editor: IEditorOptions;117}118119export interface IFileResource {120resource: URI;121isDirectory?: boolean;122}123124export const enum SortOrder {125Default = 'default',126Mixed = 'mixed',127FilesFirst = 'filesFirst',128Type = 'type',129Modified = 'modified',130FoldersNestsFiles = 'foldersNestsFiles',131}132133export const enum UndoConfirmLevel {134Verbose = 'verbose',135Default = 'default',136Light = 'light',137}138139export const enum LexicographicOptions {140Default = 'default',141Upper = 'upper',142Lower = 'lower',143Unicode = 'unicode',144}145146export interface ISortOrderConfiguration {147sortOrder: SortOrder;148lexicographicOptions: LexicographicOptions;149reverse: boolean;150}151152export class TextFileContentProvider extends Disposable implements ITextModelContentProvider {153private readonly fileWatcherDisposable = this._register(new MutableDisposable());154155constructor(156@ITextFileService private readonly textFileService: ITextFileService,157@IFileService private readonly fileService: IFileService,158@ILanguageService private readonly languageService: ILanguageService,159@IModelService private readonly modelService: IModelService160) {161super();162}163164static async open(resource: URI, scheme: string, label: string, editorService: IEditorService, options?: ITextEditorOptions): Promise<void> {165await editorService.openEditor({166original: { resource: TextFileContentProvider.resourceToTextFile(scheme, resource) },167modified: { resource },168label,169options170});171}172173private static resourceToTextFile(scheme: string, resource: URI): URI {174return resource.with({ scheme, query: JSON.stringify({ scheme: resource.scheme, query: resource.query }) });175}176177private static textFileToResource(resource: URI): URI {178const { scheme, query } = JSON.parse(resource.query);179180return resource.with({ scheme, query });181}182183async provideTextContent(resource: URI): Promise<ITextModel | null> {184if (!resource.query) {185// We require the URI to use the `query` to transport the original scheme and query186// as done by `resourceToTextFile`187return null;188}189190const savedFileResource = TextFileContentProvider.textFileToResource(resource);191192// Make sure our text file is resolved up to date193const codeEditorModel = await this.resolveEditorModel(resource);194195// Make sure to keep contents up to date when it changes196if (!this.fileWatcherDisposable.value) {197const disposables = new DisposableStore();198this.fileWatcherDisposable.value = disposables;199disposables.add(this.fileService.onDidFilesChange(changes => {200if (changes.contains(savedFileResource, FileChangeType.UPDATED)) {201this.resolveEditorModel(resource, false /* do not create if missing */); // update model when resource changes202}203}));204205if (codeEditorModel) {206disposables.add(Event.once(codeEditorModel.onWillDispose)(() => this.fileWatcherDisposable.clear()));207}208}209210return codeEditorModel;211}212213private resolveEditorModel(resource: URI, createAsNeeded?: true): Promise<ITextModel>;214private resolveEditorModel(resource: URI, createAsNeeded?: boolean): Promise<ITextModel | null>;215private async resolveEditorModel(resource: URI, createAsNeeded: boolean = true): Promise<ITextModel | null> {216const savedFileResource = TextFileContentProvider.textFileToResource(resource);217218const content = await this.textFileService.readStream(savedFileResource);219220let codeEditorModel = this.modelService.getModel(resource);221if (codeEditorModel) {222this.modelService.updateModel(codeEditorModel, content.value);223} else if (createAsNeeded) {224const textFileModel = this.modelService.getModel(savedFileResource);225226let languageSelector: ILanguageSelection;227if (textFileModel) {228languageSelector = this.languageService.createById(textFileModel.getLanguageId());229} else {230languageSelector = this.languageService.createByFilepathOrFirstLine(savedFileResource);231}232233codeEditorModel = this.modelService.createModel(content.value, languageSelector, resource);234}235236return codeEditorModel;237}238}239240export class OpenEditor implements IEditorIdentifier {241242private id: number;243private static COUNTER = 0;244245constructor(private _editor: EditorInput, private _group: IEditorGroup) {246this.id = OpenEditor.COUNTER++;247}248249get editor() {250return this._editor;251}252253get group() {254return this._group;255}256257get groupId() {258return this._group.id;259}260261getId(): string {262return `openeditor:${this.groupId}:${this.id}`;263}264265isPreview(): boolean {266return !this._group.isPinned(this.editor);267}268269isSticky(): boolean {270return this._group.isSticky(this.editor);271}272273getResource(): URI | undefined {274return EditorResourceAccessor.getOriginalUri(this.editor, { supportSideBySide: SideBySideEditor.PRIMARY });275}276}277278279