Skip to content

Commit f579f05

Browse files
committed
feat(fbaFs): minimal poc for fba filesystem provider
1 parent 93f2d66 commit f579f05

File tree

4 files changed

+196
-6
lines changed

4 files changed

+196
-6
lines changed

src/extension/fbaEditor.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { getNonce, performHttpRequest } from './util';
1414
import * as yaml from 'js-yaml';
1515
import TelemetryReporter from '@vscode/extension-telemetry';
1616
import ShortUniqueId from 'short-unique-id'
17+
import { FBAFSProvider } from './fbaFSProvider'
1718

1819
const uid = new ShortUniqueId({ length: 8 })
1920

@@ -31,6 +32,7 @@ interface DocData {
3132
gotAliveFromPanel: boolean
3233
msgsToPost: any[]
3334
lastPostedDocVersion: number
35+
lastPostedObj?: any
3436
editsPending: {
3537
document: vscode.TextDocument // the document to update
3638
docVersion: number // the document version at the time the update was queued
@@ -39,7 +41,7 @@ interface DocData {
3941
treeItem?: FishboneTreeItem
4042
}
4143

42-
interface FishboneTreeItem extends vscode.TreeItem {
44+
export interface FishboneTreeItem extends vscode.TreeItem {
4345
id?: string
4446
label?: string | vscode.TreeItemLabel
4547
tooltip?: string | vscode.MarkdownString
@@ -53,6 +55,7 @@ interface FishboneTreeItem extends vscode.TreeItem {
5355
// collapsibleState?
5456

5557
docData?: DocData
58+
_document?: vscode.TextDocument
5659
}
5760

5861
const currentFBAFileVersion = '0.7'
@@ -64,16 +67,18 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
6467
public static register(context: vscode.ExtensionContext, reporter?: TelemetryReporter): void {
6568
const provider = new FBAEditorProvider(context, reporter)
6669
context.subscriptions.push(vscode.window.registerCustomEditorProvider(FBAEditorProvider.viewType, provider))
70+
context.subscriptions.push(vscode.workspace.registerFileSystemProvider(FBAEditorProvider.fsSchema, provider._fsProvider))
6771
// does not work in CustomTextEditor (only in text view) context.subscriptions.push(vscode.languages.registerDocumentDropEditProvider({ pattern: '**/*.fba' }, provider));
6872
}
6973

7074
private static readonly viewType = 'fishbone.fba' // has to match the package.json
7175
private static readonly treeViewType = 'fishbone_tree.fba' // has to match as well
76+
private static readonly fsSchema = 'fbaFs'
7277

7378
// explorer tree view support:
7479
private _treeView?: vscode.TreeView<FishboneTreeItem>
7580
private _onDidChangeTreeData: vscode.EventEmitter<FishboneTreeItem | null> = new vscode.EventEmitter<FishboneTreeItem | null>()
76-
private _treeRootNodes: FishboneTreeItem[] = []
81+
public _treeRootNodes: FishboneTreeItem[] = []
7782

7883
private _subscriptions: Array<vscode.Disposable> = new Array<vscode.Disposable>()
7984

@@ -83,9 +88,13 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
8388
private _checkExtensionsTimer?: NodeJS.Timeout = undefined
8489
private _checkExtensionsLastActive = 0
8590

91+
private _fsProvider: FBAFSProvider
92+
8693
constructor(private readonly context: vscode.ExtensionContext, private readonly reporter?: TelemetryReporter) {
8794
console.log(`FBAEditorProvider constructor() called...`)
8895

96+
this._fsProvider = new FBAFSProvider(this)
97+
8998
// time-sync feature: check other extensions for api onDidChangeSelectedTime and connect to them.
9099
this._subscriptions.push(
91100
vscode.extensions.onDidChange(() => {
@@ -303,6 +312,7 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
303312
title: docObj.title,
304313
attributes: docObj.attributes,
305314
})
315+
docData.lastPostedObj = docObj
306316
docData.lastPostedDocVersion = document.version
307317

308318
if (docData.treeItem && docData.treeItem.label !== docObj.title) {
@@ -328,6 +338,7 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
328338
// Setup initial content for the webview
329339
webviewPanel.webview.options = {
330340
enableScripts: true,
341+
enableCommandUris: true,
331342
}
332343
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview)
333344

@@ -344,6 +355,7 @@ export class FBAEditorProvider implements vscode.CustomTextEditorProvider, vscod
344355
}
345356
docData.treeItem = {
346357
docData: docData,
358+
_document: document,
347359
children: [],
348360
label: document.uri.path,
349361
tooltip: 'Drop filters here to use them in edit dialogs for badges and "apply filter".',

src/extension/fbaFSProvider.ts

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* copyright (c) 2023, Matthias Behr
3+
*
4+
* todo:
5+
* [] implement watch
6+
* [] implement frag (aka members)
7+
* [] fix fba staying old if hidden
8+
* [] how to provide a file type (json,...) to command.open
9+
*/
10+
11+
import { TextDecoder, TextEncoder } from 'util'
12+
import * as vscode from 'vscode'
13+
import { FBAEditorProvider, FishboneTreeItem } from './fbaEditor'
14+
15+
export class FBAFSProvider implements vscode.FileSystemProvider {
16+
private onDidChangeEmitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>()
17+
18+
constructor(private editorProvider: FBAEditorProvider) {
19+
this.onDidChangeFile = this.onDidChangeEmitter.event
20+
}
21+
22+
private static decodeQueryParams(query: string): Record<string, string> {
23+
return query.split('&').reduce((accumulator: Record<string, string>, singleQueryParam) => {
24+
const [key, value] = singleQueryParam.split('=')
25+
accumulator[key] = decodeURIComponent(value)
26+
return accumulator
27+
}, {})
28+
}
29+
30+
/**
31+
* get data for uri from the known documents from provider
32+
*
33+
* @param uri
34+
* @returns a member from the docData.lastPostedObj that can/must be modified directly!
35+
*/
36+
private getDataForUri(uri: vscode.Uri): [FishboneTreeItem, any] | undefined {
37+
console.log(`FBAFSProvider.getDataForUri(${uri.toString()})`)
38+
try {
39+
const title = uri.path.slice(1) // without the /
40+
const queryParams = FBAFSProvider.decodeQueryParams(uri.query)
41+
42+
// do we find a document?
43+
const treeItems = this.editorProvider._treeRootNodes
44+
const doc = treeItems.find((v) => v.docData?.lastPostedObj?.title === title)
45+
if (doc) {
46+
console.log(`FBAFSProvider.getDataForUri found doc`, Object.keys(doc))
47+
const lastFBA = doc.docData!.lastPostedObj
48+
// now search for the fbUid
49+
const elem = FBAFSProvider.getElemFromFBA(lastFBA, queryParams)
50+
console.log(`FBAFSProvider.getDataForUri found elem`, elem)
51+
return [doc, elem]
52+
} else {
53+
console.log(`FBAFSProvider.getDataForUri found no doc for title:'${title}'`)
54+
}
55+
} catch (e) {
56+
console.log(`FBAFSProvider.getDataForUri() got error:'${e}'`)
57+
}
58+
return undefined
59+
}
60+
61+
private static getElemFromFBA(fba: any, queryParams: Record<string, string>): any {
62+
console.log(`FBAFSProvider.getElemFromFBA(${JSON.stringify(queryParams)})`)
63+
// search for fbUid in fba.fishbone and fba.attributes
64+
const fbUid = queryParams.fbUid
65+
if (fbUid) {
66+
// todo in fishbone...
67+
const attributes = fba.attributes
68+
if (Array.isArray(attributes)) {
69+
for (const attr of attributes) {
70+
if (attr.fbUid === fbUid) {
71+
return attr
72+
}
73+
}
74+
}
75+
}
76+
return undefined
77+
}
78+
79+
watch(uri: vscode.Uri, options: { readonly recursive: boolean; readonly excludes: readonly string[] }): vscode.Disposable {
80+
console.log(`FBAFSProvider.watch(${uri.toString()}, ${JSON.stringify(options)})`)
81+
return {
82+
dispose() {
83+
console.log(`FBAFSProvider watch on '${uri.toString()}' disposed.`)
84+
},
85+
}
86+
}
87+
88+
/// if this fires the stat.mtime needs to advance and a correct size needs to be reported!
89+
onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]>
90+
91+
stat(uri: vscode.Uri): vscode.FileStat | Thenable<vscode.FileStat> {
92+
console.log(`FBAFSProvider.stat(${uri.toString()})`)
93+
//console.log(`FBAFSProvider.stat uri.path='${uri.path}'`)
94+
//console.log(`FBAFSProvider.stat uri.query='${uri.query}'`)
95+
//console.log(`FBAFSProvider.stat uri.fragment='${uri.fragment}'`)
96+
97+
const queryParams = FBAFSProvider.decodeQueryParams(uri.query)
98+
console.log(`FBAFSProvider.stat queryParams='${JSON.stringify(queryParams)}'`)
99+
100+
const [doc, data] = this.getDataForUri(uri) || [undefined, undefined]
101+
console.log(`FBAFSProvider.stat keys(data)=`, Object.keys(data))
102+
103+
const size = data ? JSON.stringify(data).length : 0
104+
105+
return {
106+
ctime: 0,
107+
mtime: doc ? doc.docData?.lastPostedDocVersion || 0 : 0,
108+
size,
109+
type: vscode.FileType.File,
110+
}
111+
}
112+
113+
readFile(uri: vscode.Uri): Uint8Array | Thenable<Uint8Array> {
114+
console.log(`FBAFSProvider.readFile(${uri.toString()})`)
115+
const [doc, data] = this.getDataForUri(uri) || [undefined, undefined]
116+
if (data) {
117+
const text = JSON.stringify(data)
118+
return new TextEncoder().encode(text)
119+
}
120+
return new Uint8Array()
121+
}
122+
123+
writeFile(
124+
uri: vscode.Uri,
125+
content: Uint8Array,
126+
options: { readonly create: boolean; readonly overwrite: boolean },
127+
): void | Thenable<void> {
128+
console.log(`FBAFSProvider.writeFile(${uri.toString()}, content.length=${content.length}, options=${JSON.stringify(options)}})`)
129+
const [doc, data] = this.getDataForUri(uri) || [undefined, undefined]
130+
if (doc && doc.docData && doc._document && data) {
131+
const newText = new TextDecoder().decode(content)
132+
const newData = JSON.parse(newText)
133+
// modify the data object directly
134+
let didModify = false
135+
if (typeof newData === 'object') {
136+
Object.entries(newData).forEach(([key, value]) => {
137+
console.log(`FBAFSProvider.writeFile setting member '${key}'`)
138+
if (value !== undefined) {
139+
const oldValue = data[key]
140+
if (JSON.stringify(oldValue) !== JSON.stringify(value)) {
141+
data[key] = value
142+
didModify = true
143+
}
144+
} else {
145+
if (data[key] !== undefined) {
146+
data.delete(key)
147+
didModify = true
148+
}
149+
}
150+
})
151+
}
152+
// updateTextDocument
153+
FBAEditorProvider.updateTextDocument(doc.docData, doc?._document, doc?.docData?.lastPostedObj)
154+
} else {
155+
console.log(`FBAFSProvider.writeFile(${uri.toString()})...ignored due to wrong doc...`)
156+
}
157+
}
158+
159+
delete(uri: vscode.Uri, options: { readonly recursive: boolean }): void | Thenable<void> {
160+
console.log(`FBAFSProvider.delete(${uri.toString()}, ${JSON.stringify(options)})`)
161+
throw vscode.FileSystemError.Unavailable('delete for fba content not possible')
162+
}
163+
164+
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { readonly overwrite: boolean }): void | Thenable<void> {
165+
console.log(`FBAFSProvider.rename(${oldUri.toString()}->${newUri.toString()}, ${JSON.stringify(options)})`)
166+
throw vscode.FileSystemError.Unavailable('rename for fba content not possible')
167+
}
168+
169+
readDirectory(uri: vscode.Uri): [string, vscode.FileType][] | Thenable<[string, vscode.FileType][]> {
170+
console.log(`FBAFSProvider.readDirectory(${uri.toString()})`)
171+
return []
172+
}
173+
174+
createDirectory(uri: vscode.Uri): void | Thenable<void> {
175+
console.log(`FBAFSProvider.createDirectory(${uri.toString()})`)
176+
throw vscode.FileSystemError.Unavailable('create directory for fba content not possible')
177+
}
178+
}

src/webview/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
"dompurify": "^2.4.0",
1717
"jju": "github:mbehr1/jju#3aa4169df926e99083fdd511d7c20b5bd9ba789f",
1818
"js-yaml": "^4.1.0",
19+
"json-stable-stringify": "^1.0.1",
1920
"json5": "2.2.3",
2021
"jsonpath": "^1.1.1",
21-
"json-stable-stringify": "^1.0.1",
2222
"marked": "^3.0.8",
2323
"react": "^18.2.0",
2424
"react-dom": "^18.2.0",

src/webview/yarn.lock

+3-3
Original file line numberDiff line numberDiff line change
@@ -3539,9 +3539,9 @@ caniuse-api@^3.0.0:
35393539
lodash.uniq "^4.5.0"
35403540

35413541
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001135, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001407:
3542-
version "1.0.30001412"
3543-
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz#30f67d55a865da43e0aeec003f073ea8764d5d7c"
3544-
integrity sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA==
3542+
version "1.0.30001525"
3543+
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz"
3544+
integrity sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q==
35453545

35463546
case-sensitive-paths-webpack-plugin@^2.4.0:
35473547
version "2.4.0"

0 commit comments

Comments
 (0)