Skip to content

Commit

Permalink
Notes explorer (#1223)
Browse files Browse the repository at this point in the history
* Added notes explorer

* Fixed line reference in range tree items

Thanks to Wikilens extensions for the high level inspiration (and the choice for the backlink tree item icon, as I find it just perfect)
  • Loading branch information
riccardoferretti authored May 6, 2023
1 parent 93c5d2f commit e0e08a2
Show file tree
Hide file tree
Showing 6 changed files with 403 additions and 7 deletions.
34 changes: 34 additions & 0 deletions packages/foam-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
"icon": "$(tag)",
"contextualTitle": "Tags Explorer"
},
{
"id": "foam-vscode.notes-explorer",
"name": "Notes",
"icon": "$(notebook)",
"contextualTitle": "Notes Explorer"
},
{
"id": "foam-vscode.orphans",
"name": "Orphans",
Expand Down Expand Up @@ -130,6 +136,16 @@
"command": "foam-vscode.views.placeholders.group-by:off",
"when": "view == foam-vscode.placeholders && foam-vscode.views.placeholders.group-by == 'folder'",
"group": "navigation"
},
{
"command": "foam-vscode.views.notes-explorer.show:notes",
"when": "view == foam-vscode.notes-explorer && foam-vscode.views.notes-explorer.show == 'all'",
"group": "navigation"
},
{
"command": "foam-vscode.views.notes-explorer.show:all",
"when": "view == foam-vscode.notes-explorer && foam-vscode.views.notes-explorer.show == 'notes-only'",
"group": "navigation"
}
],
"commandPalette": [
Expand Down Expand Up @@ -165,6 +181,14 @@
"command": "foam-vscode.views.placeholders.group-by:off",
"when": "false"
},
{
"command": "foam-vscode.views.notes-explorer.show:all",
"when": "false"
},
{
"command": "foam-vscode.views.notes-explorer.show:notes",
"when": "false"
},
{
"command": "foam-vscode.open-resource",
"when": "false"
Expand Down Expand Up @@ -262,6 +286,16 @@
"title": "Flat list",
"icon": "$(list-flat)"
},
{
"command": "foam-vscode.views.notes-explorer.show:all",
"title": "Show all resources",
"icon": "$(files)"
},
{
"command": "foam-vscode.views.notes-explorer.show:notes",
"title": "Show only notes",
"icon": "$(file)"
},
{
"command": "foam-vscode.create-new-template",
"title": "Foam: Create New Template"
Expand Down
1 change: 1 addition & 0 deletions packages/foam-vscode/src/features/panels/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { default as dataviz } from './dataviz';
export { default as orphans } from './orphans';
export { default as placeholders } from './placeholders';
export { default as tags } from './tags-explorer';
export { default as notes } from './notes-explorer';
166 changes: 166 additions & 0 deletions packages/foam-vscode/src/features/panels/notes-explorer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import * as vscode from 'vscode';
import { FoamFeature } from '../../types';
import { Foam } from '../../core/model/foam';
import { FoamWorkspace } from '../../core/model/workspace';
import {
ResourceRangeTreeItem,
ResourceTreeItem,
createBacklinkItemsForResource as createBacklinkTreeItemsForResource,
} from '../../utils/tree-view-utils';
import { Resource } from '../../core/model/note';
import { FoamGraph } from '../../core/model/graph';
import { ContextMemento } from '../../utils/vsc-utils';
import {
FolderTreeItem,
FolderTreeProvider,
} from './utils/folder-tree-provider';

const feature: FoamFeature = {
activate: async (
context: vscode.ExtensionContext,
foamPromise: Promise<Foam>
) => {
const foam = await foamPromise;
const provider = new NotesProvider(
foam.workspace,
foam.graph,
context.globalState
);
provider.refresh();
const treeView = vscode.window.createTreeView<NotesTreeItems>(
'foam-vscode.notes-explorer',
{
treeDataProvider: provider,
showCollapseAll: true,
canSelectMany: true,
}
);
const revealTextEditorItem = async () => {
const target = vscode.window.activeTextEditor?.document.uri;
if (treeView.visible) {
if (target) {
const item = await findTreeItemByUri(provider, target);
// Check if the item is already selected.
// This check is needed because always calling reveal() will
// cause the tree view to take the focus from the item when
// browsing the notes explorer
if (
!treeView.selection.find(
i => i.resourceUri?.path === item.resourceUri.path
)
) {
treeView.reveal(item);
}
}
}
};

context.subscriptions.push(
treeView,
provider,
foam.graph.onDidUpdate(() => {
provider.refresh();
}),
vscode.window.onDidChangeActiveTextEditor(revealTextEditorItem),
treeView.onDidChangeVisibility(revealTextEditorItem)
);
},
};

export default feature;

export function findTreeItemByUri<I, T>(
provider: FolderTreeProvider<I, T>,
target: vscode.Uri
) {
const path = vscode.workspace.asRelativePath(
target,
vscode.workspace.workspaceFolders.length > 1
);
return provider.findTreeItemByPath(path.split('/'));
}

export type NotesTreeItems =
| ResourceTreeItem
| FolderTreeItem<Resource>
| ResourceRangeTreeItem;

export class NotesProvider extends FolderTreeProvider<
NotesTreeItems,
Resource
> {
public show = new ContextMemento<'all' | 'notes-only'>(
this.state,
`foam-vscode.views.notes-explorer.show`,
'all'
);

constructor(
private workspace: FoamWorkspace,
private graph: FoamGraph,
private state: vscode.Memento
) {
super();
this.disposables.push(
vscode.commands.registerCommand(
`foam-vscode.views.notes-explorer.show:all`,
() => {
this.show.update('all');
this.refresh();
}
),
vscode.commands.registerCommand(
`foam-vscode.views.notes-explorer.show:notes`,
() => {
this.show.update('notes-only');
this.refresh();
}
)
);
}

getValues() {
return this.workspace.list();
}

getFilterFn() {
return this.show.get() === 'notes-only'
? res => res.type !== 'image' && res.type !== 'attachment'
: () => true;
}

valueToPath(value: Resource) {
const path = vscode.workspace.asRelativePath(
value.uri.path,
vscode.workspace.workspaceFolders.length > 1
);
const parts = path.split('/');
return parts;
}

isValueType(value: Resource): value is Resource {
return value.uri != null;
}

createValueTreeItem(
value: Resource,
parent: FolderTreeItem<Resource>
): NotesTreeItems {
const res = new ResourceTreeItem(value, this.workspace, {
parent,
collapsibleState:
this.graph.getBacklinks(value.uri).length > 0
? vscode.TreeItemCollapsibleState.Collapsed
: vscode.TreeItemCollapsibleState.None,
});
res.getChildren = async () => {
const backlinks = await createBacklinkTreeItemsForResource(
this.workspace,
this.graph,
res.uri
);
return backlinks;
};
return res;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as vscode from 'vscode';
import { IDisposable } from 'packages/foam-vscode/src/core/common/lifecycle';

export abstract class BaseTreeProvider<T>
implements vscode.TreeDataProvider<T>, IDisposable
{
protected disposables: vscode.Disposable[] = [];

// prettier-ignore
private _onDidChangeTreeData: vscode.EventEmitter<T | undefined | void> = new vscode.EventEmitter<T | undefined | void>();
// prettier-ignore
readonly onDidChangeTreeData: vscode.Event<T | undefined | void> = this._onDidChangeTreeData.event;

abstract getChildren(element?: T): vscode.ProviderResult<T[]>;

getTreeItem(element: T) {
return element;
}

async resolveTreeItem(item: T): Promise<T> {
if ((item as any)?.resolveTreeItem) {
return (item as any).resolveTreeItem();
}
return Promise.resolve(item);
}

refresh(): void {
this._onDidChangeTreeData.fire();
}

dispose(): void {
this.disposables.forEach(d => d.dispose());
}
}
Loading

0 comments on commit e0e08a2

Please sign in to comment.