Skip to content

Commit

Permalink
feat: add support for creating custom editors for files
Browse files Browse the repository at this point in the history
  • Loading branch information
CompuIves committed Nov 13, 2023
1 parent 0da94f1 commit c816018
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 18 deletions.
15 changes: 13 additions & 2 deletions demo/src/features/customView.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { IDialogService } from 'vscode/services'
import { IDialogService, EditorInput } from 'vscode/services'
import { registerCustomView, registerEditorPane, ViewContainerLocation } from '@codingame/monaco-vscode-views-service-override'
import * as monaco from 'monaco-editor'
import iconUrl from '../Visual_Studio_Code_1.35_icon.svg?url'
import { ServicesAccessor } from 'vscode/vscode/vs/platform/instantiation/common/instantiation'

registerCustomView({
id: 'custom-view',
Expand Down Expand Up @@ -43,7 +44,7 @@ registerCustomView({
}]
})

const { CustomEditorInput } = registerEditorPane({
const { CustomEditorInput, registerEditor } = registerEditorPane({
id: 'custom-editor-pane',
name: 'Custom editor pane',
renderBody (container) {
Expand All @@ -54,9 +55,19 @@ const { CustomEditorInput } = registerEditorPane({

return {
dispose () {
},

async setInput (_accessor: ServicesAccessor, input: EditorInput) {
if (input.resource != null) {
container.innerHTML = 'Opened file: ' + input.resource.path
} else {
container.innerHTML = 'This is a custom editor pane<br />You can render anything you want here'
}
}
}
}
})

registerEditor('*.customeditor')

export { CustomEditorInput }
4 changes: 4 additions & 0 deletions demo/src/features/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ $$
$$`
))

fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/test.customeditor'), `
Custom Editor!`
))

fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/test.css'), `
h1 {
color: DeepSkyBlue;
Expand Down
105 changes: 89 additions & 16 deletions src/service-override/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ import 'vs/workbench/contrib/languageDetection/browser/languageDetection.contrib
import 'vs/workbench/contrib/files/browser/files.contribution.js?include=registerConfiguration'
import 'vs/workbench/contrib/files/browser/files.contribution.js?exclude=registerConfiguration'
import { Codicon } from 'vs/base/common/codicons'
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'
import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'
import { IEditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget'
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'
import { IEditorResolverService, RegisteredEditorInfo, RegisteredEditorOptions, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'
import { EditorResolverService } from 'vs/workbench/services/editor/browser/editorResolverService'
import { BreadcrumbsService, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs'
import { IContextViewService } from 'vs/platform/contextview/browser/contextView'
import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'
import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput'
import { EditorExtensions, Verbosity } from 'vs/workbench/common/editor'
import { IEditorOptions } from 'vs/platform/editor/common/editor'
import { EditorExtensions, IEditorOpenContext, Verbosity } from 'vs/workbench/common/editor'
import { IEditorOptions, IResourceEditorInput, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'
import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'
import { ITextEditorService, TextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'
import { CodeEditorService } from 'vs/workbench/services/editor/browser/codeEditorService'
Expand Down Expand Up @@ -87,14 +87,16 @@ import { ConfirmResult } from 'vs/platform/dialogs/common/dialogs'
import { ILayoutService } from 'vs/platform/layout/browser/layoutService'
import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'
import { ITitleService } from 'vs/workbench/services/title/common/titleService'
import { CancellationToken } from 'vs/base/common/cancellation'
import { MonacoDelegateEditorGroupsService, MonacoEditorService, OpenEditor } from './tools/editor'
import getBulkEditServiceOverride from './bulkEdit'
import getLayoutServiceOverride, { LayoutService } from './layout'
import getQuickAccessOverride from './quickaccess'
import getKeybindingsOverride from './keybindings'
import { changeUrlDomain } from './tools/url'
import { registerAssets } from '../assets'
import { registerServiceInitializePostParticipant } from '../lifecycle'
import { registerServiceInitializePostParticipant, serviceInitializedBarrier } from '../lifecycle'
import { getService } from '../services'

Check warning on line 99 in src/service-override/views.ts

View workflow job for this annotation

GitHub Actions / Check build

'getService' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 99 in src/service-override/views.ts

View workflow job for this annotation

GitHub Actions / Check build

'getService' is defined but never used

function createPart (id: string, role: string, classes: string[]): HTMLElement {
const part = document.createElement(role === 'status' ? 'footer' /* Use footer element for status bar #98376 */ : 'div')
Expand Down Expand Up @@ -189,6 +191,10 @@ function renderStatusBarPart (container: HTMLElement): IDisposable {
return attachPart(Parts.STATUSBAR_PART, container)
}

interface BodyRenderer extends IDisposable {
setInput? (accessor: ServicesAccessor, input: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void>
}

type Label = string | {
short: string
medium: string
Expand All @@ -197,7 +203,7 @@ type Label = string | {
interface EditorPanelOption {
readonly id: string
name: string
renderBody (container: HTMLElement): IDisposable
renderBody (container: HTMLElement): BodyRenderer
}

interface SimpleEditorInput extends EditorInput {
Expand All @@ -207,13 +213,25 @@ interface SimpleEditorInput extends EditorInput {
setDirty (dirty: boolean): void
}

function registerEditorPane (options: EditorPanelOption): { disposable: IDisposable, CustomEditorInput: new (closeHandler?: IEditorCloseHandler) => SimpleEditorInput } {
type RegisteredEditorInfoWithoutId = Partial<Omit<RegisteredEditorInfo, 'id'>>
type RegisterEditorPaneResult = {
disposable: IDisposable
CustomEditorInput: new (closeHandler?: IEditorCloseHandler, baseInput?: IResourceEditorInput | ITextResourceEditorInput) => SimpleEditorInput
/**
* Allows you to register the editor for a certain file type. When opening that input, it will render this editor.
*/
registerEditor(globPattern: string, info?: RegisteredEditorInfoWithoutId, options?: RegisteredEditorOptions, closeHandler?: IEditorCloseHandler): IDisposable
}

function registerEditorPane (options: EditorPanelOption): RegisterEditorPaneResult {
class CustomEditorPane extends EditorPane {
private content?: HTMLElement
private bodyRenderer?: BodyRenderer
constructor (
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
@IStorageService storageService: IStorageService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super(options.id, telemetryService, themeService, storageService)
}
Expand All @@ -223,13 +241,31 @@ function registerEditorPane (options: EditorPanelOption): { disposable: IDisposa
this.content.style.display = 'flex'
this.content.style.alignItems = 'stretch'
append(parent, this.content)
this._register(options.renderBody(this.content))
this.bodyRenderer = options.renderBody(this.content)
this._register(this.bodyRenderer)
}

override layout (dimension: Dimension): void {
this.content!.style.height = `${dimension.height}px`
this.content!.style.width = `${dimension.width}px`
}

override async setInput (input: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
await this.instantiationService.invokeFunction(accessor => {
if (this.bodyRenderer != null && this.bodyRenderer.setInput != null) {
return this.bodyRenderer.setInput(
accessor,
input,
options,
context,
token
)
}
return undefined
})

return super.setInput(input, options, context, token)
}
}

class CustomEditorInput extends EditorInput implements SimpleEditorInput {
Expand All @@ -240,7 +276,7 @@ function registerEditorPane (options: EditorPanelOption): { disposable: IDisposa
private description: Label = options.name
private dirty: boolean = false

constructor (public override readonly closeHandler?: IEditorCloseHandler) {
constructor (public override readonly closeHandler?: IEditorCloseHandler, public baseInput?: IResourceEditorInput | ITextResourceEditorInput) {
super()
}

Expand All @@ -249,7 +285,7 @@ function registerEditorPane (options: EditorPanelOption): { disposable: IDisposa
}

override get resource (): URI | undefined {
return undefined
return this.baseInput?.resource
}

public setName (name: string) {
Expand Down Expand Up @@ -304,17 +340,48 @@ function registerEditorPane (options: EditorPanelOption): { disposable: IDisposa
}
}

const disposable = Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
const disposableStore = new DisposableStore()

disposableStore.add(Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
EditorPaneDescriptor.create(
CustomEditorPane,
options.id,
options.name
),
[new SyncDescriptor(CustomEditorInput)])
[new SyncDescriptor(CustomEditorInput)]))

return {
disposable,
CustomEditorInput
disposable: disposableStore,
CustomEditorInput,
registerEditor (globPattern, registerEditorInfo = {}, registeredEditorOptions = {}, closeHandler): IDisposable {
if (!serviceInitializedBarrier.isOpen()) {
throw new Error("Can't register editor before services are initialized")
}

const resolverService = StandaloneServices.get(IEditorResolverService)
const registeredDisposable = resolverService.registerEditor(
globPattern,
{
id: CustomEditorInput.ID,
label: options.name,
priority: RegisteredEditorPriority.default,
...registerEditorInfo
},
registeredEditorOptions,
{
createEditorInput (editorInput: IResourceEditorInput | ITextResourceEditorInput, _group: IEditorGroup) {
return {
options: {},
editor: new CustomEditorInput(closeHandler, editorInput)
}
}
}
)

disposableStore.add(registeredDisposable)

return registeredDisposable
}
}
}

Expand Down Expand Up @@ -652,6 +719,10 @@ export {
IEditorCloseHandler,
ConfirmResult,
registerEditorPane,
RegisterEditorPaneResult,
RegisteredEditorInfoWithoutId as RegisterEditorOptions,
RegisteredEditorInfo,
RegisteredEditorOptions,

renderPart,
renderSidebarPart,
Expand All @@ -676,5 +747,7 @@ export {
SidebarPart,
ActivitybarPart,
PanelPart,
Parts
Parts,

BodyRenderer
}

0 comments on commit c816018

Please sign in to comment.