Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Factored out graph operations and moved some code into core #83

Merged
merged 13 commits into from
Jul 12, 2020

Conversation

riccardoferretti
Copy link
Collaborator

I will comment in-line to guide the reasoning

packages/foam-vscode/src/workspace.ts Outdated Show resolved Hide resolved
packages/foam-workspace-manager/src/core.ts Outdated Show resolved Hide resolved
packages/foam-workspace-manager/src/core.ts Outdated Show resolved Hide resolved
packages/foam-workspace-manager/src/utils/utils.ts Outdated Show resolved Hide resolved
packages/foam-workspace-manager/src/utils/utils.ts Outdated Show resolved Hide resolved
packages/foam-workspace-manager/src/utils/utils.ts Outdated Show resolved Hide resolved
packages/foam-workspace-manager/src/utils/utils.ts Outdated Show resolved Hide resolved
// `[backlink:${backlink.id}]: ${backlink.filename} "${backlink.title}"`
// );
// }
const references = uniq(createMarkdownReferences(foam, id)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

now the only thing the extension does is converting the md references for the editor (looking at it even the string creation should go in the core)

Copy link
Contributor

Choose a reason for hiding this comment

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

This is somehow related to the link reference definition proposal, currently I'm not quite sure in which order it would be beneficial to implement these things

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes tbh I am not fully clear on how the link reference would work best, in this case I was reimplementing the existing behavior, but I agree this needs a bit more clarity

Copy link
Collaborator

Choose a reason for hiding this comment

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

@riccardoferretti is the purpose of uniq here to dedup the list in cases where there were multiple links to the same document? Unless those references are triple equal to each other, this will still leave duplicates as lodash#uniq uses SameValueZero comparison, which for object compares reference equality, not deep equality.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah, that's why I am doing the uniq on the resulting string, which is kinda of a hack, this should be done at the createMarkdownReferences layer (on the object as you hinted at), I was a bit lazy about doing the right comparison. I should really move it there..

"@types/lodash": "^4.14.157",
"graphlib": "^2.1.8",
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to store metadata about nodes & edges with this library or should they live in a separate index? E.g. links should probably contain e.g. line and column numbers + text length to support proper highlighting in the UI.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes good point - it's possible to add metadata to node/edges. I am currently storing the Bubble object in the node, and just the text of the link in the edge (but I should probably store an object there instead, to more easily prepare for extra metadata)

Copy link
Collaborator

@jevakallio jevakallio left a comment

Choose a reason for hiding this comment

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

@riccardoferretti I noticed you've just pushed a new commit while I was reviewing, so let me conclude my initial review pass here and start a new one with the new changes.

packages/foam-workspace-manager/src/core.ts Outdated Show resolved Hide resolved
packages/foam-workspace-manager/src/core.ts Outdated Show resolved Hide resolved

public setBubble(bubble: Bubble) {
if (this.graph.hasNode(bubble.id)) {
(this.graph.outEdges(bubble.id) || []).forEach(edge => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not clear to me yet why this is done, but maybe it'll become clear later

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

basically this cleans the old edges before adding the new ones, otherwise the old links would still exist - I might actually move that out and have a removeBubble (which we would need when removing a file anyways)

packages/foam-workspace-manager/src/utils/utils.ts Outdated Show resolved Hide resolved
})
}

public getBubbles(): Bubble[] {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Whether we call this Bubble.getBubbles, or Note.getNotes, is that naming clear? Should it be .getLinkedNotes?

Not sure tbh, thinking out loud here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This one returns all the notes in the graph, whether or not they are linked to others, that's why I kept the name bare, what do you think?

Somewhat related, I tend to use the following conventions:

  • getNote(id): Note expects the id to exist and will throw if it doesn't find it
  • findNote(id): Note | null for those situations when it's unsure whether the id exists, but don't wanna fail (alternative would be to have hasNote(id), I slightly prefer the find syntax)
  • listNotes(): Note[] when dealing with arrays

If you like them we could also use those conventions, I am super easy

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm fine with the current convention in the latest iteration of this PR 👍

public path: string
public links: OutLink[]

constructor(id: ID, title: string, links: OutLink[], path: string, source: string) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Personally, for long argument lists made mainly of primitive data types, I prefer a named input object instead of positional arguments, since it's too easy to accidentally e.g. swap path and source order at call site

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree 100%.

In fact, for Note, I was thinking of just making it a type/interface, as I have removed all the methods I initially added here

Copy link
Collaborator

Choose a reason for hiding this comment

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

@riccardoferretti yes, all for using types over classes whenever we can!

// `[backlink:${backlink.id}]: ${backlink.filename} "${backlink.title}"`
// );
// }
const references = uniq(createMarkdownReferences(foam, id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@riccardoferretti is the purpose of uniq here to dedup the list in cases where there were multiple links to the same document? Unless those references are triple equal to each other, this will still leave duplicates as lodash#uniq uses SameValueZero comparison, which for object compares reference equality, not deep equality.

@@ -23,7 +23,7 @@ hello world from ./src/hello.ts!
const {args, flags} = this.parse(Hello)
const name = flags.name ?? 'world'

const wm = new WorkspaceManager('./foo');
const wm = new NoteGraph();
Copy link
Contributor

Choose a reason for hiding this comment

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

would make sense to call this "ng" - or maybe just "g" :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or, gosh, even graph to be painfully explicit :)

(This is in the dummy code in CLI, however, so not mega important, since this file won't live through first real commit to the CLI project)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good point, here I was just fixing the object used, wasn't paying attention to the name of the variable

as it's just a dummy I will leave it that way

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nevermind, there was a compilation error afterwards, so I just commented this part of the function :)

- `ast`: raw markdown ast
- `checksum`: if we do caching

### Link text
Copy link
Collaborator

Choose a reason for hiding this comment

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

Reviewers, please ignore this, needs cleaning up -- committed working scratchpad here accidentally

@@ -1,5 +1,5 @@
{
"name": "foam-cli",
"name": "@foam/cli",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is the namespace/scope required for the TypeScript scoping? We don't own the @foam scope on npm so we can't publish the CLI by this name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good point I didn't think of that. will use foam-core, foam-cli and foam-vscode

### Open questions

- How should writing to files work
- What if affected notes have unsaved changes
Copy link
Contributor

Choose a reason for hiding this comment

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

Good question! Writing to files (in the context of VS Code) should probably be done via something like applyEdit API (search from https://code.visualstudio.com/api/references/vscode-api#workspace)


### Node definition

What do we need the node (and edge metadata) to contain:
Copy link
Contributor

Choose a reason for hiding this comment

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

How about the graph metadata, e.g. "root folder of the graph"?

Copy link
Collaborator

Choose a reason for hiding this comment

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

As per comment later on, adding this would not hurt, so that implementers will have access to it if needed

What do we need the node (and edge metadata) to contain:

- `id`: tbd
- should be unique, needs some kind of unique gen function
Copy link
Contributor

Choose a reason for hiding this comment

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

Would path in relation to the graph root folder work as unique id?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@jojanaho#3766 these are my scribbles, I pushed these to @riccardoferretti's branch while we were hacking on a call

File path relative to workspace root is exactly what we landed on.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I started to look into this, there are a couple of open questions that we can chat about. I will not include it in this PR though so we can merge it in the meantime

Copy link
Collaborator

Choose a reason for hiding this comment

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

Perfect

- `id`: tbd
- should be unique, needs some kind of unique gen function
- should be reconstructable even if links are not updated every time
- what happens during rename? is reparenting the graph going to be hard?
Copy link
Contributor

Choose a reason for hiding this comment

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

What does the reparenting of the graph mean in this context?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe my thinking here was that if we use file path as id, and the file's path changes, is it possible to update the node's id, or must the node be deleted, new Note node be created, and then all in/outEdges be re-routed to it.

If id is mutable, then this is no issue.

If it's not, it's merely an inconvenience.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was leaning towards having the ID being the URI of the file, basically case n.2
I don't see it as a big issue, and I think it might be cleaner, but I am open to the idea of having another ID for the note (the question would be how to generate it/store it/....)

- what happens during rename? is reparenting the graph going to be hard?
- do id's need to be persistent, or can we create them per in-memory session, keep them stable despite renames, and then next session generate a new id?
- Ideally should be a path to file, so it's easy to look up from the graph by id for renaming
- `type`: Note | Image | etc
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe MIME-type?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We could implement a Node TS type that's a union of different types (Note | Image | Etc)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah, there are some considerations here similar to the ones I have encountered for the links. we should have a chat about these

- Ideally should be a path to file, so it's easy to look up from the graph by id for renaming
- `type`: Note | Image | etc
- `title`: can be read from markdown title or frontmatter metadata
- `path`: full path to file, relative to workspace (graph) root
Copy link
Contributor

Choose a reason for hiding this comment

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

The relation of this to id?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The same, if the id is a path, so either we drop path, or we keep both in case we have to change the id function later

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I started to look into this, but it won't probably make it in this PR as it turns out a couple of things change around wikilinks as a result


Not supported by Markdown Notes:

- `[[path/to/file-name.md]]` - Just use markdown links
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO this is absolutely needed (better yet in format of [[path/to/file-name]]). Foldering of notes would probably be pretty typical use case (is at least to me). It's not particularly hard to implement in comparison to flat notes, and looks like we are already building the base for functionality that's currently in Markdown Notes...

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed


Issues:

- Name clashes in directories
Copy link
Contributor

Choose a reason for hiding this comment

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

Not an issue if root-based wikilinks are supported

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

what do you mean? I am not familiar with root-based wikilinks

Copy link
Collaborator

Choose a reason for hiding this comment

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

@riccardoferretti I think @jojanaho meant the line he commented on above, e.g. [[path/to/file-name.md]].

Sounds like we need to write a further document on aligning how we'll approach this. @jojanaho on our call with Riccardo yesterday we brain stormed a few ideas (in the scratchpad section above) but didn't make any decisions or arrive in firm conclusions

Issues:

- Name clashes in directories
- Name clashes between extensions
Copy link
Contributor

Choose a reason for hiding this comment

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

This could probably be handled with linting

- Change filename/title needs to reflect everywhere
- Orphaning

- If we can't rely on in-memory process to rename things correctly while changes happen (e.g. file is renamed, moved, deleted, or titled) <ref id="1" />
Copy link
Contributor

Choose a reason for hiding this comment

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

Getting this to work properly probably requires using VS code APIs for rename operations (e.g. https://code.visualstudio.com/api/references/vscode-api#RenameProvider)


How others solve this:

- Unique ids -- could support optionally as part of file name or front matter metadata. Should not be required.
Copy link
Contributor

Choose a reason for hiding this comment

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

For renames outside VS code file hash could maybe do, inside VS code probably through APIs(?)

Copy link
Collaborator

Choose a reason for hiding this comment

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

@jojanaho could you expand on this?

@@ -0,0 +1,89 @@
import { Graph, Edge } from 'graphlib'
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe call this file (core.ts) as notegraph.ts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 -- I'd say note-graph.ts since capital G makes it a compound word, and therefore should be separated.

export interface NoteLink {
to: ID
text: string
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah this could be interesting, but I want to better understand the use case (do we need to update the info on every edit, or only on save? depends on what we need the info for I think)

public id: ID
public title: string
public source: string
public path: string
Copy link
Contributor

Choose a reason for hiding this comment

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

here id vs path

Copy link
Collaborator

Choose a reason for hiding this comment

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

Mentioned this on previous comment, but the fact that the ID happens to be a path is an implementation detail and we shouldn't rely on the fact. If we do need a file path (and we do), I think it would be good to keep it in path

public path: string
public links: NoteLink[]

constructor(id: ID, title: string, links: NoteLink[], path: string, source: string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe use Partials here? https://stackoverflow.com/a/37682352

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was wondering whether this needs to be a class in the first place, or just a type/interface - what do you think?



export class NoteGraph {
private graph: Graph
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

Currently the old WorkspaceManager.path was never used, but it may be useful considering that in the graph nodes we want to keep relative urls, and it may be useful to have the information at hand to construct absolute file URIs

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah I removed it because it wasn't used, and I was debating whether this info should live in the config or the graph. currently we store the absolute paths in the graph, having a base path would allow us to make those relative. why do you prefer relative urls @jevakallio ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@riccardoferretti that's a good question.

I guess I prefer them for readability -- the paths will be closer to what's being used in links and generated into link ref defs -- but that's not a strong preference.

Another argument for relative paths is that if we ever persist the graph, relative links continue to work when e.g. cloning the repo to different computer, moving it to different folders, or even as logical paths in a GitHub repo / published site.

If there's no good reason for absolute paths in Note.path, I'd rather keep it relative, but can be convinced otherwise.

- `text`: The link label
- `type` markdown | mediawiki | image | http
- `section`
- `block` (ref)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@jojanaho this is in reference to a vague plan I have about building Roam's block references and block embeds into Foam. This is not something we need to think of now, more of a future consideration.

The idea here was to annotate blocks of text (paragraph, bullet, line, etc.) using some method (HTML tags were discussed), and then being able to link to that section from another document such that in VS Code that block is brought as an editable embed to that document. Additionally, this linking would be supported by a hypothetical Foam static site generator, that would be able to construct published pages from blocks in other documents.

"declarationMap": true,
"baseUrl": ".",
"paths": {
"@foam/core": ["./packages/foam-core/src"],
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the @scope/package naming convention is not mandatory, perhaps we should drop it? Alternatively we'll need another scope, since we don't own the npm user/org @foam. We could do @foambubble, but that makes it slightly confusing as folks might think it's somehow coupled to the GitHub org.

Copy link
Collaborator

@jevakallio jevakallio left a comment

Choose a reason for hiding this comment

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

@riccardoferretti did another review, and this is looking better and better on every pass!

I'd like to aim to get a version of foam-core landed tomorrow, as @anku255 and @chirag-singhal may need to start work on the CLI/janitor on Monday, and may need access to this functionality.

I'm ok having some of the details be reworked in future PRs as long as we get the broad strokes correctly:

  • The public NoteGraph API (some suggestions on file name, constructor signature)
  • The package naming convention (can't publish to @foam npm org)
  • Any other issues that we can address in a reasonable time frame.

@riccardoferretti let me know if you don't have time to look at this tomorrow (Sunday) and I can pick up the branch, clean it up and land it

Copy link
Collaborator

@jevakallio jevakallio left a comment

Choose a reason for hiding this comment

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

I tested this and fixed a couple of issues! @jojanaho @riccardoferretti could one of you test this locally and see if you can get it to break? I can't.

If it works for you, feel free to merge, I'm leaving an approving review here on the assumption that my last commits are ok (but don't assume they are).

I'll be back in a few hours.

@riccardoferretti riccardoferretti merged commit 3191d7d into master Jul 12, 2020
Alognas2 added a commit to Alognas2/foam that referenced this pull request Aug 5, 2024
renamed @foam/core => foam-core and @foam/cli => foam-cli (see foambubble/foam#83 (comment))
Celoskip3 added a commit to Celoskip3/foam that referenced this pull request Aug 10, 2024
renamed @foam/core => foam-core and @foam/cli => foam-cli (see foambubble/foam#83 (comment))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants