Compare commits

..

14 Commits

Author SHA1 Message Date
5eb1a87123 Org-roam 0.1.1 (#83)
* Update README

* Add CONTRIBUTING

* Add CHANGELOG
2020-02-15 15:30:33 +08:00
914bbe3b53 (docs): overhaul documentation (#76)
Updated for latest Org-roam, and add all relevant information into the new documentation site.
2020-02-15 14:49:11 +08:00
01130b49e1 Fix org-roam hooks not being attached on nested files (#82) 2020-02-15 11:36:10 +08:00
684ab67952 Insert org-roam-links relative to current file (#78)
Fixes #77
2020-02-14 23:14:38 +08:00
60eeb3985a Move org-roam sync/async utilities to org-roam-utils (#75)
This fixes #74 for some reason
2020-02-14 01:52:56 +08:00
a6cdc77980 deduplicate async/non-async functions (#72)
Signed-off-by: Jethro Kuan <jethrokuan95@gmail.com>
2020-02-13 20:08:09 +08:00
9cd12a4f11 Add github test action (#73)
* Add github test action

* add mock tests
2020-02-13 16:41:27 +08:00
e00538f909 Fix org-roam-insert inserting absolute paths (#71)
Fixes #70
2020-02-13 16:04:41 +08:00
270995b2d4 Refactored functions to buffer-passing style (#69)
See https://nullprogram.com/blog/2014/05/27/
2020-02-13 15:55:21 +08:00
1cfd71f5a8 Fix several linting errors (#68)
Also add @alphapapa's makem scripts
2020-02-13 13:20:48 +08:00
ede33d7411 Fix org-roam--make-file to create files with extensions (#67) 2020-02-13 12:36:19 +08:00
7817116403 add more metadata into org-roam (#66)
In preparation for publishing to MELPA
2020-02-13 04:09:47 +08:00
efd2072070 Add documentation for configuration options (#65)
* document org-roam-directory

* rename org-roam-position, and document org-roam-buffer-position

* document org-roam-buffer

* document org-roam-buffer-width

* Document org-roam-graphviz-executable

* document org-roam-graph-viewer

* document org-roam-link-title-format
2020-02-13 03:14:34 +08:00
791c059200 Simplify org-roam-insert and org-roam-find-file (#62)
* Simplify org-roam-insert and org-roam-find-file

See #59.

* Add docs for org-roam automatic filenaming

* Update installation instructions
2020-02-13 00:25:45 +08:00
23 changed files with 1972 additions and 343 deletions

69
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,69 @@
# * test.yml --- Test Emacs packages using makem.sh on GitHub Actions
# https://github.com/alphapapa/makem.sh
# Based on Steve Purcell's examples at
# <https://github.com/purcell/setup-emacs/blob/master/.github/workflows/test.yml>,
# <https://github.com/purcell/package-lint/blob/master/.github/workflows/test.yml>.
# * License:
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# * Code:
name: "CI"
on:
pull_request:
push:
branches:
- master
- develop
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
emacs_version:
- 26.3
- snapshot
steps:
- uses: purcell/setup-emacs@master
with:
version: ${{ matrix.emacs_version }}
- uses: actions/checkout@v2
- name: Initialize sandbox
run: |
SANDBOX_DIR=$(mktemp -d) || exit 1
echo ::set-env name=SANDBOX_DIR::$SANDBOX_DIR
./makem.sh -vv --sandbox $SANDBOX_DIR --install-deps --install-linters
# The "all" rule is not used, because it treats compilation warnings
# as failures, so linting and testing are run as separate steps.
- name: Lint
continue-on-error: true
run: ./makem.sh -vv --sandbox $SANDBOX_DIR lint
- name: Test
if: always() # Run test even if linting fails.
run: ./makem.sh -vv --sandbox $SANDBOX_DIR test
# Local Variables:
# eval: (outline-minor-mode)
# End:

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/.sandbox/

37
CHANGELOG.md Normal file
View File

@ -0,0 +1,37 @@
# Changelog
## 0.1.1 (2020-02-15)
Mostly a documentation/cleanup release.
### New Features
* [#62][gh-62] Add the options `org-roam-use-timestamps-as-filename` and `org-roam-file-format`, more in documentation.
### Breaking Changes
* [#62][gh-62] The ID (file-name) workflow is no longer first-class, but a fallback when titles don't exist.
### Changes
* [#66][gh-66], [#68][gh-68]: Improved the quality of the package in preparation of submission to MELPA
* [#73][gh-73]: Added CI to the project via Github Issues (Thanks [@alphapapa](https://github.com/alphapapa/) for scripts and setup)
* [#69][gh-69], [#72][gh-72], [#75][gh-75]: Major cleanup and de-duplication of code
### Bugfixes
* [#67][gh-67]: Fixed `org-roam--make-file` not creating files with extensions
* [#71][gh-71], [#78][gh-78]: Fixed `org-roam-insert` not inserting correct paths
* [#82][gh-82]: Fixed nested Org-roam files not being detected as part of Org-roam
[gh-62]: https://github.com/jethrokuan/org-roam/pull/66
[gh-66]: https://github.com/jethrokuan/org-roam/pull/66
[gh-67]: https://github.com/jethrokuan/org-roam/pull/67
[gh-68]: https://github.com/jethrokuan/org-roam/pull/68
[gh-69]: https://github.com/jethrokuan/org-roam/pull/69
[gh-71]: https://github.com/jethrokuan/org-roam/pull/71
[gh-72]: https://github.com/jethrokuan/org-roam/pull/72
[gh-73]: https://github.com/jethrokuan/org-roam/pull/73
[gh-75]: https://github.com/jethrokuan/org-roam/pull/75
[gh-78]: https://github.com/jethrokuan/org-roam/pull/78
[gh-82]: https://github.com/jethrokuan/org-roam/pull/82
# Local Variables:
# eval: (auto-fill-mode -1)
# End:

34
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,34 @@
# Contributing
If you discover issues, have ideas for improvements or new features, please
report them to the [issue tracker][1] of the repository or submit a pull
request. Please, try to follow these guidelines when you do so.
## Issue reporting
* Check that the issue has not already been reported.
* Check that the issue has not already been fixed in the latest code
(a.k.a. `develop`).
* Be clear, concise and precise in your description of the problem.
* Open an issue with a descriptive title and a summary in grammatically correct,
complete sentences.
* Include any relevant code to the issue summary.
* If you're reporting performance issues it'd be nice if you added some profiling data (Emacs has a built-in profiler).
## Pull requests
* Read [how to properly contribute to open source projects on Github][2].
* Use a topic branch to easily amend a pull request later, if necessary.
* Write [good commit messages][3].
* Mention related tickets in the commit messages (e.g. `[Fix #N] Add missing autoload cookies`)
* Update the [changelog][5].
* Use the same coding conventions as the rest of the project.
* Verify your Emacs Lisp code with `checkdoc` (<kbd>C-c ? d</kbd>).
* Open a [pull request][4] that relates to *only* one subject with a clear title
and description in grammatically correct, complete sentences.
[1]: https://github.com/jethrokuan/org-roam/issues
[2]: http://gun.io/blog/how-to-github-fork-branch-and-pull-request
[3]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[4]: https://help.github.com/articles/using-pull-requests
[5]: https://github.com/jethrokuan/org-roam/blob/master/CHANGELOG.md

56
Makefile Normal file
View File

@ -0,0 +1,56 @@
# * makem.sh/Makefile --- Script to aid building and testing Emacs Lisp packages
# This Makefile is from the makem.sh repo: <https://github.com/alphapapa/makem.sh>.
# * Arguments
# For consistency, we use only var=val options, not hyphen-prefixed options.
# NOTE: I don't like duplicating the arguments here and in makem.sh,
# but I haven't been able to find a way to pass arguments which
# conflict with Make's own arguments through Make to the script.
# Using -- doesn't seem to do it.
ifdef install-deps
INSTALL_DEPS = "--install-deps"
endif
ifdef install-linters
INSTALL_LINTERS = "--install-linters"
endif
ifdef sandbox
ifeq ($(sandbox), t)
SANDBOX = --sandbox
else
SANDBOX = --sandbox $(sandbox)
endif
endif
ifdef debug
DEBUG = "--debug"
endif
# ** Verbosity
# Since the "-v" in "make -v" gets intercepted by Make itself, we have
# to use a variable.
verbose = $(v)
ifneq (,$(findstring vv,$(verbose)))
VERBOSE = "-vv"
else ifneq (,$(findstring v,$(verbose)))
VERBOSE = "-v"
endif
# * Rules
# TODO: Handle cases in which "test" or "tests" are called and a
# directory by that name exists, which can confuse Make.
%:
@./makem.sh $(DEBUG) $(VERBOSE) $(SANDBOX) $(INSTALL_DEPS) $(INSTALL_LINTERS) $(@)
.DEFAULT: init
init:
@./makem.sh $(DEBUG) $(VERBOSE) $(SANDBOX) $(INSTALL_DEPS) $(INSTALL_LINTERS)

View File

@ -1,28 +1,26 @@
[![License GPL 3][badge-license]](http://www.gnu.org/licenses/gpl-3.0.txt)
[![Documentation Status](https://readthedocs.org/projects/org-roam/badge/?version=latest)](https://org-roam.readthedocs.io/en/latest/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/org-roam/badge/?version=latest)](https://org-roam.readthedocs.io/en/latest/?badge=latest)
[![GitHub Release](https://img.shields.io/github/v/release/jethrokuan/org-roam)](https://img.shields.io/github/v/release/jethrokuan/org-roam)
## Synopsis
Org-roam is a rudimentary [Roam][roamresearch] replica built around Org-roam is a rudimentary [Roam][roamresearch] replica built around
the all-powerful [Org-mode][org]. the all-powerful [Org-mode][org].
Like Roam, Org-roam offers a powerful and effortless non-hierarchical Like Roam, Org-roam offers a powerful and effortless non-hierarchical
note-taking approach. With Org-roam, notes flow naturally, making note-taking approach. With Org-roam, notes flow naturally, making
note-taking fun and easy. note-taking fun and easy. Org-roam *enables* a note-taking workflow that
is not fluid with vanilla Org-mode (more in [this blog
post](https://blog.jethro.dev/posts/how_to_take_smart_notes_org/)).
The goal of the project is to implement core features of Roam around The goal of the project is to implement core features of Roam around
Org-mode, and eventually introduce features enabled by the Emacs Org-mode, and eventually introduce features enabled by the Emacs
ecosystem. ecosystem.
For more documentation, see [the documentation page](https://org-roam.readthedocs.io/en/latest/). Visit [the documentation
page](https://org-roam.readthedocs.io/en/latest/) for a tutorial and
## Understanding Roam more links.
To understand more about Roam, I recommend the following links:
- [Building a second brain in
Roam](https://reddit.com/r/RoamResearch/comments/eho7de/building_a_second_brain_in_roamand_why_you_might)
- [Roam: Why I Love It and How I Use
It](https://www.nateliason.com/blog/roam)
## Project Status
As of February 2020, it is in a very early stage of development. As of February 2020, it is in a very early stage of development.
@ -37,16 +35,52 @@ used to navigate to the respective files.
![img](doc/images/org-roam-graph.gif) ![img](doc/images/org-roam-graph.gif)
## Installation
The recommended method is using use-package and straight, or a similar package manager.
```emacs-lisp
(use-package org-roam
:after org
:hook
((org-mode . org-roam-mode)
(after-init . org-roam--build-cache-async) ;; optional!
)
:straight (:host github :repo "jethrokuan/org-roam" :branch "develop")
:custom
(org-roam-directory "/path/to/org-files/")
:bind
("C-c n l" . org-roam)
("C-c n t" . org-roam-today)
("C-c n f" . org-roam-find-file)
("C-c n i" . org-roam-insert)
("C-c n g" . org-roam-show-graph))
```
For more detailed installation instructions, please see [the
installation
documentation](https://org-roam.readthedocs.io/en/develop/installation/).
## Knowledge Bases using Org-Roam ## Knowledge Bases using Org-Roam
- [Jethro Kuan](https://braindump.jethro.dev/) - [Jethro Kuan](https://braindump.jethro.dev/)
([Source](https://github.com/jethrokuan/braindump/tree/master/org)) ([Source](https://github.com/jethrokuan/braindump/tree/master/org))
## Changelog
A changelog is being maintained [here](CHANGELOG.md)
## Contributing ## Contributing
To report bugs and suggest new feature use the issue tracker. If you To report bugs and suggest new feature use the issue tracker. If you
have some code which you would like to be merged, then open a pull have some code which you would like to be merged, then open a pull
request. Please also see CONTRIBUTING.md. request. Please also see [CONTRIBUTING.md](CONTRIBUTING.md).
## License
Copyright © Jethro Kuan and contributors. Distributed under the GNU
General Public License, Version 3
[roamresearch]: https://www.roamresearch.com/ [roamresearch]: https://www.roamresearch.com/
[org]: https://orgmode.org/ [org]: https://orgmode.org/
[badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg

116
doc/configuration.md Normal file
View File

@ -0,0 +1,116 @@
To ensure that Org-roam remains manageable, the number of
configuration options is deliberately kept small. However, we have
attempted to accommodate as many usage styles as possible.
In this section, we'll go over the main customization options
available to Org-Roam. This section is *crucial*. We need to exploit
the flexibility of Emacs, and mould our tools exactly to our liking.
All of Org-roam's customization options can be viewed via `M-x
customize-group org-roam`.
## Setting the Org-roam Directory
Perhaps the single most important variable to set is
`org-roam-directory`. Set `org-roam-directory` to the folder
containing all your Org files:
```emacs-lisp
(setq org-roam-directory "/path/to/org/")
```
Every Org file, at any level of nesting, within `/path/to/org/` is
considered part of the Org-roam ecosystem.
## Org-roam Buffer
The Org-roam buffer defaults to popping up from the right. You may
choose to set it to pop up from the left with `(setq
org-roam-buffer-position 'left)`.
The Org-roam buffer name can also be renamed: e.g. `(setq
org-roam-buffer "*my-buffer-name*")`.
The Org-roam buffer width is adjustable via `org-roam-buffer-width`.
The value of `org-roam-buffer-width` set as a percentage of the total
frame width. For example:
```emacs-lisp
(setq org-roam-buffer-width 0.4)
```
Will result in the Org-roam buffer taking up 40% of the screen width.
I have found this to be a good number.
## Org-roam Links
By default, links are inserted with the title as the link description.
This can make them hard to distinguish from external links. If you
wish, you may choose add special indicators for Org-roam links by
tweaking `org-roam-link-title-format`, for example:
```emacs-lisp
(setq org-roam-link-title-format "R:%s")
```
## Org-roam Files
These customization options revolve around the Org files created and
managed by Org-roam.
### Automatically Creating Files Using Timestamp
A common hassle is ensuring that files are uniquely named within the
Org-roam directory. Org-roam's default workflow utilizes the title of
Org files in all of its main commands (`org-roam-insert`,
`org-roam-find-file`). Hence, having any unique file name is a decent
option, and the default workflow uses the timestamp as the filename.
The format of the filename is specified by the string
`org-roam-file-format`, which defaults to `"%Y%m%d%H%M%S"`. To see
valid specifications, see the help (`C-h f`) for `format-time-string`.
There are several reasons for keeping filenames meaningful. For
example, one may wish to publish the Org files, and some publishing
methods such as Org-publish use the file names as slugs for the URLs.
If you wish to maintain manual control of filenames, set
`org-roam-use-timestamp-as-filename` to `nil`:
```emacs-lisp
(setq org-roam-use-timestamp-as-filename nil)
```
When this setting is turned off, the user is instead manually prompted
for a filename. It is then the user's responsibility to ensure that
the file names are unique.
### Autopopulating Titles
The default workflow uses the title of the Org file in several
commands. The title is specified via the `#+TITLE:` attribute,
typically near the top of the file. The option
`org-roam-autopopulate-title` defaults to `t`. When true, the title
attribute is automatically inserted into the files created via
Org-roam commands. Setting it to `nil` will disable this behaviour.
## Org-roam Graph Viewer
Org-roam generates an SVG image using
[Graphviz](https://graphviz.org/). To setup graph navigation, see the
[Graph Setup](graph_setup.md) page.
Org-roam tries its best to locate the Graphviz executable from your
PATH, but if it fails to do so, you may set it manually:
```
(setq org-roam-graphviz-executable "/path/to/dot")
```
Org-roam also attempts to use Firefox (located on PATH) to view the
SVG, you may choose to set it to any compatible program:
```
(setq org-roam-graph-viewer "/path/to/image-viewer")
```

View File

@ -1,10 +1,9 @@
## Ecosystem
A number of packages work well combined with Org-Roam: A number of packages work well combined with Org-Roam:
### Deft ## Deft
[Deft](https://jblevins.org/projects/deft/) provides a nice
interface for browsing and filtering org-roam notes. [Deft][deft] provides a nice interface for browsing and filtering
org-roam notes.
``` ```
(use-package deft (use-package deft
@ -19,7 +18,12 @@ interface for browsing and filtering org-roam notes.
(deft-use-filename-as-title t)) (deft-use-filename-as-title t))
``` ```
### Org-journal The Deft interface can slow down quickly when the number of files get
huge. [Notdeft][notdeft] is a fork of Deft that uses an external
search engine and indexer.
## Org-journal
[Org-journal](https://github.com/bastibe/org-journal) is a more [Org-journal](https://github.com/bastibe/org-journal) is a more
powerful alternative to the simple function `org-roam-today`. It powerful alternative to the simple function `org-roam-today`. It
provides better journaling capabilities, and a nice calendar interface provides better journaling capabilities, and a nice calendar interface
@ -35,3 +39,59 @@ to see all dated entries.
(org-journal-dir "/path/to/org-roam-files/") (org-journal-dir "/path/to/org-roam-files/")
(org-journal-date-format "%A, %d %B %Y")) (org-journal-date-format "%A, %d %B %Y"))
``` ```
## Note-taking Add-ons
These are some plugins that make note-taking in Org-mode more
enjoyable.
### Org-download
[Org-download][org-download] lets you screenshot and yank images from
the web into your notes:
![org-download](images/org-download.gif)
```emacs-lisp
(use-package org-download
:after org
:bind
(:map org-mode-map
(("s-Y" . org-download-screenshot)
("s-y" . org-download-yank))))
```
### mathpix.el
[mathpix.el][mathpix-el] uses [Mathpix's](https://mathpix.com/) API to convert clips into
latex equations:
![mathpix](images/mathpix.gif)
```emacs-lisp
(use-package mathpix.el
:straight (:host github :repo "jethrokuan/mathpix.el")
:custom ((mathpix-app-id "app-id")
(mathpix-app-key "app-key"))
:bind
("C-x m" . mathpix-screenshot))
```
### Org-noter / Interleave
[Org-noter][org-noter] and [Interleave][interleave] are both projects
that allow synchronised annotation of documents (PDF, EPUB etc.)
within Org-mode.
### Org-ref
[Org-ref][org-ref] does citation and bibliography management in
Org-mode, and a great tool for scientific notes.
[deft]: https://jblevins.org/projects/deft/
[notdeft]: https://github.com/hasu/notdeft
[org-download]: https://github.com/abo-abo/org-download
[mathpix-el]: https://github.com/jethrokuan/mathpix.el
[org-noter]: https://github.com/weirdNox/org-noter
[interleave]: https://github.com/rudolfochrist/interleave
[org-ref]: https://github.com/jkitchin/org-ref

39
doc/graph_setup.md Normal file
View File

@ -0,0 +1,39 @@
The setup is similar to that of org-protocol. Here `roam://` links are
defined, and need to be associated with an application.
The gist of the setup is setting up a Bash script to trim off the
`roam://` prefix from the link, causing the desktop application to
call `emacsclient path/to/org-roam-file.org`.
## Setting Up for Linux
Create a desktop application. I place mine in
`~/.local/share/applications/roam.desktop`:
```
[Desktop Entry]
Name=Org-Roam Client
Exec=/home/jethro/.local/bin/launch_emacs %u
Icon=emacs-icon
Type=Application
Terminal=false
MimeType=x-scheme-handler/roam
```
Note the `Exec` key is set to a bash script poorly named
`launch_emacs`. You can set it to whatever you want.
Create the corresponding bash script, and make it executable. Here's
how it looks like:
```bash
#!/usr/bin/env bash
emacsclient "${1#*:}"
```
Finally, associate `roam://` links with the desktop application by
running in your shell:
```bash
xdg-mime default roam.desktop x-scheme-handler/roam
```

BIN
doc/images/mathpix.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 KiB

BIN
doc/images/org-download.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -6,11 +6,13 @@ The recommended method is using [use-package][use-package] and
``` ```
(use-package org-roam (use-package org-roam
:after org :after org
:hook (org-mode . org-roam-mode) :hook
:straight (:host github :repo "jethrokuan/org-roam") ((org-mode . org-roam-mode)
(after-init . org-roam--build-cache-async) ;; optional!
)
:straight (:host github :repo "jethrokuan/org-roam" :branch "develop")
:custom :custom
(org-roam-directory "/path/to/org-files/") (org-roam-directory "/path/to/org-files/")
(org-roam-link-representation 'title) ;; or keep it as 'id
:bind :bind
("C-c n l" . org-roam) ("C-c n l" . org-roam)
("C-c n t" . org-roam-today) ("C-c n t" . org-roam-today)
@ -26,10 +28,34 @@ directory and add it to your load path:
git clone https://github.com/jethrokuan/org-roam/ ~/.emacs.d/elisp/org-roam git clone https://github.com/jethrokuan/org-roam/ ~/.emacs.d/elisp/org-roam
``` ```
```
(use-package org-roam
:after org
:load-path "elisp/"
:hook
((org-mode . org-roam-mode)
(after-init . org-roam--build-cache-async) ;; optional!
)
:custom
(org-roam-directory "/path/to/org-files/")
:bind
("C-c n l" . org-roam)
("C-c n t" . org-roam-today)
("C-c n f" . org-roam-find-file)
("C-c n i" . org-roam-insert)
("C-c n g" . org-roam-show-graph))
```
Or without use-package:
``` ```
(add-to-list 'load-path "./elisp") (add-to-list 'load-path "./elisp")
(require 'org-roam) (require 'org-roam)
``` ```
There are a number of important configuration options, that greatly
affect the Roam workflow. Do look through them at the
[Configuration](configuration.md) page.
[use-package]: https://github.com/jwiegley/use-package [use-package]: https://github.com/jwiegley/use-package
[straight]: https://github.com/raxod502/straight.el [straight]: https://github.com/raxod502/straight.el

View File

@ -0,0 +1,20 @@
## Recommended Books
- [How to Take Smart Notes][1]
## Articles
- [How to Take Smart Notes in Org-mode - Jethro Kuan][7]
- [The Zettelkasten Method - LessWrong 2.0][3]
- [Building a second brain in Roam][4]
- [Roam: Why I Love It and How I Use It][5]
- [Adam Keesling's Twitter Thread][6]
## What to Do With Your Notes
- [How to Use Roam to Outline a New Article in Under 20 Minutes][2]
[1]: https://www.goodreads.com/book/show/34507927-how-to-take-smart-notes?ac=1&from_search=true&qid=6L8iEE1FIA&rank=1
[2]: https://www.youtube.com/watch?v=RvWic15iXjk
[3]: https://www.lesswrong.com/posts/NfdHG6oHBJ8Qxc26s/the-zettelkasten-method-1
[4]: https://reddit.com/r/RoamResearch/comments/eho7de/building_a_second_brain_in_roamand_why_you_might
[5]: https://www.nateliason.com/blog/roam
[6]: https://twitter.com/adam_keesling/status/1196864424725774336?s=20
[7]: https://blog.jethro.dev/posts/how_to_take_smart_notes_org/

View File

@ -1,20 +1,69 @@
### A Tour of Org-Roam Org-roam was built to support a workflow that was not possible with
vanilla Org-mode. This flow is modelled after the [Zettelkasten
method][zettelkasten], and many of [Roam Research][roam]'s workflows.
Understanding this flow is crucial! Org-roam doesn't auto-magically
make your note-taking better -- it's changing the note-taking workflow
that does.
All of this starts from the note. A note is just a simple `.org` file To understand more the methods and madness, the [Note-Taking
in the directory. Any org file in the directory is considered part of Workflow][appendix:ntw] page contains a page of useful references.
the org-roam ecosystem. Notes are quickly linked together (and created I've also written [a post][jethro-blog-post] about how I use Org-roam.
if necessary) using `org-roam-insert`.
![org-roam-insert](images/org-roam-insert.gif) Without further ado, let's begin!
Org-roam tracks all of these file links, and builds a cache ## Building the Cache
asynchronously in the background. This cache is used to populate the
backlinks buffer, which shows files that link to the current file, as Assuming you've set `org-roam-directory` appropriately, running `M-x
well as some preview contents: org-roam--build-cache-async` should build up the caches that will
allow you to begin using Org-roam. I do this on startup:
```emacs-lisp
(add-hook 'after-init-hook 'org-roam--build-cache-async)
```
## Finding a Note
`org-roam-find-file` shows you the list of notes you currently have in
Org-roam. Selecting the title will bring you to the corresponding
note. Entering a title of a note that does not yet exist will create a
new note with that title.
![org-roam-find-file](images/org-roam-find-file.gif)
## Inserting Links
Within your Org-roam notes, you are encouraged to liberally insert
links to existing (or new) Org-roam notes with `org-roam-insert`.
Entering a non-existent title will also create a new note with that
title.
![org-roam-insert](images/org-roam-insert-filetag.gif)
It is crucial for good usage of Org-roam to insert links liberally,
where you want them the notes to resurface!
## The Org-roam Buffer
All of Org-roam's operations are designed such that the built cache is
a consistent view of the inter-connectivity between your notes. The
Org-roam buffer shows backlinks: i.e. the files that link to the
currently viewed file, along with some surrounding context. The
Org-roam buffer will always show the backlinks for the current
Org-roam file in view.
![org-roam-buffer](images/org-roam-buffer.gif) ![org-roam-buffer](images/org-roam-buffer.gif)
These file links also form a graph. The generated graph is navigable ## Exporting the Graph
in Emacs.
It's also possible to export the links as a graph, using graphviz. The
generated graph is navigable in Emacs, but requires some additional
setup, which I describe in the [Graph Appendix][appendix:graph-setup]
page.
![org-roam-graph](images/org-roam-graph.gif) ![org-roam-graph](images/org-roam-graph.gif)
[zettelkasten]: https://zettelkasten.de/
[appendix:ntw]: notetaking_workflow.md
[appendix:graph-setup]: graph_setup.md
[roam]: https://www.roamresearch.com/
[jethro-blog-post]: https://blog.jethro.dev/posts/how_to_take_smart_notes_org/

1079
makem.sh Executable file

File diff suppressed because it is too large Load Diff

View File

@ -7,8 +7,11 @@ nav:
- Home: index.md - Home: index.md
- A Tour of Org-Roam: tour.md - A Tour of Org-Roam: tour.md
- Installation: installation.md - Installation: installation.md
- Configuration: configuration.md
- Ecosystem: ecosystem.md - Ecosystem: ecosystem.md
- Similar Packages: comparison.md - Similar Packages: comparison.md
- "Appendix: Note-taking Workflow": notetaking_workflow.md
- "Appendix: Graph Setup": graph_setup.md
markdown_extensions: markdown_extensions:
- admonition - admonition
- pymdownx.betterem: - pymdownx.betterem:

121
org-roam-utils.el Normal file
View File

@ -0,0 +1,121 @@
;;; org-roam-utils.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 0.1.0
;; Package-Requires: ((emacs "26.1"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This library is an attempt at injecting Roam functionality into Org-mode.
;; This is achieved primarily through building caches for forward links,
;; backward links, and file titles.
;;
;;; Code:
(require 'org)
(require 'org-element)
(require 'subr-x)
(require 'cl-lib)
(defun org-roam--find-files (dir)
"Return all `org-roam' files in `DIR'."
(if (file-exists-p dir)
(let ((files (directory-files dir t "." t))
(dir-ignore-regexp (concat "\\(?:"
"\\."
"\\|\\.\\."
"\\)$"))
result)
(dolist (file files)
(cond
((file-directory-p file)
(when (not (string-match dir-ignore-regexp file))
(setq result (append (org-roam--find-files file) result))))
((and (file-readable-p file)
(string= (file-name-extension file) "org"))
(setq result (cons (file-truename file) result)))))
result)))
(defun org-roam--parse-content (&optional file-path)
"Parse the current buffer, and return a list of items for processing."
(org-element-map (org-element-parse-buffer) 'link
(lambda (link)
(let ((type (org-element-property :type link))
(path (org-element-property :path link))
(start (org-element-property :begin link)))
(when (and (string= type "file")
(string= (file-name-extension path) "org"))
(goto-char start)
(let* ((element (org-element-at-point))
(content (or (org-element-property :raw-value element)
(buffer-substring
(or (org-element-property :content-begin element)
(org-element-property :begin element))
(or (org-element-property :content-end element)
(org-element-property :end element))))))
(list :from (or file-path
(file-truename (buffer-file-name (current-buffer))))
:to (file-truename (expand-file-name path org-roam-directory))
:content (string-trim content))))))))
(cl-defun org-roam--insert-item (item &key forward backward)
"Insert ITEM into FORWARD and BACKWARD cache.
ITEM is of the form: (:from from-path :to to-path :content preview-content)."
(pcase-let ((`(:from ,p-from :to ,p-to :content ,content) item))
;; Build forward-links
(let ((links (gethash p-from forward)))
(if links
(puthash p-from
(if (member p-to links)
links
(cons p-to links)) forward)
(puthash p-from (list p-to) forward)))
;; Build backward-links
(let ((contents-hash (gethash p-to backward)))
(if contents-hash
(if-let ((contents-list (gethash p-from contents-hash)))
(let ((updated (cons content contents-list)))
(puthash p-from updated contents-hash)
(puthash p-to contents-hash backward))
(progn
(puthash p-from (list content) contents-hash)
(puthash p-to contents-hash backward)))
(let ((contents-hash (make-hash-table :test #'equal)))
(puthash p-from (list content) contents-hash)
(puthash p-to contents-hash backward))))))
(defun org-roam--extract-title ()
"Extract the title from `BUFFER'."
(org-element-map
(org-element-parse-buffer)
'keyword
(lambda (kw)
(when (string= (org-element-property :key kw) "TITLE")
(org-element-property :value kw)))
:first-match t))
(provide 'org-roam-utils)
;;; org-roam-utils.el ends here

View File

@ -1,9 +1,39 @@
;;; org-roam.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*- ;;; org-roam.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 0.1.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (async "1.9.4"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary: ;;; Commentary:
;; ;;
;; This library is an attempt at injecting Roam functionality into Org-mode.
;; This is achieved primarily through building caches for forward links,
;; backward links, and file titles.
;;
;;
;;; Code: ;;; Code:
(eval-when-compile (require 'cl-lib)) (eval-when-compile (require 'cl-lib))
(require 'dash) (require 'dash)
(require 'org-element) (require 'org-element)
@ -11,19 +41,24 @@
(require 'subr-x) (require 'subr-x)
(require 's) (require 's)
(require 'f) (require 'f)
(require 'org-roam-utils)
;;; Customizations ;;; Customizations
(defgroup org-roam nil (defgroup org-roam nil
"Roam Research replica in Org-mode." "Roam Research replica in Org-mode."
:group 'org :group 'org
:prefix "org-roam-") :prefix "org-roam-"
:link '(url-link :tag "Github" "https://github.com/jethrokuan/org-roam")
:link '(url-link :tag "Online Manual" "https://org-roam.readthedocs.io/"))
(defcustom org-roam-directory (expand-file-name "~/org-roam/") (defcustom org-roam-directory (expand-file-name "~/org-roam/")
"Org-roam directory." "Path to Org-roam files.
:type 'directory
All Org files, at any level of nesting, is considered part of the Org-roam."
:type 'directoy
:group 'org-roam) :group 'org-roam)
(defcustom org-roam-position 'right (defcustom org-roam-buffer-position 'right
"Position of `org-roam' buffer. "Position of `org-roam' buffer.
Valid values are Valid values are
@ -33,26 +68,8 @@ Valid values are
(const right)) (const right))
:group 'org-roam) :group 'org-roam)
(defcustom org-roam-link-representation 'id (defcustom org-roam-file-format "%Y%m%d%H%M%S"
"The value used to represent an org-roam link. "The timestamp format to use filenames."
Valid values are
* file,
* title."
:type '(choice (const id)
(const title))
:group 'org-roam)
(defcustom org-roam-timestamped-files nil
"Whether to use timestamps to generate unique filenames."
:type 'boolean
:group 'org-roam)
(defcustom org-roam-timestamp-format "%Y-%m-%d%H%M%S"
"The timestamp format to use filenames.")
(defcustom org-roam-link-id-format "§%s"
"The format string used when inserting org-roam links that use id."
:type 'string :type 'string
:group 'org-roam) :group 'org-roam)
@ -61,6 +78,11 @@ Valid values are
:type 'string :type 'string
:group 'org-roam) :group 'org-roam)
(defcustom org-roam-use-timestamp-as-filename t
"Whether to use timestamp as a file name. If not true, prompt for a file name each time."
:type 'boolean
:group 'org-roam)
(defcustom org-roam-autopopulate-title t "Whether to autopopulate the title." (defcustom org-roam-autopopulate-title t "Whether to autopopulate the title."
:type 'boolean :type 'boolean
:group 'org-roam) :group 'org-roam)
@ -130,35 +152,16 @@ If called interactively, then PARENTS is non-nil."
(defun org-roam--org-roam-file-p () (defun org-roam--org-roam-file-p ()
"Return t if file is part of org-roam system, false otherwise." "Return t if file is part of org-roam system, false otherwise."
(and (buffer-file-name (current-buffer)) (and (buffer-file-name (current-buffer))
(f-child-of-p (file-truename (buffer-file-name (current-buffer))) (f-descendant-of-p (file-truename (buffer-file-name (current-buffer)))
org-roam-directory))) org-roam-directory)))
(defun org-roam--get-title (file) (defun org-roam--get-title-from-cache (file)
"Return title of `FILE'. "Return title of `FILE' from the cache."
It first tries the cache. If the cache does not contain the file,
it will return the title by loading the file."
(or (gethash file org-roam-titles-cache) (or (gethash file org-roam-titles-cache)
(org-roam--extract-file-title file))) (progn
(unless org-roam-cache-initialized
(defun org-roam--find-files (dir) (user-error "The Org-Roam caches aren't built! Please run org-roam--build-cache-async"))
"Return all org-roam files in `DIR'." nil)))
(if (file-exists-p dir)
(let ((files (directory-files dir t "." t))
(dir-ignore-regexp (concat "\\(?:"
"\\."
"\\|\\.\\."
"\\)$"))
result)
(dolist (file files)
(cond
((file-directory-p file)
(when (not (string-match dir-ignore-regexp file))
(setq result (append (org-roam--find-files file) result))))
((and (file-readable-p file)
(string= (file-name-extension file) "org"))
(setq result (cons (file-truename file) result)))))
result)))
(defun org-roam--find-all-files () (defun org-roam--find-all-files ()
"Return all org-roam files." "Return all org-roam files."
@ -177,26 +180,22 @@ If `ABSOLUTE', return the absolute file-path. Else, return the relative file-pat
(file-relative-name absolute-file-path (file-relative-name absolute-file-path
(file-truename org-roam-directory))))) (file-truename org-roam-directory)))))
(defun org-roam--get-id (file-path) (defun org-roam--get-title-or-slug (file-path)
"Convert `FILE-PATH' to the org-roam id." "Convert `FILE-PATH' to the file title, if it exists. Else, return the path."
(file-name-sans-extension (or (org-roam--get-title-from-cache file-path)
(file-relative-name (-> file-path
(file-truename file-path) (file-relative-name (file-truename org-roam-directory))
(file-truename org-roam-directory)))) (file-name-sans-extension))))
(defun org-roam--get-title-or-id (file-path) (defun org-roam--title-to-slug (title)
"Convert `FILE-PATH' to the file title, if it exists. Else, return the id." "Convert TITLE to a filename-suitable slug."
(or (org-roam--get-title file-path)
(org-roam--get-id file-path)))
(defun org-roam--title-to-id (title)
"Convert TITLE to id."
(let* ((s (s-downcase title)) (let* ((s (s-downcase title))
(s (replace-regexp-in-string "[^a-zA-Z0-9_ ]" "" s)) (s (replace-regexp-in-string "[^a-zA-Z0-9_ ]" "" s))
(s (s-split " " s)) (s (s-split " " s))
(s (s-join "_" s))) (s (s-join "_" s)))
s)) s))
;;; Creating org-roam files ;;; Creating org-roam files
(defun org-roam--populate-title (file &optional title) (defun org-roam--populate-title (file &optional title)
"Populate title line for FILE using TITLE, if provided. "Populate title line for FILE using TITLE, if provided.
@ -218,7 +217,7 @@ If not provided, derive the title from the file name."
(defun org-roam--make-file (file-path &optional title) (defun org-roam--make-file (file-path &optional title)
"Create an org-roam file at FILE-PATH, optionally setting the TITLE attribute." "Create an org-roam file at FILE-PATH, optionally setting the TITLE attribute."
(if (file-exists-p file-path) (if (file-exists-p file-path)
(error (format "Aborting, file already exists at " file-path)) (error (format "Aborting, file already exists at %s" file-path))
(if org-roam-autopopulate-title (if org-roam-autopopulate-title
(org-roam--populate-title file-path title) (org-roam--populate-title file-path title)
(make-empty-file file-path)))) (make-empty-file file-path))))
@ -232,9 +231,20 @@ If not provided, derive the title from the file name."
(org-roam--make-file file-path)) (org-roam--make-file file-path))
(find-file file-path))) (find-file file-path)))
(defun org-roam--get-new-id () (defun org-roam--get-new-id (&optional title)
"Return a new ID, generated from the current time." "Return a new ID, generated from the current time.
(format-time-string org-roam-timestamp-format (current-time)))
Optionally pass it the title, for a smart file name."
(if org-roam-use-timestamp-as-filename
(format-time-string org-roam-file-format (current-time))
(let* ((slug (read-string "Enter ID (without extension): "
(if title
(org-roam--title-to-slug title)
"")))
(file-path (org-roam--get-file-path slug t)))
(if (file-exists-p file-path)
(user-error "There's already a file at %s")
slug))))
(defun org-roam-new-file () (defun org-roam-new-file ()
"Quickly create a new file, using the current timestamp." "Quickly create a new file, using the current timestamp."
@ -243,59 +253,40 @@ If not provided, derive the title from the file name."
;;; Inserting org-roam links ;;; Inserting org-roam links
(defun org-roam-insert () (defun org-roam-insert ()
"Insert an org-roam link." "Find an org-roam file, and insert a relative org link to it at point."
(interactive) (interactive)
(pcase org-roam-link-representation
('id (org-roam--insert-id))
('title (org-roam--insert-title))))
(defun org-roam--insert-title ()
"Find `ID', and insert a relative org link to it at point."
(let* ((completions (mapcar (lambda (file) (let* ((completions (mapcar (lambda (file)
(list (org-roam--get-title-or-id file) (list (org-roam--get-title-or-slug file)
(org-roam--get-id file))) file))
(org-roam--find-all-files))) (org-roam--find-all-files)))
(title (completing-read "File: " completions)) (title (completing-read "File: " completions))
(id (cadr (assoc title completions)))) (absolute-file-path (or (cadr (assoc title completions))
(when (not id) (org-roam--get-file-path (org-roam--get-new-id title) t)))
(if org-roam-timestamped-files (current-file-path (-> (current-buffer)
(setq id (org-roam--get-new-id))) (buffer-file-name)
(read-string "Enter new file id: " (org-roam--title-to-id title))) (file-truename)
(let ((file-path (org-roam--get-file-path id))) (file-name-directory))))
(unless (file-exists-p file-path) (unless (file-exists-p absolute-file-path)
(org-roam--make-file file-path title)) (org-roam--make-file absolute-file-path title))
(insert (format "[[%s][%s]]"
(concat "file:" file-path)
(format org-roam-link-title-format title))))))
(defun org-roam--insert-id ()
"Find `ID', and insert a relative org link to it at point."
(let* ((id (completing-read "File: " (mapcar #'org-roam--get-id (org-roam--find-all-files))))
(file-path (org-roam--get-file-path id)))
(unless (file-exists-p file-path)
(org-roam--make-file file-path))
(insert (format "[[%s][%s]]" (insert (format "[[%s][%s]]"
(concat "file:" file-path) (concat "file:" (file-relative-name absolute-file-path
(format org-roam-link-id-format id))))) current-file-path))
(format org-roam-link-title-format title)))))
;;; Finding org-roam files ;;; Finding org-roam files
(defun org-roam-find-file () (defun org-roam-find-file ()
"Find and open an org-roam file." "Find and open an org-roam file."
(interactive) (interactive)
(let* ((completions (mapcar (lambda (file) (let* ((completions (mapcar (lambda (file)
(list (org-roam--get-title-or-id file) file)) (list (org-roam--get-title-or-slug file) file))
(org-roam--find-all-files))) (org-roam--find-all-files)))
(title-or-id (completing-read "File: " completions)) (title-or-slug (completing-read "File: " completions))
(file-path (cadr (assoc title-or-id completions)))) (absolute-file-path (or (cadr (assoc title-or-slug completions))
(unless file-path (org-roam--get-file-path
(let ((id (if org-roam-timestamped-files (org-roam--get-new-id title-or-slug) t))))
(org-roam--get-new-id) (unless (file-exists-p absolute-file-path)
(read-string "Enter new file id: " (org-roam--make-file absolute-file-path title-or-slug))
(org-roam--title-to-id title-or-id))))) (find-file absolute-file-path)))
(setq file-path (org-roam--get-file-path id t))))
(unless (file-exists-p file-path)
(org-roam--make-file file-path title-or-id))
(find-file file-path)))
;;; Building the org-roam cache (asynchronously) ;;; Building the org-roam cache (asynchronously)
(defun org-roam--build-cache-async () (defun org-roam--build-cache-async ()
@ -303,101 +294,30 @@ If not provided, derive the title from the file name."
(interactive) (interactive)
(async-start (async-start
`(lambda () `(lambda ()
(require 'org) (setq load-path ',load-path)
(require 'org-element) (package-initialize)
(require 'subr-x) ; temp-fix (require 'org-roam-utils)
(require 'cl-lib)
,(async-inject-variables "org-roam-directory") ,(async-inject-variables "org-roam-directory")
(let ((backward-links (make-hash-table :test #'equal)) (let ((backward-links (make-hash-table :test #'equal))
(forward-links (make-hash-table :test #'equal)) (forward-links (make-hash-table :test #'equal))
(file-titles (make-hash-table :test #'equal))) (file-titles (make-hash-table :test #'equal)))
(cl-labels ((org-roam--find-files (let* ((org-roam-files (org-roam--find-files org-roam-directory))
(dir) (file-items (mapcar (lambda (file)
(if (file-exists-p dir) (with-temp-buffer
(let ((files (directory-files dir t "." t)) (insert-file-contents file)
(dir-ignore-regexp (concat "\\(?:" (org-roam--parse-content file))) org-roam-files)))
"\\." (dolist (items file-items)
"\\|\\.\\." (dolist (item items)
"\\)$")) (org-roam--insert-item
result) item
(dolist (file files) :forward forward-links
(cond :backward backward-links)))
((file-directory-p file) (mapcar (lambda (file)
(when (not (string-match dir-ignore-regexp file)) (with-temp-buffer
(setq result (append (org-roam--find-files file) result)))) (insert-file-contents file)
((and (file-readable-p file) (when-let ((title (org-roam--extract-title)))
(string= (file-name-extension file) "org")) (puthash file title file-titles))))
(setq result (cons (file-truename file) result))))) org-roam-files))
result)))
(org-roam--parse-content
(file)
(with-temp-buffer
(insert-file-contents file)
(with-current-buffer (current-buffer)
(org-element-map (org-element-parse-buffer) 'link
(lambda (link)
(let ((type (org-element-property :type link))
(path (org-element-property :path link))
(start (org-element-property :begin link)))
(when (and (string= type "file")
(string= (file-name-extension path) "org"))
(goto-char start)
(let* ((element (org-element-at-point))
(content (or (org-element-property :raw-value element)
(buffer-substring
(or (org-element-property :content-begin element)
(org-element-property :begin element))
(or (org-element-property :content-end element)
(org-element-property :end element))))))
(list :from file
:to (file-truename (expand-file-name path org-roam-directory))
:content (string-trim content))))))))))
(org-roam--process-items
(items)
(mapcar
(lambda (item)
(pcase-let ((`(:from ,p-from :to ,p-to :content ,content) item))
;; Build forward-links
(let ((links (gethash p-from forward-links)))
(if links
(puthash p-from
(if (member p-to links)
links
(cons p-to links)) forward-links)
(puthash p-from (list p-to) forward-links)))
;; Build backward-links
(let ((contents-hash (gethash p-to backward-links)))
(if contents-hash
(if-let ((contents-list (gethash p-from contents-hash)))
(let ((updated (cons content contents-list)))
(puthash p-from updated contents-hash)
(puthash p-to contents-hash backward-links))
(progn
(puthash p-from (list content) contents-hash)
(puthash p-to contents-hash backward-links)))
(let ((contents-hash (make-hash-table :test #'equal)))
(puthash p-from (list content) contents-hash)
(puthash p-to contents-hash backward-links))))))
items))
(org-roam--extract-title
(buffer)
(with-current-buffer buffer
(org-element-map
(org-element-parse-buffer)
'keyword
(lambda (kw)
(when (string= (org-element-property :key kw) "TITLE")
(org-element-property :value kw)))
:first-match t))))
(let ((org-roam-files (org-roam--find-files org-roam-directory)))
(mapcar #'org-roam--process-items
(mapcar #'org-roam--parse-content org-roam-files))
(mapcar (lambda (file)
(with-temp-buffer
(insert-file-contents file)
(when-let ((title (org-roam--extract-title (current-buffer))))
(puthash file title file-titles))))
org-roam-files)))
(list (list
:forward forward-links :forward forward-links
:backward backward-links :backward backward-links
@ -409,92 +329,43 @@ If not provided, derive the title from the file name."
(setq org-roam-cache-initialized t) (setq org-roam-cache-initialized t)
(message "Org-roam cache built!")))) (message "Org-roam cache built!"))))
(defun org-roam--insert-item (item) (defun org-roam--clear-cache ()
"Insert `ITEM' into org-roam caches. "Remove any related links to the file.
`ITEM' is of the form: (:from from-path :to to-path :content preview-content)
Before calling this function, `org-roam-cache' should be already populated."
(pcase-let ((`(:from ,p-from :to ,p-to :content ,content) item))
;; Build forward-links
(let ((links (gethash p-from org-roam-forward-links-cache)))
(if links
(puthash p-from
(if (member p-to links)
links
(cons p-to links)) org-roam-forward-links-cache)
(puthash p-from (list p-to) org-roam-forward-links-cache)))
;; Build backward-links
(let ((contents-hash (gethash p-to org-roam-backward-links-cache)))
(if contents-hash
(if-let ((contents-list (gethash p-from contents-hash)))
(let ((updated (cons content contents-list)))
(puthash p-from updated contents-hash)
(puthash p-to contents-hash org-roam-backward-links-cache))
(progn
(puthash p-from (list content) contents-hash)
(puthash p-to contents-hash org-roam-backward-links-cache)))
(let ((contents-hash (make-hash-table :test #'equal)))
(puthash p-from (list content) contents-hash)
(puthash p-to contents-hash org-roam-backward-links-cache))))))
(defun org-roam--parse-content ()
"Parse the current buffer, and return a list of items for processing."
(with-current-buffer (current-buffer)
(org-element-map (org-element-parse-buffer) 'link
(lambda (link)
(let ((type (org-element-property :type link))
(path (org-element-property :path link))
(start (org-element-property :begin link)))
(when (and (string= type "file")
(string= (file-name-extension path) "org"))
(goto-char start)
(let* ((element (org-element-at-point))
(content (or (org-element-property :raw-value element)
(buffer-substring
(or (org-element-property :content-begin element)
(org-element-property :begin element))
(or (org-element-property :content-end element)
(org-element-property :end element))))))
(list :from (file-truename (buffer-file-name (current-buffer)))
:to (file-truename (expand-file-name path org-roam-directory))
:content (string-trim content)))))))))
(defun org-roam--clear-cache-for-buffer (buffer)
"Remove any related links to the file for `BUFFER'.
This is equivalent to removing the node from the graph." This is equivalent to removing the node from the graph."
(with-current-buffer (current-buffer) (let ((file (file-truename (buffer-file-name (current-buffer)))))
(let ((file (file-truename (buffer-file-name buffer)))) ;; Step 1: Remove all existing links for file
;; Step 1: Remove all existing links for file (when-let ((forward-links (gethash file org-roam-forward-links-cache)))
(when-let ((forward-links (gethash file org-roam-forward-links-cache))) ;; Delete backlinks to file
;; Delete backlinks to file (dolist (link forward-links)
(dolist (link forward-links) (when-let ((backward-links (gethash link org-roam-backward-links-cache)))
(when-let ((backward-links (gethash link org-roam-backward-links-cache))) (remhash file backward-links)
(remhash file backward-links) (puthash link backward-links org-roam-backward-links-cache)))
(puthash link backward-links org-roam-backward-links-cache))) ;; Clean out forward links
;; Clean out forward links (remhash file org-roam-forward-links-cache))
(remhash file org-roam-forward-links-cache)) ;; Step 2: Remove from the title cache
;; Step 2: Remove from the title cache (remhash file org-roam-titles-cache)))
(remhash file org-roam-titles-cache))))
(defun org-roam--update-cache-title (buffer) (defun org-roam--update-cache-title ()
"Inserts the `TITLE' of file in buffer into the cache." "Insert the title of the current buffer into the cache."
(when-let ((title (org-roam--extract-title buffer))) (when-let ((title (org-roam--extract-title)))
(puthash (file-truename (buffer-file-name buffer)) (puthash (file-truename (buffer-file-name (current-buffer)))
title title
org-roam-titles-cache))) org-roam-titles-cache)))
(defun org-roam--update-cache () (defun org-roam--update-cache ()
"Update org-roam caches for the current buffer file." "Update org-roam caches for the current buffer file."
(save-excursion (save-excursion
(org-roam--clear-cache-for-buffer (current-buffer)) (org-roam--clear-cache)
;; Insert into title cache ;; Insert into title cache
(org-roam--update-cache-title (current-buffer)) (org-roam--update-cache-title)
;; Insert new items ;; Insert new items
(let ((items (org-roam--parse-content))) (let ((items (org-roam--parse-content)))
(dolist (item items) (dolist (item items)
(org-roam--insert-item item))) (org-roam--insert-item
item
:forward org-roam-forward-links-cache
:backward org-roam-backward-links-cache)))
;; Rerender buffer ;; Rerender buffer
(org-roam--maybe-update-buffer :redisplay t))) (org-roam--maybe-update-buffer :redisplay t)))
@ -516,27 +387,10 @@ This is equivalent to removing the node from the graph."
(org-roam--new-file-named (format-time-string "%Y-%m-%d" time)))) (org-roam--new-file-named (format-time-string "%Y-%m-%d" time))))
;;; Org-roam buffer updates ;;; Org-roam buffer updates
(defun org-roam--extract-title (buffer)
"Extract the title from `BUFFER'."
(with-current-buffer buffer
(org-element-map
(org-element-parse-buffer)
'keyword
(lambda (kw)
(when (string= (org-element-property :key kw) "TITLE")
(org-element-property :value kw)))
:first-match t)))
(defun org-roam--extract-file-title (file)
"Extract the title from `FILE'."
(with-temp-buffer
(insert-file-contents file)
(org-roam--extract-title (current-buffer))))
(defun org-roam-update (file-path) (defun org-roam-update (file-path)
"Show the backlinks for given org file for file at `FILE-PATH'." "Show the backlinks for given org file for file at `FILE-PATH'."
(org-roam--ensure-cache-built) (org-roam--ensure-cache-built)
(let ((buffer-title (org-roam--get-title-or-id file-path))) (let ((buffer-title (org-roam--get-title-or-slug file-path)))
(with-current-buffer org-roam-buffer (with-current-buffer org-roam-buffer
(let ((inhibit-read-only t)) (let ((inhibit-read-only t))
(erase-buffer) (erase-buffer)
@ -553,7 +407,7 @@ This is equivalent to removing the node from the graph."
(maphash (lambda (file-from contents) (maphash (lambda (file-from contents)
(insert (format "** [[file:%s][%s]]\n" (insert (format "** [[file:%s][%s]]\n"
file-from file-from
(org-roam--get-title-or-id file-from))) (org-roam--get-title-or-slug file-from)))
(dolist (content contents) (dolist (content contents)
(insert (concat (propertize (s-trim (s-replace "\n" " " content)) (insert (concat (propertize (s-trim (s-replace "\n" " " content))
'font-lock-face 'org-block) 'font-lock-face 'org-block)
@ -586,11 +440,11 @@ Valid states are 'visible, 'exists and 'none."
(enlarge-window-horizontally (- w (window-width)))))))) (enlarge-window-horizontally (- w (window-width))))))))
(defun org-roam--setup-buffer () (defun org-roam--setup-buffer ()
"Setup the `org-roam' buffer at the `org-roam-position'." "Setup the `org-roam' buffer at the `org-roam-buffer-position'."
(let ((window (get-buffer-window))) (let ((window (get-buffer-window)))
(-> (get-buffer-create org-roam-buffer) (-> (get-buffer-create org-roam-buffer)
(display-buffer-in-side-window (display-buffer-in-side-window
`((side . ,org-roam-position))) `((side . ,org-roam-buffer-position)))
(select-window)) (select-window))
(org-roam--set-width (org-roam--set-width
(round (* (frame-width) (round (* (frame-width)
@ -654,19 +508,17 @@ This needs to be quick/infrequent, because this is run at
(org-roam--ensure-cache-built) (org-roam--ensure-cache-built)
(with-temp-buffer (with-temp-buffer
(insert "digraph {\n") (insert "digraph {\n")
(mapcar (lambda (file) (dolist (file (org-roam--find-all-files))
(insert (insert
(format " \"%s\" [URL=\"roam://%s\"];\n" (format " \"%s\" [URL=\"roam://%s\"];\n"
(org-roam--get-id file) (org-roam--get-title-or-slug file)
file))) file)))
(org-roam--find-all-files))
(maphash (maphash
(lambda (from-link to-links) (lambda (from-link to-links)
(dolist (to-link to-links) (dolist (to-link to-links)
(insert (format " \"%s\" -> \"%s\";\n" (insert (format " \"%s\" -> \"%s\";\n"
(org-roam--get-id from-link) (org-roam--get-title-or-slug from-link)
(org-roam--get-id to-link)))) (org-roam--get-title-or-slug to-link)))))
)
org-roam-forward-links-cache) org-roam-forward-links-cache)
(insert "}") (insert "}")
(buffer-string))) (buffer-string)))
@ -687,7 +539,6 @@ This needs to be quick/infrequent, because this is run at
(call-process org-roam-graphviz-executable nil 0 nil temp-dot "-Tsvg" "-o" temp-graph) (call-process org-roam-graphviz-executable nil 0 nil temp-dot "-Tsvg" "-o" temp-graph)
(call-process org-roam-graph-viewer nil 0 nil temp-graph))) (call-process org-roam-graph-viewer nil 0 nil temp-graph)))
(provide 'org-roam) (provide 'org-roam)
;;; org-roam.el ends here ;;; org-roam.el ends here

View File

@ -5,6 +5,5 @@ pkgs.mkShell {
name = "docs"; name = "docs";
buildInput = with pkgs; [ buildInput = with pkgs; [
mkdocs mkdocs
python3Packages.alabaster
]; ];
} }

35
tests/test-org-roam.el Normal file
View File

@ -0,0 +1,35 @@
;;; test-org-roam.el --- Tests for org-roam -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Package-Requires: ((buttercup))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
;;;; Requirements
(require 'buttercup)
(require 'org-roam)
;;; Tests
(describe "Org-roam cache"
(it "Mock Test"
(expect t :to-be t)))