Skip to content

Commit

Permalink
Showing 13 changed files with 367 additions and 213 deletions.
47 changes: 25 additions & 22 deletions demo/src/features/output.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import { ExtensionHostKind, registerExtension } from 'vscode/extensions'
import { useHtmlFileSystemProvider } from '../setup.common'

const { getApi } = registerExtension({
name: 'outputDemo',
publisher: 'codingame',
version: '1.0.0',
engines: {
vscode: '*'
}
}, ExtensionHostKind.LocalProcess)
if (!useHtmlFileSystemProvider) {
const { getApi } = registerExtension({
name: 'outputDemo',
publisher: 'codingame',
version: '1.0.0',
engines: {
vscode: '*'
}
}, ExtensionHostKind.LocalProcess)

void getApi().then(async vscode => {
const fakeOutputChannel = vscode.window.createOutputChannel('Fake output')
const anotherFakeOutputChannel = vscode.window.createOutputChannel('Your code', 'javascript')
void getApi().then(async vscode => {
const fakeOutputChannel = vscode.window.createOutputChannel('Fake output')
const anotherFakeOutputChannel = vscode.window.createOutputChannel('Your code', 'javascript')

fakeOutputChannel.append('Here\'s some fake output\n')
setInterval(() => {
fakeOutputChannel.append('Hello world\n')
}, 1000)
fakeOutputChannel.append('Here\'s some fake output\n')
setInterval(() => {
fakeOutputChannel.append('Hello world\n')
}, 1000)

const mainDocument = await vscode.workspace.openTextDocument(vscode.Uri.file('/tmp/test.js'))
anotherFakeOutputChannel.replace(mainDocument.getText())
vscode.workspace.onDidChangeTextDocument((e) => {
if (e.document === mainDocument && e.contentChanges.length > 0) {
anotherFakeOutputChannel.replace(e.document.getText())
}
const mainDocument = await vscode.workspace.openTextDocument(vscode.Uri.file('/tmp/test.js'))
anotherFakeOutputChannel.replace(mainDocument.getText())
vscode.workspace.onDidChangeTextDocument((e) => {
if (e.document === mainDocument && e.contentChanges.length > 0) {
anotherFakeOutputChannel.replace(e.document.getText())
}
})
})
})
}
4 changes: 2 additions & 2 deletions demo/src/features/scm.ts
Original file line number Diff line number Diff line change
@@ -14,9 +14,9 @@ const { getApi } = registerExtension({
})

void getApi().then(async vscode => {
const workspaceFolder = vscode.workspace.workspaceFolders![0]
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]
if (workspaceFolder == null) {
throw new Error('No workspace folder')
return
}

vscode.commands.registerCommand('scm-demo.click-file', async (uri: Uri) => {
71 changes: 37 additions & 34 deletions demo/src/features/search.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,44 @@
import { ExtensionHostKind, registerExtension } from 'vscode/extensions'
import * as monaco from 'monaco-editor'
import { useHtmlFileSystemProvider } from '../setup.common'

const { getApi } = registerExtension({
name: 'searchProvider',
publisher: 'codingame',
version: '1.0.0',
engines: {
vscode: '*'
},
enabledApiProposals: ['fileSearchProvider', 'textSearchProvider']
}, ExtensionHostKind.LocalProcess, {
system: true // to be able to use api proposals
})

void getApi().then(async api => {
api.workspace.registerFileSearchProvider('file', {
async provideFileSearchResults () {
return monaco.editor.getModels().map(model => model.uri).filter(uri => uri.scheme === 'file')
}
if (!useHtmlFileSystemProvider) {
const { getApi } = registerExtension({
name: 'searchProvider',
publisher: 'codingame',
version: '1.0.0',
engines: {
vscode: '*'
},
enabledApiProposals: ['fileSearchProvider', 'textSearchProvider']
}, ExtensionHostKind.LocalProcess, {
system: true // to be able to use api proposals
})
api.workspace.registerTextSearchProvider('file', {
async provideTextSearchResults (query, _, progress) {
for (const model of monaco.editor.getModels()) {
const matches = model.findMatches(query.pattern, false, query.isRegExp ?? false, query.isCaseSensitive ?? false, query.isWordMatch ?? false ? ' ' : null, true)
if (matches.length > 0) {
const ranges = matches.map(match => new api.Range(match.range.startLineNumber, match.range.startColumn, match.range.endLineNumber, match.range.endColumn))
progress.report({
uri: model.uri,
ranges,
preview: {
text: model.getValue(),
matches: ranges
}
})

void getApi().then(async api => {
api.workspace.registerFileSearchProvider('file', {
async provideFileSearchResults () {
return monaco.editor.getModels().map(model => model.uri).filter(uri => uri.scheme === 'file')
}
})
api.workspace.registerTextSearchProvider('file', {
async provideTextSearchResults (query, _, progress) {
for (const model of monaco.editor.getModels()) {
const matches = model.findMatches(query.pattern, false, query.isRegExp ?? false, query.isCaseSensitive ?? false, query.isWordMatch ?? false ? ' ' : null, true)
if (matches.length > 0) {
const ranges = matches.map(match => new api.Range(match.range.startLineNumber, match.range.startColumn, match.range.endLineNumber, match.range.endColumn))
progress.report({
uri: model.uri,
ranges,
preview: {
text: model.getValue(),
matches: ranges
}
})
}
}
return {}
}
return {}
}
})
})
})
}
52 changes: 25 additions & 27 deletions demo/src/main.common.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import './style.css'
import * as monaco from 'monaco-editor'
import { registerFileSystemOverlay, HTMLFileSystemProvider } from '@codingame/monaco-vscode-files-service-override'
import { ILogService, StandaloneServices } from 'vscode/services'
import './setup.common'
import { ExtensionHostKind, registerExtension } from 'vscode/extensions'
import { useHtmlFileSystemProvider } from './setup.common'
import './features/output'
import './features/debugger'
import './features/search'
@@ -53,7 +52,6 @@ import '@codingame/monaco-vscode-markdown-math-default-extension'
import '@codingame/monaco-vscode-npm-default-extension'
import '@codingame/monaco-vscode-media-preview-default-extension'
import '@codingame/monaco-vscode-ipynb-default-extension'
import { ExtensionHostKind, registerExtension } from 'vscode/extensions'

const { getApi } = registerExtension({
name: 'demo-main',
@@ -65,20 +63,22 @@ const { getApi } = registerExtension({
}, ExtensionHostKind.LocalProcess)

void getApi().then(async vscode => {
const mainModelUri = vscode.Uri.file('/tmp/test.js')
await Promise.all([
vscode.workspace.openTextDocument(mainModelUri),
vscode.workspace.openTextDocument(monaco.Uri.file('/tmp/test_readonly.js')) // open the file so vscode sees it's locked
])
if (!useHtmlFileSystemProvider) {
const mainModelUri = vscode.Uri.file('/tmp/test.js')
await Promise.all([
vscode.workspace.openTextDocument(mainModelUri),
vscode.workspace.openTextDocument(monaco.Uri.file('/tmp/test_readonly.js')) // open the file so vscode sees it's locked
])

const diagnostics = vscode.languages.createDiagnosticCollection('demo')
diagnostics.set(mainModelUri, [{
range: new vscode.Range(2, 9, 2, 12),
severity: vscode.DiagnosticSeverity.Error,
message: 'This is not a real error, just a demo, don\'t worry',
source: 'Demo',
code: 42
}])
const diagnostics = vscode.languages.createDiagnosticCollection('demo')
diagnostics.set(mainModelUri, [{
range: new vscode.Range(2, 9, 2, 12),
severity: vscode.DiagnosticSeverity.Error,
message: 'This is not a real error, just a demo, don\'t worry',
source: 'Demo',
code: 42
}])
}

document.querySelector('#toggleFullWorkbench')!.addEventListener('click', async () => {
const url = new URL(window.location.href)
@@ -96,15 +96,13 @@ void getApi().then(async vscode => {
window.location.href = url.toString()
})

document.querySelector('#filesystem')!.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker()

const htmlFileSystemProvider = new HTMLFileSystemProvider(undefined, 'unused', StandaloneServices.get(ILogService))
await htmlFileSystemProvider.registerDirectoryHandle(dirHandle)
registerFileSystemOverlay(1, htmlFileSystemProvider)

vscode.workspace.updateWorkspaceFolders(0, 0, {
uri: vscode.Uri.file(dirHandle.name)
})
document.querySelector('#toggleHTMLFileSystemProvider')!.addEventListener('click', async () => {
const url = new URL(window.location.href)
if (url.searchParams.has('htmlFileSystemProvider')) {
url.searchParams.delete('htmlFileSystemProvider')
} else {
url.searchParams.set('htmlFileSystemProvider', 'true')
}
window.location.href = url.toString()
})
})
232 changes: 125 additions & 107 deletions demo/src/setup.common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import getConfigurationServiceOverride, { IStoredWorkspace, initUserConfiguration } from '@codingame/monaco-vscode-configuration-service-override'
import getKeybindingsServiceOverride, { initUserKeybindings } from '@codingame/monaco-vscode-keybindings-service-override'
import { RegisteredFileSystemProvider, RegisteredMemoryFile, RegisteredReadOnlyFile, createIndexedDBProviders, initFile, registerFileSystemOverlay } from '@codingame/monaco-vscode-files-service-override'
import { RegisteredFileSystemProvider, RegisteredMemoryFile, RegisteredReadOnlyFile, createIndexedDBProviders, registerHTMLFileSystemProvider, registerFileSystemOverlay, initFile } from '@codingame/monaco-vscode-files-service-override'
import * as monaco from 'monaco-editor'
import { IWorkbenchConstructionOptions, LogLevel, IEditorOverrideServices } from 'vscode/services'
import * as vscode from 'vscode'
@@ -66,90 +66,125 @@ import { TerminalBackend } from './features/terminal'
import { workerConfig } from './tools/extHostWorker'
import 'vscode/localExtensionHost'

const fileSystemProvider = new RegisteredFileSystemProvider(false)
const url = new URL(document.location.href)
const params = url.searchParams
export const remoteAuthority = params.get('remoteAuthority') ?? undefined
export const connectionToken = params.get('connectionToken') ?? undefined
export const remotePath = remoteAuthority != null ? params.get('remotePath') ?? undefined : undefined
export const resetLayout = params.has('resetLayout')
export const useHtmlFileSystemProvider = params.has('htmlFileSystemProvider')
params.delete('resetLayout')

fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/test.js'), `// import anotherfile
let variable = 1
function inc () {
variable++
}
window.history.replaceState({}, document.title, url.href)

while (variable < 5000) {
inc()
console.log('Hello world', variable);
}`
))
export let workspaceFile = monaco.Uri.file('/workspace.code-workspace')

fileSystemProvider.registerFile(new RegisteredReadOnlyFile(vscode.Uri.file('/tmp/test_readonly.js'), async () => 'This is a readonly static file'))
export const userDataProvider = await createIndexedDBProviders()

fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/jsconfig.json'), `{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"lib": [
"es2021",
"DOM"
]
if (useHtmlFileSystemProvider) {
workspaceFile = monaco.Uri.from({ scheme: 'tmp', path: '/test.code-workspace' })
await initFile(workspaceFile, JSON.stringify(<IStoredWorkspace>{
folders: []
}, null, 2))

registerHTMLFileSystemProvider()
} else {
const fileSystemProvider = new RegisteredFileSystemProvider(false)

fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/test.js'), `// import anotherfile
let variable = 1
function inc () {
variable++
}
}`
))
fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/index.html'), `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>monaco-vscode-api demo</title>
<link rel="stylesheet" href="test.css">
</head>
<body>
<style type="text/css">
h1 {
color: DeepSkyBlue;
}
</style>
while (variable < 5000) {
inc()
console.log('Hello world', variable);
}`
))

fileSystemProvider.registerFile(new RegisteredReadOnlyFile(vscode.Uri.file('/tmp/test_readonly.js'), async () => 'This is a readonly static file'))

<h1>Hello, world!</h1>
</body>
</html>`
))
fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/jsconfig.json'), `{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"lib": [
"es2021",
"DOM"
]
}
}`
))

fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/test.md'), `
***Hello World***
fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/index.html'), `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>monaco-vscode-api demo</title>
<link rel="stylesheet" href="test.css">
</head>
<body>
<style type="text/css">
h1 {
color: DeepSkyBlue;
}
</style>
Math block:
$$
\\displaystyle
\\left( \\sum_{k=1}^n a_k b_k \\right)^2
\\leq
\\left( \\sum_{k=1}^n a_k^2 \\right)
\\left( \\sum_{k=1}^n b_k^2 \\right)
$$
<h1>Hello, world!</h1>
</body>
</html>`
))

# Easy Math
fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/test.md'), `
***Hello World***
2 + 2 = 4 // this test will pass
2 + 2 = 5 // this test will fail
Math block:
$$
\\displaystyle
\\left( \\sum_{k=1}^n a_k b_k \\right)^2
\\leq
\\left( \\sum_{k=1}^n a_k^2 \\right)
\\left( \\sum_{k=1}^n b_k^2 \\right)
$$
# Harder Math
# Easy Math
230230 + 5819123 = 6049353
`
))
2 + 2 = 4 // this test will pass
2 + 2 = 5 // this test will fail
fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/test.customeditor'), `
Custom Editor!`
))
# Harder Math
fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/test.css'), `
h1 {
color: DeepSkyBlue;
}`
))
230230 + 5819123 = 6049353
`
))

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

export const userDataProvider = await createIndexedDBProviders()
fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/test.css'), `
h1 {
color: DeepSkyBlue;
}`
))

// Use a workspace file to be able to add another folder later (for the "Attach filesystem" button)
fileSystemProvider.registerFile(new RegisteredMemoryFile(workspaceFile, JSON.stringify(<IStoredWorkspace>{
folders: [{
path: '/tmp'
}]
}, null, 2)))

fileSystemProvider.registerFile(new RegisteredMemoryFile(monaco.Uri.file('/tmp/.vscode/extensions.json'), JSON.stringify({
recommendations: [
'vscodevim.vim'
]
}, null, 2)))

registerFileSystemOverlay(1, fileSystemProvider)
}

// Workers
export type WorkerLoader = () => Worker
@@ -158,7 +193,8 @@ const workerLoaders: Partial<Record<string, WorkerLoader>> = {
textMateWorker: () => new Worker(new URL('@codingame/monaco-vscode-textmate-service-override/worker', import.meta.url), { type: 'module' }),
outputLinkComputer: () => new Worker(new URL('@codingame/monaco-vscode-output-service-override/worker', import.meta.url), { type: 'module' }),
languageDetectionWorkerService: () => new Worker(new URL('@codingame/monaco-vscode-language-detection-worker-service-override/worker', import.meta.url), { type: 'module' }),
notebookEditorWorkerService: () => new Worker(new URL('@codingame/monaco-vscode-notebook-service-override/worker', import.meta.url), { type: 'module' })
notebookEditorWorkerService: () => new Worker(new URL('@codingame/monaco-vscode-notebook-service-override/worker', import.meta.url), { type: 'module' }),
localFileSearchWorker: () => new Worker(new URL('@codingame/monaco-vscode-search-service-override/worker', import.meta.url), { type: 'module' })

}
window.MonacoEnvironment = {
@@ -171,32 +207,10 @@ window.MonacoEnvironment = {
}
}

const url = new URL(document.location.href)
const params = url.searchParams
export const remoteAuthority = params.get('remoteAuthority') ?? undefined
export const connectionToken = params.get('connectionToken') ?? undefined
export const remotePath = remoteAuthority != null ? params.get('remotePath') ?? undefined : undefined
export const resetLayout = params.has('resetLayout')
params.delete('resetLayout')

window.history.replaceState({}, document.title, url.href)

// Set configuration before initializing service so it's directly available (especially for the theme, to prevent a flicker)
export const workspaceFile = monaco.Uri.file('/workspace.code-workspace')
await Promise.all([
initUserConfiguration(defaultConfiguration),
initUserKeybindings(defaultKeybindings),
// Use a workspace file to be able to add another folder later (for the "Attach filesystem" button)
initFile(workspaceFile, JSON.stringify(<IStoredWorkspace>{
folders: [{
path: '/tmp'
}]
})),
initFile(monaco.Uri.file('/tmp/.vscode/extensions.json'), `{
"recommendations": [
"vscodevim.vim"
]
}`)
initUserKeybindings(defaultKeybindings)
])

export const constructOptions: IWorkbenchConstructionOptions = {
@@ -229,19 +243,23 @@ export const constructOptions: IWorkbenchConstructionOptions = {
'window.title': 'Monaco-Vscode-Api${separator}${dirty}${activeEditorShort}'
},
defaultLayout: {
editors: [{
uri: monaco.Uri.file('/tmp/test.js'),
viewColumn: 1
}, {
uri: monaco.Uri.file('/tmp/test.md'),
viewColumn: 2
}],
layout: {
editors: {
orientation: 0,
groups: [{ size: 1 }, { size: 1 }]
}
},
editors: useHtmlFileSystemProvider
? undefined
: [{
uri: monaco.Uri.file('/tmp/test.js'),
viewColumn: 1
}, {
uri: monaco.Uri.file('/tmp/test.md'),
viewColumn: 2
}],
layout: useHtmlFileSystemProvider
? undefined
: {
editors: {
orientation: 0,
groups: [{ size: 1 }, { size: 1 }]
}
},
views: [{
id: 'custom-view'
}],
@@ -277,7 +295,7 @@ export const commonServices: IEditorOverrideServices = {
...getExtensionGalleryServiceOverride({ webOnly: false }),
...getModelServiceOverride(),
...getNotificationServiceOverride(),
...getDialogsServiceOverride(),
...getDialogsServiceOverride({ useHtmlFileSystemProvider }),
...getConfigurationServiceOverride(),
...getKeybindingsServiceOverride(),
...getTextmateServiceOverride(),
@@ -293,7 +311,7 @@ export const commonServices: IEditorOverrideServices = {
...getSnippetServiceOverride(),
...getOutputServiceOverride(),
...getTerminalServiceOverride(new TerminalBackend()),
...getSearchServiceOverride(),
...getSearchServiceOverride({ useHtmlFileSystemProvider }),
...getMarkersServiceOverride(),
...getAccessibilityServiceOverride(),
...getLanguageDetectionWorkerServiceOverride(),
2 changes: 1 addition & 1 deletion demo/src/setup.views.ts
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ container.innerHTML = `
<h1>Editor</h1>
<div id="editors"></div>
<button id="filesystem">Attach filesystem</button>
<button id="toggleHTMLFileSystemProvider">Toggle HTML filesystem provider</button>
<button id="customEditorPanel">Open custom editor panel</button>
<button id="clearStorage">Clear user data</button>
<button id="resetLayout">Reset layout</button>
2 changes: 1 addition & 1 deletion demo/src/setup.workbench.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ document.body.replaceChildren(container)

const buttons = document.createElement('div')
buttons.innerHTML = `
<button id="filesystem">Attach filesystem</button>
<button id="toggleHTMLFileSystemProvider">Toggle HTML filesystem provider</button>
<button id="customEditorPanel">Open custom editor panel</button>
<button id="clearStorage">Clear user data</button>
<button id="resetLayout">Reset layout</button>
3 changes: 2 additions & 1 deletion rollup/rollup.config.ts
Original file line number Diff line number Diff line change
@@ -362,7 +362,8 @@ const workerGroups: Record<string, string> = {
languageDetection: 'service-override:language-detection-worker',
outputLinkComputer: 'service-override:output',
textmate: 'service-override:textmate',
notebook: 'service-override:notebook'
notebook: 'service-override:notebook',
localFileSearch: 'service-override:search'
}

const externals = Object.keys({ ...pkg.dependencies })
2 changes: 2 additions & 0 deletions src/monaco.ts
Original file line number Diff line number Diff line change
@@ -43,6 +43,8 @@ import { Event } from 'vs/base/common/event'
import { ResourceContextKey } from 'vs/workbench/common/contextkeys'
import { createInjectedClass } from './tools/injection'
import { getService } from './services'
export { registerEditorAction, registerEditorContribution, registerDiffEditorContribution, registerMultiEditorAction, EditorAction, EditorCommand } from 'vs/editor/browser/editorExtensions'
export { IEditorContribution, IDiffEditorContribution } from 'vs/editor/common/editorCommon'

function computeConfiguration (configuration: IEditorConfiguration, overrides?: Readonly<IEditorOptions>): IEditorOptions {
const editorConfiguration: IEditorOptions = isObject(configuration.editor) ? deepClone(configuration.editor) : Object.create(null)
15 changes: 12 additions & 3 deletions src/service-override/dialogs.ts
Original file line number Diff line number Diff line change
@@ -3,12 +3,13 @@ import { DialogService } from 'vs/workbench/services/dialogs/common/dialogServic
import { IDialogService, IFileDialogService, IOpenDialogOptions, IPickAndOpenOptions, ISaveDialogOptions } from 'vs/platform/dialogs/common/dialogs'
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'
import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'
import { FileDialogService } from 'vs/workbench/services/dialogs/browser/fileDialogService'
import { URI } from 'vs/base/common/uri'
import { unsupported } from '../tools'
import 'vs/workbench/browser/parts/dialogs/dialog.web.contribution'
import 'vs/workbench/contrib/welcomeDialog/browser/welcomeDialog.contribution'

class FileDialogService extends AbstractFileDialogService {
class DialogServiceOverride extends AbstractFileDialogService {
override pickWorkspaceAndOpen = unsupported

async pickFileFolderAndOpen (options: IPickAndOpenOptions): Promise<void> {
@@ -60,9 +61,17 @@ class FileDialogService extends AbstractFileDialogService {
}
}

export default function getServiceOverride (): IEditorOverrideServices {
interface DialogServiceOverrideProps {
/**
* Is an `HTMLFileSystemProvider` is used as only provider for the `file` scheme directly (without overlay)
* Enable this option to enable browser file dialogs
*/
useHtmlFileSystemProvider: boolean
}

export default function getServiceOverride ({ useHtmlFileSystemProvider }: DialogServiceOverrideProps): IEditorOverrideServices {
return {
[IDialogService.toString()]: new SyncDescriptor(DialogService, undefined, true),
[IFileDialogService.toString()]: new SyncDescriptor(FileDialogService, undefined, true)
[IFileDialogService.toString()]: useHtmlFileSystemProvider ? new SyncDescriptor(FileDialogService, undefined, true) : new SyncDescriptor(DialogServiceOverride, undefined, true)
}
}
127 changes: 114 additions & 13 deletions src/service-override/files.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IEditorOverrideServices } from 'vs/editor/standalone/browser/standaloneServices'
import { IEditorOverrideServices, StandaloneServices } from 'vs/editor/standalone/browser/standaloneServices'
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'
import { FileService } from 'vs/platform/files/common/fileService'
import { ILogService } from 'vs/platform/log/common/log'
import { ILogService, LogLevel } from 'vs/platform/log/common/log'
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'
import { URI } from 'vs/base/common/uri'
import { FileChangeType, FilePermission, FileSystemProviderCapabilities, FileType, IFileSystemProvider, toFileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError, FileSystemProviderErrorCode, IFileChange, IFileDeleteOptions, IFileOverwriteOptions, IFileService, IFileSystemProviderWithFileReadWriteCapability, IFileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files'
@@ -305,6 +305,9 @@ class OverlayFileSystemProvider implements IFileSystemProviderWithFileReadWriteC
capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive

private async readFromDelegates<T> (caller: (delegate: IFileSystemProviderWithFileReadWriteCapability) => Promise<T>) {
if (this.delegates.length === 0) {
throw createFileSystemProviderError('No delegate', FileSystemProviderErrorCode.Unavailable)
}
let firstError: unknown | undefined
for (const delegate of this.delegates) {
try {
@@ -325,6 +328,9 @@ class OverlayFileSystemProvider implements IFileSystemProviderWithFileReadWriteC
}

private async writeToDelegates (caller: (delegate: IFileSystemProviderWithFileReadWriteCapability) => Promise<void>): Promise<void> {
if (this.delegates.length === 0) {
throw createFileSystemProviderError('No delegate', FileSystemProviderErrorCode.Unavailable)
}
for (const provider of this.delegates) {
if ((provider.capabilities & FileSystemProviderCapabilities.Readonly) > 0) {
continue
@@ -513,8 +519,45 @@ class DelegateFileSystemProvider implements IFileSystemProvider {
}
}

const fileSystemProvider = new OverlayFileSystemProvider()
fileSystemProvider.register(0, new MkdirpOnWriteInMemoryFileSystemProvider())
class EmptyFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability {
async readFile (): Promise<Uint8Array> {
throw createFileSystemProviderError('Not found', FileSystemProviderErrorCode.FileNotFound)
}

async writeFile (): Promise<void> {
throw createFileSystemProviderError('Not allowed', FileSystemProviderErrorCode.NoPermissions)
}

capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive
onDidChangeCapabilities = Event.None
onDidChangeFile = Event.None
watch (): IDisposable {
return Disposable.None
}

async stat (): Promise<IStat> {
throw createFileSystemProviderError('Not found', FileSystemProviderErrorCode.FileNotFound)
}

async mkdir (): Promise<void> {
throw createFileSystemProviderError('Not allowed', FileSystemProviderErrorCode.NoPermissions)
}

async readdir (): Promise<[string, FileType][]> {
throw createFileSystemProviderError('Not found', FileSystemProviderErrorCode.FileNotFound)
}

async delete (): Promise<void> {
throw createFileSystemProviderError('Not allowed', FileSystemProviderErrorCode.NoPermissions)
}

async rename (): Promise<void> {
throw createFileSystemProviderError('Not allowed', FileSystemProviderErrorCode.NoPermissions)
}
}

const overlayFileSystemProvider = new OverlayFileSystemProvider()
overlayFileSystemProvider.register(0, new MkdirpOnWriteInMemoryFileSystemProvider())

const extensionFileSystemProvider = new RegisteredFileSystemProvider(true)
const userDataFileSystemProvider = new InMemoryFileSystemProvider()
@@ -536,10 +579,10 @@ const providers: Record<string, IFileSystemProvider> = {
[logsPath.scheme]: new InMemoryFileSystemProvider(),
[Schemas.vscodeUserData]: userDataFileSystemProvider,
[Schemas.tmp]: new InMemoryFileSystemProvider(),
[Schemas.file]: fileSystemProvider
[Schemas.file]: overlayFileSystemProvider
}

class MemoryFileService extends FileService {
class FileServiceOverride extends FileService {
constructor (logService: ILogService, @ITelemetryService telemetryService: ITelemetryService) {
super(logService)

@@ -548,7 +591,7 @@ class MemoryFileService extends FileService {
if (provider instanceof OverlayFileSystemProvider) {
provider.onDidChangeOverlays(() => {
disposable.dispose()
disposable = this.registerProvider(scheme, fileSystemProvider)
disposable = this.registerProvider(scheme, provider)
})
}

@@ -568,7 +611,7 @@ registerServiceInitializePreParticipant(async (accessor) => {

export default function getServiceOverride (): IEditorOverrideServices {
return {
[IFileService.toString()]: new SyncDescriptor(MemoryFileService, [fileLogger], true),
[IFileService.toString()]: new SyncDescriptor(FileServiceOverride, [fileLogger], true),
[ITextFileService.toString()]: new SyncDescriptor(BrowserTextFileService, [], true),
[IFilesConfigurationService.toString()]: new SyncDescriptor(FilesConfigurationService, [], true),
[IElevatedFileService.toString()]: new SyncDescriptor(BrowserElevatedFileService, [], true)
@@ -615,13 +658,15 @@ export async function initFile (file: URI, content: Uint8Array | string, options
})
}

let indexedDB: IndexedDB | undefined
const userDataStore = 'vscode-userdata-store'
const logsStore = 'vscode-logs-store'
const handlesStore = 'vscode-filehandles-store'
/**
* Can be used to replace memory providers by indexeddb providers before the fileService is initialized
*/
export async function createIndexedDBProviders (): Promise<IndexedDBFileSystemProvider> {
const userDataStore = 'vscode-userdata-store'
const logsStore = 'vscode-logs-store'
const indexedDB = await IndexedDB.create('vscode-web-db', 3, [userDataStore, logsStore])
indexedDB = await IndexedDB.create('vscode-web-db', 3, [userDataStore, logsStore, handlesStore])

// Logger
registerCustomProvider(logsPath.scheme, new IndexedDBFileSystemProvider(logsPath.scheme, indexedDB, logsStore, false))
@@ -632,6 +677,56 @@ export async function createIndexedDBProviders (): Promise<IndexedDBFileSystemPr
return userDataProvider
}

/**
* Can be used to replace the default filesystem provider by the HTMLFileSystemProvider before the fileService is initialized
* Should be called "after" createIndexedDBProviders if used
*/
export function registerHTMLFileSystemProvider (): void {
class LazyLogService implements ILogService {
_serviceBrand: undefined
get onDidChangeLogLevel () {
return StandaloneServices.get(ILogService).onDidChangeLogLevel
}

getLevel (): LogLevel {
return StandaloneServices.get(ILogService).getLevel()
}

setLevel (level: LogLevel): void {
StandaloneServices.get(ILogService).setLevel(level)
}

trace (message: string, ...args: any[]): void {
StandaloneServices.get(ILogService).trace(message, ...args)
}

debug (message: string, ...args: any[]): void {
StandaloneServices.get(ILogService).debug(message, ...args)
}

info (message: string, ...args: any[]): void {
StandaloneServices.get(ILogService).info(message, ...args)
}

warn (message: string, ...args: any[]): void {
StandaloneServices.get(ILogService).warn(message, ...args)
}

error (message: string | Error, ...args: any[]): void {
StandaloneServices.get(ILogService).error(message, ...args)
}

flush (): void {
StandaloneServices.get(ILogService).flush()
}

dispose (): void {
StandaloneServices.get(ILogService).dispose()
}
}
registerCustomProvider(Schemas.file, new HTMLFileSystemProvider(indexedDB, handlesStore, new LazyLogService()))
}

/**
* Register a file system overlay
*
@@ -644,7 +739,11 @@ export async function createIndexedDBProviders (): Promise<IndexedDBFileSystemPr
* - any provider registered with a negative priority will be behind the default one
*/
export function registerFileSystemOverlay (priority: number, provider: IFileSystemProviderWithFileReadWriteCapability): IDisposable {
return fileSystemProvider.register(priority, provider)
const overlayProvider = providers.file
if (!(overlayProvider instanceof OverlayFileSystemProvider)) {
throw new Error('The overlay filesystem provider was replaced')
}
return overlayProvider.register(priority, provider)
}

export {
@@ -669,5 +768,7 @@ export {
RegisteredFile,
RegisteredReadOnlyFile,
RegisteredMemoryFile,
DelegateFileSystemProvider
DelegateFileSystemProvider,
OverlayFileSystemProvider,
EmptyFileSystemProvider
}
13 changes: 11 additions & 2 deletions src/service-override/search.ts
Original file line number Diff line number Diff line change
@@ -6,12 +6,21 @@ import { ISearchViewModelWorkbenchService, SearchViewModelWorkbenchService } fro
import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'
import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'
import { ReplaceService } from 'vs/workbench/contrib/search/browser/replaceService'
import { RemoteSearchService } from 'vs/workbench/services/search/browser/searchService'
import 'vs/workbench/contrib/search/browser/search.contribution'
import 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'

export default function getServiceOverride (): IEditorOverrideServices {
interface SearchServiceOverrideProps {
/**
* Is an `HTMLFileSystemProvider` is used as only provider for the `file` scheme directly (without overlay)
* Enable this option to enable searching local filesystem
*/
useHtmlFileSystemProvider: boolean
}

export default function getServiceOverride ({ useHtmlFileSystemProvider }: SearchServiceOverrideProps): IEditorOverrideServices {
return {
[ISearchService.toString()]: new SyncDescriptor(SearchService, [], true),
[ISearchService.toString()]: useHtmlFileSystemProvider ? new SyncDescriptor(RemoteSearchService, [], true) : new SyncDescriptor(SearchService, [], true),
[ISearchViewModelWorkbenchService.toString()]: new SyncDescriptor(SearchViewModelWorkbenchService, [], true),
[ISearchHistoryService.toString()]: new SyncDescriptor(SearchHistoryService, [], true),
[IReplaceService.toString()]: new SyncDescriptor(ReplaceService, [], true)
10 changes: 10 additions & 0 deletions src/workers/localFileSearch.worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { create } from 'vs/workbench/services/search/worker/localFileSearch'
import { SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker'

const simpleWorker = new SimpleWorkerServer((msg) => {
globalThis.postMessage(msg)
}, create)

globalThis.onmessage = (e: MessageEvent) => {
simpleWorker.onmessage(e.data)
}

1 comment on commit 41894a1

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.