213 | Comments and contributions to Zetteldeft are more than welcome.
214 |
215 |
216 |
217 | Reach out via Mastodon, file an issue over on Github, or submit a pull request to get your code included.
218 |
219 |
220 |
221 | If you want to contribute code, please keep in mind that Zetteldeft is maintained in the zetteldeft.org file.
222 | The zetteldeft.el is then created from the zetteldeft.org – or, in Org-mode jargon, "tangled".
223 |
224 |
225 |
226 | Please respect this order of things if you can, by making changes to the .org file (rather than to the .el file directly).
227 | Alter the code in the source blocks, or create new source blocks like so:
228 |
221 | UPDATE 2023: Zetteldeft is no longer in active development, as its main author is moving away from a note-taking system based on Deft towards one based on Denote.
222 | The Emacs package and this repository will remain available, but might no longer receive updates.
223 |
224 |
225 |
226 |
227 | The zetteldeft.org file in this repository contains documented code for a set of functions for emacs, which aims to extend the deft package and turn it into a (very very) basic Zettelkasten note-taking system.
228 |
229 |
230 |
231 | The best way to get to know Zetteldeft, is from within Zetteldeft.
232 | Get started by cloning the zd-tutorial repository, available at https://github.com/EFLS/zd-tutorial.
233 | Bootstrap use-package installation instructions included.
234 |
235 |
236 |
237 | If you’d rather start with an introduction of key concepts and basic functions, check out an introduction here: efls.github.io/zetteldeft.
238 |
239 |
240 |
241 | Documentation, literate code and further technical details can be found in the .org file of this repository, or can be read in a more convenient format over at efls.github.io/zetteldeft/zetteldeft.html.
242 |
243 |
244 |
245 | zetteldeft is available on MELPA.
246 | If you use use-package, installing is as easy as
247 |
258 | Please note that this was originally written for personal use and that I’m far from an emacs lisp expert.
259 | That said, contributions and feedback are more than welcome.
260 |
274 |
275 |
--------------------------------------------------------------------------------
/introduction.org:
--------------------------------------------------------------------------------
1 | #+title: Introducing Zetteldeft
2 | #+subtitle: A Zettelkasten for Emacs
3 | #+author: EFLS
4 | #+date: 2018-2020
5 | #+OPTIONS: num:nil
6 | #+EXPORT_FILE_NAME: ./docs/index
7 |
8 | *NOTE* (Mar 2023): Zetteldeft is no longer under active development. The the code is functional and the package remains available, but I am currently exploring note-taking based on Denote rather than on Deft. Feel free to reach out (via Mastodon or email) if you want to exchange ideas.
9 |
10 | * Introduction :ignore:
11 |
12 | Zetteldeft is an extension of the Deft package for Emacs.
13 | Building on Deft's search functionality, Zetteldeft provides a way to create and manage links between short notes.
14 |
15 | In the spirit of Zettelkasten, Zetteldeft maintains a flat hierarchy at the level of files, but invites authors to create structure and meaning via links and tags.
16 | The result is a free-form note taking system of linked notes and ideas.
17 | Or, as the original Zettelkasten creator Niklas Luhmann would call it: a partner in communication.[fn:lhcs]
18 |
19 | The text below introduces Zetteldeft, but even easier is the *tutorial*: get to know Zetteldeft from within Zetteldeft with =zd-tutorial= on [[https://github.com/EFLS/zd-tutorial][Github]].
20 | Installation instructions included.
21 | Clone it and get started!
22 |
23 | Just want *source*?
24 | Take a look at this [[file:zetteldeft.org]["literate code"]], which contains both source and documentation,
25 | or simply refer to the [[https://github.com/efls/zetteldeft][Github repository]].
26 |
27 | I've been maintaining this code for personal use since 2018,[fn:innp] and have been sharing it on Github for a while now.
28 | It's high time for a proper introduction of this package.
29 |
30 | One last note: I hope to make this a living document, so expect changes and additions.
31 | And feel free to leave comments, for example via [[https://github.com/efls/zetteldeft][Github]], [[https://twitter.com/EFLS0][Twitter]], or (preferably) [[https://mastodon.social/@EFLS][Mastodon]].
32 |
33 | #+TOC: headlines 2
34 |
35 |
36 | [fn:lhcs] Luhmann was first and foremost a social theorist who developed a unique systems theory.
37 | In one of his writings, he compares his Zettelkasten to a "communicative system".
38 | Available here in English translation:
39 | http://luhmann.surge.sh/communicating-with-slip-boxes.
40 |
41 | [fn:innp] Please beware that I'm no programmer.
42 | Zetteldeft is written in Emacs Lisp, the only language I can claim to have ever programmed in outside of school, but can't claim to know well.
43 | I use Emacs mainly for writing, both personal and academic, and have fallen in love with its extensibility.
44 | My overall experience resonates strongly with this story [[https://www.gnu.org/gnu/rms-lisp.en.html][shared by Richard Stallman]]:
45 |
46 | #+begin_quote
47 | Multics Emacs proved to be a great success -- programming new editing commands was so convenient that even the secretaries in his [Bernie Greenberg] office started learning how to use it.
48 | They used a manual someone had written which showed how to extend Emacs, but didn't say it was a programming.
49 | So the secretaries, who believed they couldn't do programming, weren't scared off.
50 | They read the manual, discovered they could do useful things and they learned to program.
51 | #+end_quote
52 |
53 | * A Zettelkasten in Deft
54 |
55 | As the name suggests, Zetteldeft is inspired by the now famous Zettelkasten system first implemented by the German sociologist Niklas Luhmann long before Personal Computers made their appearance.
56 |
57 | In our digital times, however, a Zettelkasten note-taking system looks a lot like (though is not the same as) a personal wiki for your notes.
58 | This introduction won't attempt to explain what a Zettelkasten is or what you might use it for.
59 | To that end, the internet provides various resources, such as the great [[https://www.zettelkasten.de][zettelkasten.de]] and their community.[fn:insp]
60 |
61 | In this text I want to briefly introduce what Zetteldeft is and mention its core features, so that you can check it out if you'd like.
62 |
63 |
64 | [fn:insp] Zetteldeft is inspired by /The Archive/, created by the guys at [[https://www.zettelkasten.de][zettelkasten.de]].
65 |
66 | * Key concepts
67 |
68 | Following the Zettelkasten philosophy, each note in Zetteldeft should either: (1) contain a core idea, (2) connect different ideas (and link to notes), (3) or contain a structured set of links to other notes.
69 |
70 | How you do that is completely up to you, but links between notes are key.
71 | And, it should be emphasized, links require /work/.
72 | Work done personally by /you/, the author, so that your notes might breathe life.[fn:lhtw]
73 |
74 | In Zetteldeft, each note has a unique *identifier* or ID, based on the time and date of its creation, included at the beginning of its filename.
75 | This, fore example, is the name of a file in my Zettelkasten: =2018-07-07-2356 The zetteldeft idea.org=.
76 |
77 | Notes contain *links* to other notes.
78 | These links are indicated by prepending the =§= character to an ID:
79 | =§2018-07-07-2356= links to the file mentioned above.
80 | No other formatting is needed: a plain text =§= and the ID suffice to create a link.
81 | And don't worry, you won't have to type the =§= manually (or the ID for that matter).
82 | And yes, you can change this link indicator or even disable it (and include a link suffix, if you so require).
83 |
84 | #+CAPTION: A note with links. The note shown is part of [[https://github.com/efls/zd-tutorial][zd-tutorial]]
85 | [[./img/screenshot.png]]
86 |
87 | The ID combined with Deft's full text search allow to
88 | 1. retrieve a note via its identifier (by searching file titles),
89 | 2. find out which notes link to a given note (via a full text search).
90 |
91 | That's pretty much all there is to it, for the basics at least.
92 | All of this is done in plain text.
93 | Org-mode by default, but it really is formatting agnostic.
94 |
95 | A way to further organize your notes, is to use tags, indicated with a =#= (or another string, it's all customizable).
96 | In my Zettelkasten, for example, I use =#zetteldeft= for all notes related to...
97 | Well, you can guess.
98 |
99 |
100 | [fn:lhtw] Or, in the words of Luhmann himself:
101 |
102 | #+begin_quote
103 | It is impossible to think without writing; at least it is impossible in any sophisticated or networked (/anschlußfähig/) fashion.
104 | #+end_quote
105 |
106 | Quote from [fn:lhcs].
107 |
108 | * Basic functions
109 | ** A quick introduction
110 |
111 | Let's look at some basic functions you need to get started.
112 |
113 | *Create* a note with =C-c d n= (or =zetteldeft-new-file=).
114 | Enter a title and you're set.
115 | Zetteldeft will generate a note ID and include it in its filename.
116 |
117 | To *insert a link* to a note, you can use
118 | - =C-c d i= (or =zetteldeft-find-file-id-insert=),
119 | - or =C-c d I= if you want to include the title of your destination (which calls =zetteldeft-find-file-full-title-insert=).
120 |
121 | Hit =C-c d f= (or =zetteldeft-follow-link=) to *follow a link* to a note.
122 | All link indicators, those =§= symbols, will be replaced different characters (thanks to the Avy package).
123 | Pick one to follow a link.
124 | If only one link is available, or if point is in a link, it will be selected automatically.
125 |
126 | Use =C-c d F= to open a link in a separate window of choice.
127 | This is especially useful when browsing your own notes, looking for new ideas and connections.
128 |
129 | To quickly *open* one of the notes in your Zettelkasten, use =C-c d o= (or =zetteldeft-find-file=) and search the titles.
130 | Or simply hit =C-c d D= to open Deft and start a full text search.
131 |
132 | To quickly find out which notes *refer* to the current note, use =C-c d c= (which is =zetteldeft-search-current-id=).
133 |
134 | To search a *tag*, hit =C-c d t= and select a highlighted tag, similar to how you follow a link.
135 | Easily insert tags with =C-c d #= and select one from the list (or enter a new one).
136 | To generate a *list* of tags currently in your Zettelkasten, use =C-c d T=.
137 | Quickly launch a search for a tag of choice with =C-c d /=.
138 |
139 | There are many more functions, but these will be enough to get you started.
140 |
141 | ** An overview of keybindings
142 |
143 | As Zetteldeft does not launch a minor mode, no default keys are bound.
144 | You can set keys mentioned in this text by calling =zetteldeft-set-classic-keybindings=.
145 |
146 | For different setups with similar bindings, check the [[file:zetteldeft.org][literate source]].
147 | Personally, I prefer vim style bindings behind a leader key, set up with general,
148 | [[file:zetteldeft.org::#kb-general][like so]].
149 |
150 | #+CAPTION: Classic keybindings
151 | | Key | Function |
152 | |---------+----------------------------------------|
153 | | =C-c d d= | =deft= |
154 | | =C-c d D= | =zetteldeft-deft-new-search= |
155 | | =C-c d R= | =deft-refresh= |
156 | | =C-c d s= | =zetteldeft-search-at-point= |
157 | | =C-c d c= | =zetteldeft-search-current-id= |
158 | | =C-c d f= | =zetteldeft-follow-link= |
159 | | =C-c d F= | =zetteldeft-avy-file-search-ace-window= |
160 | | =C-c d l= | =zetteldeft-avy-link-search= |
161 | | =C-c d t= | =zetteldeft-avy-tag-search= |
162 | | =C-c d T= | =zetteldeft-tag-buffer= |
163 | | =C-c d #= | =zetteldeft-tag-insert= |
164 | | =C-c d i= | =zetteldeft-find-file-id-insert= |
165 | | =C-c d I= | =zetteldeft-find-file-full-title-insert= |
166 | | =C-c d o= | =zetteldeft-find-file= |
167 | | =C-c d n= | =zetteldeft-new-file= |
168 | | =C-c d N= | =zetteldeft-new-file-and-link= |
169 | | =C-c d r= | =zetteldeft-file-rename= |
170 | | =C-c d x= | =zetteldeft-count-words= |
171 |
172 | * Sneak peek at more advanced features
173 |
174 | As emphasized above, any Zettelkasten system relies on its author for links between notes.
175 | There are, however, some features in Zetteldeft that help you with this.
176 | For this introduction, I won't go into detail, but more information is found in the full [[file:zetteldeft.org][Zetteldeft.org]].
177 |
178 | There is =zetteldeft-insert-list-links= to automatically generate a list of links to notes containing a provided search term.
179 | Or use =zetteldeft-insert-list-links-missing= if you only want to include those notes that /don't/ yet appear in the current note.
180 |
181 | Zetteldeft is not limited to Org-mode, but integrates well with source code blocks to, for example, automate generating the lists mentioned above.
182 |
183 | You can export your notes to HTML to read them outside of of Emacs, as explained [[file:zetteldeft.org::#export-setup][in the documentation]].
184 |
185 | With the help of =graphviz=, we can even draw graphical representations of links between notes.
186 | Check out =zetteldeft-org-graph-search= and =zetteldeft-org-graph-note= [[file:zetteldeft.org::#visuals][in the documentation]].
187 | It generates something like this:
188 |
189 | #+CAPTION: Example of a graph generated with graphviz.
190 | [[./img/zetteldeft-graph.jpg]]
191 |
192 | This feature is fairly crude but easily hackable.
193 | Ideas on how to extend or replace it are more than welcome.
194 |
195 | * Installing & getting started
196 | ** Installing Zetteldeft
197 | *** Intro :ignore:
198 |
199 | This section will take you through an example Zetteldeft setup and installation.
200 | It assumes basic Emacs knowledge, so I'm going to guess you understand that the code below should go in your =init.el= (or equivalent).
201 |
202 | It also assumes that you have =use-package= installed, that you use [[http://melpa.org/#/][MELPA]] to install Emacs packages, and that you'll write notes in =org-mode=.
203 |
204 | Prefer Markdown?
205 | That's easy enough to change in the example below.
206 |
207 | For different methods of installation, please refer to the [[file:zetteldeft.org::#install][documentation]].
208 |
209 | *** Deft
210 |
211 | Zetteldeft relies on Deft.
212 | Let's start with a basic setup.
213 |
214 | #+begin_src emacs-lisp
215 | (use-package deft
216 | :ensure t
217 | :custom
218 | (deft-extensions '("org" "md" "txt"))
219 | (deft-directory "~/notes")
220 | (deft-use-filename-as-title t))
221 | #+end_src
222 |
223 | Note that none of these settings are strictly required, apart from changing the default =deft-directory=.
224 |
225 | The =deft-use-filename-as-title= ensures that we can see the note IDs from the deft buffer, but this can be disabled if you prefer.
226 |
227 | *** Zetteldeft
228 |
229 | Installing Zetteldeft can be done in a similar fashion.
230 |
231 | Let's start bare bones:
232 |
233 | #+BEGIN_SRC emacs-lisp
234 | (use-package zetteldeft
235 | :ensure t
236 | :after deft
237 | :config (zetteldeft-set-classic-keybindings))
238 | #+END_SRC
239 |
240 | That should be enough to get you started, really.
241 |
242 | *** Installation with Spacemacs
243 |
244 | Installation with Spacemacs is easy.
245 | Locate =dotspacemacs-configuration-layers= in your =.spacemacs= and add the code like so.
246 |
247 | #+BEGIN_SRC emacs-lisp
248 | (setq-default dotspacemacs-configuration-layers
249 | '((deft :variables deft-zetteldeft t)))
250 | #+END_SRC
251 |
252 | This should take care of keybindings as well.
253 | Take a look in [[file:zetteldeft.org][the documentation]] to see how keys are bound.
254 |
255 | ** Customization
256 |
257 | Some pointers for further customization:
258 | - alter =zetteldeft-link-indicator= to change the prefix to links,
259 | or set it to an empty string to remove it altogether,
260 | - change =zetteldeft-title-prefix= and =zetteldeft-title-suffix= to change how titles are appear,
261 | - you can modify =zetteldeft-id-format= to change how IDs are generated, but make sure to change =zetteldeft-id-regex= accordingly so that the new IDs can be detected.
262 |
263 | There's more to Zetteldeft, and to its customization, but that's all for this introduction.
264 |
265 | ** Using Zetteldeft with Markdown notes
266 | :PROPERTIES:
267 | :CUSTOM_ID: markdown
268 | :END:
269 |
270 | While Zetteldeft works nicely with Org-mode, you can call its functions from any mode.
271 | Many people keep Zettelkasten in Markdown, so let's explore how such a setup can be achieved.
272 |
273 | First, make sure =deft-extensions= is set correctly.
274 | If =md= is the first element on the list, new notes will be Markdown notes.
275 | Zetteldeft uses Deft to create new notes, so using =zetteldeft-new-file= should now create Markdown files.
276 |
277 | #+begin_src emacs-lisp
278 | (setq deft-extensions '("md" "org" "txt"))
279 | #+end_src
280 |
281 | In such Zettelkasten links are often wrapped in square brackets.
282 | This can be easily achieved by setting the =zetteldeft-link-indicator= and =zetteldeft-link-suffix=.
283 |
284 | #+begin_src emacs-lisp
285 | (setq zetteldeft-link-indicator "[["
286 | zetteldeft-link-suffix "]]")
287 | #+end_src
288 |
289 | To make sure that your Markdown notes start with correct title syntax, customize the =zetteldeft-title-prefix=.
290 |
291 | #+begin_src emacs-lisp
292 | (setq zetteldeft-title-prefix "# ")
293 | #+end_src
294 |
295 | When using =zetteldeft-insert-list-links=, you might want to change a list entry to correct Markdown syntax, like so:
296 |
297 | #+begin_src emacs-lisp
298 | (setq zetteldeft-list-prefix "* ")
299 | #+end_src
300 |
301 | To highlight links you need to set up font-lock keywords for =markdown-mode=.
302 |
303 | #+begin_src emacs-lisp
304 | (font-lock-add-keywords 'markdown-mode
305 | `((,zetteldeft-id-regex
306 | . font-lock-warning-face)))
307 | #+end_src
308 |
309 | Alternatively, if you want to highlight the brackets as well, you need to escape them like so:
310 |
311 | #+begin_src emacs-lisp
312 | (font-lock-add-keywords 'markdown-mode
313 | `((,(concat "\\[\\["
314 | zetteldeft-id-regex
315 | "\\]\\]")
316 | . font-lock-warning-face)))
317 | #+end_src
318 |
319 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Introducing Zetteldeft
8 |
9 |
10 |
194 |
195 |
196 |
197 |
214 | NOTE (Mar 2023): Zetteldeft is no longer under active development. The the code is functional and the package remains available, but I am currently exploring note-taking based on Denote rather than on Deft. Feel free to reach out (via Mastodon or email) if you want to exchange ideas.
215 |
216 |
217 |
218 | Zetteldeft is an extension of the Deft package for Emacs.
219 | Building on Deft’s search functionality, Zetteldeft provides a way to create and manage links between short notes.
220 |
221 |
222 |
223 | In the spirit of Zettelkasten, Zetteldeft maintains a flat hierarchy at the level of files, but invites authors to create structure and meaning via links and tags.
224 | The result is a free-form note taking system of linked notes and ideas.
225 | Or, as the original Zettelkasten creator Niklas Luhmann would call it: a partner in communication.1
226 |
227 |
228 |
229 | The text below introduces Zetteldeft, but even easier is the tutorial: get to know Zetteldeft from within Zetteldeft with zd-tutorial on Github.
230 | Installation instructions included.
231 | Clone it and get started!
232 |
233 |
234 |
235 | Just want source?
236 | Take a look at this "literate code", which contains both source and documentation,
237 | or simply refer to the Github repository.
238 |
239 |
240 |
241 | I’ve been maintaining this code for personal use since 2018,2 and have been sharing it on Github for a while now.
242 | It’s high time for a proper introduction of this package.
243 |
244 |
245 |
246 | One last note: I hope to make this a living document, so expect changes and additions.
247 | And feel free to leave comments, for example via Github, Twitter, or (preferably) Mastodon.
248 |
249 |
250 |
273 |
274 |
275 |
A Zettelkasten in Deft
276 |
277 |
278 | As the name suggests, Zetteldeft is inspired by the now famous Zettelkasten system first implemented by the German sociologist Niklas Luhmann long before Personal Computers made their appearance.
279 |
280 |
281 |
282 | In our digital times, however, a Zettelkasten note-taking system looks a lot like (though is not the same as) a personal wiki for your notes.
283 | This introduction won’t attempt to explain what a Zettelkasten is or what you might use it for.
284 | To that end, the internet provides various resources, such as the great zettelkasten.de and their community.3
285 |
286 |
287 |
288 | In this text I want to briefly introduce what Zetteldeft is and mention its core features, so that you can check it out if you’d like.
289 |
290 |
291 |
292 |
293 |
294 |
Key concepts
295 |
296 |
297 | Following the Zettelkasten philosophy, each note in Zetteldeft should either: (1) contain a core idea, (2) connect different ideas (and link to notes), (3) or contain a structured set of links to other notes.
298 |
299 |
300 |
301 | How you do that is completely up to you, but links between notes are key.
302 | And, it should be emphasized, links require work.
303 | Work done personally by you, the author, so that your notes might breathe life.4
304 |
305 |
306 |
307 | In Zetteldeft, each note has a unique identifier or ID, based on the time and date of its creation, included at the beginning of its filename.
308 | This, fore example, is the name of a file in my Zettelkasten: 2018-07-07-2356 The zetteldeft idea.org.
309 |
310 |
311 |
312 | Notes contain links to other notes.
313 | These links are indicated by prepending the § character to an ID:
314 | §2018-07-07-2356 links to the file mentioned above.
315 | No other formatting is needed: a plain text § and the ID suffice to create a link.
316 | And don’t worry, you won’t have to type the § manually (or the ID for that matter).
317 | And yes, you can change this link indicator or even disable it (and include a link suffix, if you so require).
318 |
319 |
320 |
321 |
322 |
323 |
324 | Figure 1: A note with links. The note shown is part of zd-tutorial
325 |
326 |
327 |
328 | The ID combined with Deft’s full text search allow to
329 |
330 |
331 |
retrieve a note via its identifier (by searching file titles),
332 |
find out which notes link to a given note (via a full text search).
333 |
334 |
335 |
336 | That’s pretty much all there is to it, for the basics at least.
337 | All of this is done in plain text.
338 | Org-mode by default, but it really is formatting agnostic.
339 |
340 |
341 |
342 | A way to further organize your notes, is to use tags, indicated with a # (or another string, it’s all customizable).
343 | In my Zettelkasten, for example, I use #zetteldeft for all notes related to…
344 | Well, you can guess.
345 |
346 |
347 |
348 |
349 |
350 |
Basic functions
351 |
352 |
353 |
354 |
A quick introduction
355 |
356 |
357 | Let’s look at some basic functions you need to get started.
358 |
359 |
360 |
361 | Create a note with C-c d n (or zetteldeft-new-file).
362 | Enter a title and you’re set.
363 | Zetteldeft will generate a note ID and include it in its filename.
364 |
365 |
366 |
367 | To insert a link to a note, you can use
368 |
369 |
370 |
C-c d i (or zetteldeft-find-file-id-insert),
371 |
or C-c d I if you want to include the title of your destination (which calls zetteldeft-find-file-full-title-insert).
372 |
373 |
374 |
375 | Hit C-c d f (or zetteldeft-follow-link) to follow a link to a note.
376 | All link indicators, those § symbols, will be replaced different characters (thanks to the Avy package).
377 | Pick one to follow a link.
378 | If only one link is available, or if point is in a link, it will be selected automatically.
379 |
380 |
381 |
382 | Use C-c d F to open a link in a separate window of choice.
383 | This is especially useful when browsing your own notes, looking for new ideas and connections.
384 |
385 |
386 |
387 | To quickly open one of the notes in your Zettelkasten, use C-c d o (or zetteldeft-find-file) and search the titles.
388 | Or simply hit C-c d D to open Deft and start a full text search.
389 |
390 |
391 |
392 | To quickly find out which notes refer to the current note, use C-c d c (which is zetteldeft-search-current-id).
393 |
394 |
395 |
396 | To search a tag, hit C-c d t and select a highlighted tag, similar to how you follow a link.
397 | Easily insert tags with C-c d # and select one from the list (or enter a new one).
398 | To generate a list of tags currently in your Zettelkasten, use C-c d T.
399 | Quickly launch a search for a tag of choice with C-c d /.
400 |
401 |
402 |
403 | There are many more functions, but these will be enough to get you started.
404 |
405 |
406 |
407 |
408 |
409 |
An overview of keybindings
410 |
411 |
412 | As Zetteldeft does not launch a minor mode, no default keys are bound.
413 | You can set keys mentioned in this text by calling zetteldeft-set-classic-keybindings.
414 |
415 |
416 |
417 | For different setups with similar bindings, check the literate source.
418 | Personally, I prefer vim style bindings behind a leader key, set up with general,
419 | like so.
420 |
421 |
422 |
423 |
Table 1: Classic keybindings
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
Key
433 |
Function
434 |
435 |
436 |
437 |
438 |
C-c d d
439 |
deft
440 |
441 |
442 |
443 |
C-c d D
444 |
zetteldeft-deft-new-search
445 |
446 |
447 |
448 |
C-c d R
449 |
deft-refresh
450 |
451 |
452 |
453 |
C-c d s
454 |
zetteldeft-search-at-point
455 |
456 |
457 |
458 |
C-c d c
459 |
zetteldeft-search-current-id
460 |
461 |
462 |
463 |
C-c d f
464 |
zetteldeft-follow-link
465 |
466 |
467 |
468 |
C-c d F
469 |
zetteldeft-avy-file-search-ace-window
470 |
471 |
472 |
473 |
C-c d l
474 |
zetteldeft-avy-link-search
475 |
476 |
477 |
478 |
C-c d t
479 |
zetteldeft-avy-tag-search
480 |
481 |
482 |
483 |
C-c d T
484 |
zetteldeft-tag-buffer
485 |
486 |
487 |
488 |
C-c d #
489 |
zetteldeft-tag-insert
490 |
491 |
492 |
493 |
C-c d i
494 |
zetteldeft-find-file-id-insert
495 |
496 |
497 |
498 |
C-c d I
499 |
zetteldeft-find-file-full-title-insert
500 |
501 |
502 |
503 |
C-c d o
504 |
zetteldeft-find-file
505 |
506 |
507 |
508 |
C-c d n
509 |
zetteldeft-new-file
510 |
511 |
512 |
513 |
C-c d N
514 |
zetteldeft-new-file-and-link
515 |
516 |
517 |
518 |
C-c d r
519 |
zetteldeft-file-rename
520 |
521 |
522 |
523 |
C-c d x
524 |
zetteldeft-count-words
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
Sneak peek at more advanced features
534 |
535 |
536 | As emphasized above, any Zettelkasten system relies on its author for links between notes.
537 | There are, however, some features in Zetteldeft that help you with this.
538 | For this introduction, I won’t go into detail, but more information is found in the full Zetteldeft.org.
539 |
540 |
541 |
542 | There is zetteldeft-insert-list-links to automatically generate a list of links to notes containing a provided search term.
543 | Or use zetteldeft-insert-list-links-missing if you only want to include those notes that don’t yet appear in the current note.
544 |
545 |
546 |
547 | Zetteldeft is not limited to Org-mode, but integrates well with source code blocks to, for example, automate generating the lists mentioned above.
548 |
549 |
550 |
551 | You can export your notes to HTML to read them outside of of Emacs, as explained in the documentation.
552 |
553 |
554 |
555 | With the help of graphviz, we can even draw graphical representations of links between notes.
556 | Check out zetteldeft-org-graph-search and zetteldeft-org-graph-notein the documentation.
557 | It generates something like this:
558 |
559 |
560 |
561 |
562 |
563 |
564 | Figure 2: Example of a graph generated with graphviz.
565 |
566 |
567 |
568 | This feature is fairly crude but easily hackable.
569 | Ideas on how to extend or replace it are more than welcome.
570 |
571 |
572 |
573 |
574 |
575 |
Installing & getting started
576 |
577 |
578 |
579 |
Installing Zetteldeft
580 |
581 |
582 | This section will take you through an example Zetteldeft setup and installation.
583 | It assumes basic Emacs knowledge, so I’m going to guess you understand that the code below should go in your init.el (or equivalent).
584 |
585 |
586 |
587 | It also assumes that you have use-package installed, that you use MELPA to install Emacs packages, and that you’ll write notes in org-mode.
588 |
589 |
590 |
591 | Prefer Markdown?
592 | That’s easy enough to change in the example below.
593 |
594 |
595 |
596 | For different methods of installation, please refer to the documentation.
597 |
598 |
599 |
600 |
601 |
Deft
602 |
603 |
604 | Zetteldeft relies on Deft.
605 | Let’s start with a basic setup.
606 |
668 | This should take care of keybindings as well.
669 | Take a look in the documentation to see how keys are bound.
670 |
671 |
672 |
673 |
674 |
675 |
676 |
Customization
677 |
678 |
679 | Some pointers for further customization:
680 |
681 |
682 |
alter zetteldeft-link-indicator to change the prefix to links,
683 | or set it to an empty string to remove it altogether,
684 |
change zetteldeft-title-prefix and zetteldeft-title-suffix to change how titles are appear,
685 |
you can modify zetteldeft-id-format to change how IDs are generated, but make sure to change zetteldeft-id-regex accordingly so that the new IDs can be detected.
686 |
687 |
688 |
689 | There’s more to Zetteldeft, and to its customization, but that’s all for this introduction.
690 |
691 |
692 |
693 |
694 |
695 |
Using Zetteldeft with Markdown notes
696 |
697 |
698 | While Zetteldeft works nicely with Org-mode, you can call its functions from any mode.
699 | Many people keep Zettelkasten in Markdown, so let’s explore how such a setup can be achieved.
700 |
701 |
702 |
703 | First, make sure deft-extensions is set correctly.
704 | If md is the first element on the list, new notes will be Markdown notes.
705 | Zetteldeft uses Deft to create new notes, so using zetteldeft-new-file should now create Markdown files.
706 |
707 |
708 |
709 |
(setq deft-extensions '("md""org""txt"))
710 |
711 |
712 |
713 |
714 | In such Zettelkasten links are often wrapped in square brackets.
715 | This can be easily achieved by setting the zetteldeft-link-indicator and zetteldeft-link-suffix.
716 |
773 | Luhmann was first and foremost a social theorist who developed a unique systems theory.
774 | In one of his writings, he compares his Zettelkasten to a "communicative system".
775 | Available here in English translation:
776 | http://luhmann.surge.sh/communicating-with-slip-boxes.
777 |
780 | Please beware that I’m no programmer.
781 | Zetteldeft is written in Emacs Lisp, the only language I can claim to have ever programmed in outside of school, but can’t claim to know well.
782 | I use Emacs mainly for writing, both personal and academic, and have fallen in love with its extensibility.
783 | My overall experience resonates strongly with this story shared by Richard Stallman:
784 |
785 |
786 |
787 |
788 | Multics Emacs proved to be a great success – programming new editing commands was so convenient that even the secretaries in his [Bernie Greenberg] office started learning how to use it.
789 | They used a manual someone had written which showed how to extend Emacs, but didn’t say it was a programming.
790 | So the secretaries, who believed they couldn’t do programming, weren’t scared off.
791 | They read the manual, discovered they could do useful things and they learned to program.
792 |
828 |
829 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
--------------------------------------------------------------------------------
/zetteldeft.el:
--------------------------------------------------------------------------------
1 | ;;; zetteldeft.el --- Turn deft into a zettelkasten system -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (C) 2018-2021 EFLS
4 |
5 | ;; Author: EFLS
6 | ;; URL: https://efls.github.io/zetteldeft/
7 | ;; Keywords: deft zettelkasten zetteldeft wp files
8 | ;; Version: 0.3
9 | ;; Package-Requires: ((emacs "25.1") (deft "0.8") (ace-window "0.7.0"))
10 |
11 | ;; This file is not part of Emacs
12 |
13 | ;; This program is free software; you can redistribute it and/or modify
14 | ;; it under the terms of the GNU General Public License as published by
15 | ;; the Free Software Foundation, either version 3 of the License, or
16 | ;; (at your option) any later version.
17 |
18 | ;; This program is distributed in the hope that it will be useful,
19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 | ;; GNU General Public License for more details.
22 |
23 | ;; You should have received a copy of the GNU General Public License
24 | ;; along with this program. If not, see .
25 |
26 | ;;; Commentary:
27 |
28 | ;; Zetteldeft is an extension of the deft package for Emacs.
29 | ;; It generates unique IDs to create stable links between notes, which
30 | ;; allows the user to make an interconnected system of notes.
31 | ;; Zetteldeft uses deft to find and follow links to notes.
32 | ;; For more information, see zetteldeft.org
33 | ;; or https://efls.github.io/zetteldeft
34 |
35 | ;; Note: this file is tangled from zetteldeft.org.
36 | ;; The .org contains documentation and notes on usage of the package.
37 |
38 | ;;; Code:
39 |
40 | (require 'deft)
41 |
42 | (unless (require 'avy nil 'no-error)
43 | (user-error "Avy not installed, required for zetteldeft-avy-* functions"))
44 |
45 | (require 'thingatpt)
46 |
47 | (require 'ace-window)
48 |
49 | (require 'seq)
50 |
51 | (declare-function avy-jump "avy")
52 | (unless (fboundp 'avy-jump)
53 | (display-warning 'zetteldeft
54 | "Function `avy-jump' not available. Please update `avy'"))
55 |
56 | (defgroup zetteldeft nil
57 | "A zettelkasten on top of deft."
58 | :group 'deft
59 | :link '(url-link "https://efls.github.io/zetteldeft"))
60 |
61 | ;;;###autoload
62 | (defun zetteldeft-search-at-point ()
63 | "Search via `deft' with `thing-at-point' as filter.
64 | Thing can be a double-bracketed link, a hashtag, or a word."
65 | (interactive)
66 | (let ((string (zetteldeft--get-thing-at-point)))
67 | (if string
68 | (zetteldeft--search-global string t)
69 | (user-error "No search term at point"))))
70 |
71 | ;;;###autoload
72 | (defun zetteldeft-search-current-id ()
73 | "Search deft with the id of the current file as filter.
74 | Open if there is only one result."
75 | (interactive)
76 | (zetteldeft--search-global
77 | (zetteldeft--current-id) t))
78 |
79 | (defun zetteldeft--get-thing-at-point ()
80 | "Return the thing at point.
81 | This can be
82 | - a link: a string between [[ brackets ]],
83 | - a tag matching `zetteldeft-tag-regex',
84 | - a link matching `zetteldeft-link-indicator',
85 | `zetteldeft-id-regex' and `zetteldeft-link-suffix',
86 | - or a word."
87 | (let* ((link-brackets-re "\\[\\[\\([^]]+\\)\\]\\]")
88 | (link-id-re (zetteldeft--link-regex))
89 | (htag-re zetteldeft-tag-regex))
90 | (cond
91 | ((thing-at-point-looking-at link-brackets-re)
92 | (match-string-no-properties 1))
93 | ((thing-at-point-looking-at link-id-re)
94 | (match-string-no-properties 0))
95 | ((thing-at-point-looking-at htag-re)
96 | (match-string-no-properties 0))
97 | (t (thing-at-point 'word t)))))
98 |
99 | (defun zetteldeft--search-global (str &optional dntOpn)
100 | "Search deft with STR as filter.
101 | If there is only one result, open that file (unless DNTOPN is true)."
102 | ;; Sanitize the filter string
103 | (setq str (replace-regexp-in-string "[[:space:]\n]+" " " str))
104 | ;; Switch to Deft window if buffer is currently visible
105 | (when (deft-buffer-visible-p)
106 | (select-window (deft-buffer-visible-p)))
107 | ;; Call deft search on the filter string
108 | (let ((deft-incremental-search t))
109 | (deft)
110 | (deft-filter str t))
111 | ;; If there is a single match, open the file
112 | (unless dntOpn
113 | (when (eq (length deft-current-files) 1)
114 | (deft-open-file (car deft-current-files)))))
115 |
116 | (defun zetteldeft--search-filename (thisStr &optional otherWindow)
117 | "Search for deft files with string THISSTR in filename.
118 | Open if there is only one result (in another window if OTHERWINDOW is non-nil).
119 | Return a message if no results are found."
120 | ;; Sanitize the filter string
121 | (setq thisStr (replace-regexp-in-string "[[:space:]\n]+" " " thisStr))
122 | ;; Call deft search on the filter string
123 | (let ((deft-filter-only-filenames t))
124 | (deft-filter thisStr t))
125 | ;; If there is a single match, open the file
126 | (cond
127 | ((eq (length deft-current-files) 1)
128 | (deft-open-file (car deft-current-files) otherWindow))
129 | ((eq (length deft-current-files) 0)
130 | (message "No notes found with %s in name." thisStr))))
131 |
132 | (defun zetteldeft--get-file-list (srch)
133 | "Return a list of files with the search item SRCH."
134 | (let ((deft-current-sort-method 'title))
135 | (deft-filter srch t)
136 | deft-current-files))
137 |
138 | ;;;###autoload
139 | (defun zetteldeft-search-tag ()
140 | "Prompt interactively for Zetteldeft tag and launch Deft search"
141 | (interactive)
142 | (let* ((tags (zetteldeft--get-all-sorted-tags))
143 | (search-term (completing-read "Tag to search for: " tags)))
144 | (zetteldeft--search-global search-term t)))
145 |
146 | (defun zetteldeft--id-font-lock-setup (var val)
147 | "Add font-lock highlighting for zetteldeft links.
148 | Called when `zetteldeft-link-indicator' or
149 | `zetteldeft-id-regex' are customized."
150 | (when (and (boundp 'zetteldeft-link-indicator)
151 | (boundp 'zetteldeft-id-regex)
152 | (boundp 'zetteldeft-link-suffix))
153 | (font-lock-remove-keywords 'org-mode
154 | `((,(concat zetteldeft-link-indicator
155 | zetteldeft-id-regex
156 | zetteldeft-link-suffix)
157 | . font-lock-warning-face))))
158 | (set-default var val)
159 | (when (and (boundp 'zetteldeft-id-regex)
160 | (boundp 'zetteldeft-link-indicator)
161 | (boundp 'zetteldeft-link-suffix))
162 | (font-lock-add-keywords 'org-mode
163 | `((,(concat zetteldeft-link-indicator
164 | zetteldeft-id-regex
165 | zetteldeft-link-suffix)
166 | . font-lock-warning-face)))))
167 |
168 | (defcustom zetteldeft-id-format "%Y-%m-%d-%H%M"
169 | "Format used when generating time-based zetteldeft IDs.
170 |
171 | Be warned: the regexp to find IDs is set separately.
172 | If you change this value, set `zetteldeft-id-regex' so that
173 | the IDs can be found.
174 |
175 | Check the documentation of the `format-time-string'
176 | function to see which placeholders can be used."
177 | :type 'string
178 | :group 'zetteldeft)
179 |
180 | (setq deft-new-file-format zetteldeft-id-format)
181 |
182 | (defun zetteldeft-generate-id (title &optional filename)
183 | "Generate and return a Zetteldeft ID.
184 | The ID is created using `zetteldeft-id-format', unless
185 | `zetteldeft-custom-id-function' is bound to a function, in which case
186 | that function is used and TITLE and FILENAME are passed to it."
187 | (let ((id
188 | (if-let ((f zetteldeft-custom-id-function))
189 | (funcall f title filename)
190 | (format-time-string zetteldeft-id-format))))
191 | (if (zetteldeft--id-available-p id)
192 | id
193 | (error "Generated ID %s is not unique." id))))
194 |
195 | (defun zetteldeft--id-available-p (str)
196 | "Return t only if provided string STR is unique among Zetteldeft filenames."
197 | (let ((deft-filter-only-filenames t))
198 | (deft-filter str t))
199 | (eq 0 (length deft-current-files)))
200 |
201 | (defcustom zetteldeft-custom-id-function nil
202 | "User-defined function to generate an ID.
203 | The specified function must accept arguments for note `TITLE'
204 | and &optional `FILENAME'. The returned ID must be a string."
205 | :type 'function
206 | :group 'zetteldeft)
207 |
208 | (defcustom zetteldeft-id-regex "[0-9]\\{4\\}\\(-[0-9]\\{2,\\}\\)\\{3\\}"
209 | "The regular expression used to search for zetteldeft IDs.
210 | Set it so that it matches strings generated with
211 | `zetteldeft-id-format'."
212 | :type 'string
213 | :group 'zetteldeft
214 | :set 'zetteldeft--id-font-lock-setup)
215 |
216 | (defcustom zetteldeft-link-indicator "§"
217 | "String to indicate zetteldeft links.
218 | String prepended to IDs to easily identify them as links to zetteldeft notes.
219 | This variable should be a string containing only one character."
220 | :type 'string
221 | :group 'zetteldeft
222 | :set 'zetteldeft--id-font-lock-setup)
223 |
224 | (defcustom zetteldeft-link-suffix ""
225 | "String to append to zetteldeft links.
226 | To disable, set to empty string rather than to nil."
227 | :type 'string
228 | :group 'zetteldeft
229 | :set 'zetteldeft--id-font-lock-setup)
230 |
231 | (defun zetteldeft--link-regex ()
232 | "Return regex for a Zetteldeft link.
233 | Concat link indicator, id-regex, and link suffix."
234 | (concat zetteldeft-link-indicator
235 | zetteldeft-id-regex
236 | zetteldeft-link-suffix))
237 |
238 | (defun zetteldeft--lift-id (str)
239 | "Extract zetteldeft ID from STR.
240 | This is done with the regular expression stored in
241 | `zetteldeft-id-regex'."
242 | (with-temp-buffer
243 | (insert str)
244 | (when (re-search-forward zetteldeft-id-regex nil t -1)
245 | (match-string 0))))
246 |
247 | (defun zetteldeft--insert-link (id &optional title)
248 | "Insert a link to Zetteldeft note ID.
249 | If TITLE is included, use it as link text. To customize how inserted
250 | links are formatted, change the `zetteldeft-insert-link-function'
251 | variable."
252 | (interactive)
253 | (funcall zetteldeft-insert-link-function id title))
254 |
255 | (defcustom zetteldeft-insert-link-function
256 | #'zetteldeft-insert-link-zd-style
257 | "The function to use when inserting note links.
258 |
259 | Use either
260 | - `zetteldeft-insert-link-zd-style' for Zetteldeft type links
261 | - `zetteldeft-insert-link-org-style' for Org-mode zdlink: links
262 | - A custom function that takes two arguments: an ID and an optional title."
263 | :type 'function
264 | :options '(zetteldeft-insert-link-zd-style
265 | zetteldeft-insert-link-org-style)
266 | :group 'zetteldeft)
267 |
268 | (defun zetteldeft-insert-link-zd-style (id &optional title)
269 | "Insert a Zetteldeft link to note with provided ID."
270 | (insert zetteldeft-link-indicator
271 | id
272 | zetteldeft-link-suffix)
273 | (when title (insert " " title)))
274 |
275 | (defun zetteldeft-insert-link-org-style (id &optional title)
276 | "Insert a Zetteldeft link in Org-mode format as zdlink: type."
277 | (if title
278 | (insert "[[zdlink:" id "][" title "]]")
279 | (insert "[[zdlink:" id "]]")))
280 |
281 | ;;;###autoload
282 | (defun zetteldeft-find-file (file)
283 | "Open deft file FILE.
284 | When no completing match, prompt user to create a new deft file using
285 | input as the title."
286 | (interactive
287 | (list (completing-read "Deft find file: "
288 | (deft-find-all-files-no-prefix))))
289 | (let* ((dir (expand-file-name deft-directory)))
290 | (unless (string-match (concat "^" dir) file)
291 | (setq file (concat dir "/" file)))
292 | (if (file-exists-p file)
293 | (deft-open-file file)
294 | (when (y-or-n-p (format
295 | "Create new note with title \"%s\"?"
296 | (file-name-base file)))
297 | (zetteldeft-new-file (file-name-base file))))))
298 |
299 | (defvar zetteldeft-home-id nil
300 | "String with ID of home note, used by `zetteldeft-go-home'.")
301 |
302 | (defun zetteldeft-go-home ()
303 | "Move to a designated home note.
304 | Set `zetteldeft-home-id' to an ID string of your home note."
305 | (interactive)
306 | (if (stringp zetteldeft-home-id)
307 | (zetteldeft-find-file
308 | (zetteldeft--id-to-full-path zetteldeft-home-id))
309 | (message "No home set. Provide a string to zetteldeft-home-id.")))
310 |
311 | ;;;###autoload
312 | (defun zetteldeft-find-file-id-insert (file)
313 | "Find deft file FILE and insert a link."
314 | (interactive (list
315 | (completing-read "File to insert id from: "
316 | (deft-find-all-files-no-prefix))))
317 | (zetteldeft--insert-link (zetteldeft--lift-id file)))
318 |
319 | (defcustom zetteldeft-backlink-prefix "# Backlink: "
320 | "Prefix string included before a back link.
321 | Formatted as `org-mode' comment by default."
322 | :type 'string
323 | :group 'zetteldeft)
324 |
325 | (defcustom zetteldeft-backlink-location-function
326 | #'zetteldeft-backlink-get-location
327 | "Function to get location for new backlinks.
328 | The function should return a position in the current buffer.")
329 |
330 | (defun zetteldeft-backlink-get-location ()
331 | "Default function that returns where a backlink should be added.
332 |
333 | This is the line below whichever is found first:
334 | - existing backlink
335 | - tag line
336 | - title
337 | - at top of file
338 | "
339 | (interactive)
340 | (save-excursion
341 | (goto-char (point-min))
342 | (cond
343 | ((re-search-forward (regexp-quote zetteldeft-backlink-prefix) nil t)
344 | (forward-line)
345 | (point))
346 | ((re-search-forward (regexp-quote zetteldeft-tag-line-prefix) nil t)
347 | (forward-line)
348 | (newline)
349 | (point))
350 | ((re-search-forward (regexp-quote zetteldeft-title-prefix) nil t)
351 | (forward-line)
352 | (newline)
353 | (point))
354 | (t (point-min)))))
355 |
356 | ;;;###autoload
357 | (defun zetteldeft-backlink-add (file)
358 | "Find deft file FILE and insert a backlink to it.
359 | Finds the title line, and adds `backlink-prefix' with
360 | ID and title on a new line."
361 | (interactive (list
362 | (completing-read "File to add backlink to: "
363 | (deft-find-all-files-no-prefix))))
364 | (save-excursion
365 | (goto-char (funcall zetteldeft-backlink-location-function))
366 | (insert zetteldeft-backlink-prefix)
367 | (zetteldeft--insert-link
368 | (zetteldeft--lift-id file)
369 | (zetteldeft--lift-file-title (concat deft-directory file)))
370 | (insert "\n"))
371 | (message "Backlink added."))
372 |
373 | ;;;###autoload
374 | (defun zetteldeft-find-file-full-title-insert (file)
375 | "Find deft file FILE and insert a link with title."
376 | (interactive (list
377 | (completing-read "File to insert full title from: "
378 | (deft-find-all-files-no-prefix))))
379 | (let ((id (zetteldeft--lift-id file)))
380 | (zetteldeft--insert-link
381 | id
382 | (zetteldeft--id-to-title id))))
383 |
384 | (defun zetteldeft--full-search (string)
385 | "Return list of deft files with STRING in full body of file."
386 | (let ((dir (expand-file-name deft-directory))
387 | (result-files (zetteldeft--get-file-list string))
388 | (this-file (buffer-file-name)))
389 | (when this-file
390 | (setq result-files (delete this-file result-files)))
391 | (setq result-files
392 | (mapcar
393 | (lambda (f) (replace-regexp-in-string dir "" f))
394 | result-files))
395 | (completing-read (format "Search files containing \"%s\": " string)
396 | result-files)))
397 |
398 | ;;;###autoload
399 | (defun zetteldeft-full-search-id-insert (string)
400 | "Insert ID of file from list of files containing STRING."
401 | (interactive (list (read-string "Search string: ")))
402 | (zetteldeft-find-file-id-insert
403 | (zetteldeft--full-search string)))
404 |
405 | ;;;###autoload
406 | (defun zetteldeft-full-search-full-title-insert (string)
407 | "Insert title and ID of file from list of files containing
408 | STRING."
409 | (interactive (list (read-string "Search string: ")))
410 | (zetteldeft-find-file-full-title-insert
411 | (zetteldeft--full-search string)))
412 |
413 | ;;;###autoload
414 | (defun zetteldeft-full-search-find-file (string)
415 | "Open file containing STRING."
416 | (interactive (list (read-string "Search string: ")))
417 | (zetteldeft-find-file (zetteldeft--full-search string)))
418 |
419 | (defcustom zetteldeft-id-filename-separator " "
420 | "String to separate zetteldeft ID from filename."
421 | :type 'string
422 | :group 'zetteldeft)
423 |
424 | (declare-function evil-insert-state "evil")
425 |
426 | (defcustom zetteldeft-new-filename-to-kill-ring nil
427 | "Add new filename to kill ring?"
428 | :type 'boolean
429 | :group 'zetteldeft)
430 |
431 | ;;;###autoload
432 | (defun zetteldeft-new-file (str &optional id)
433 | "Create a new deft file.
434 |
435 | The filename is a Zetteldeft ID, appended by STR. The ID will be
436 | generated, unless ID is provided. A file title will be inserted in the
437 | newly created file wrapped in `zetteldeft-title-prefix' and
438 | `zetteldeft-title-suffix'. When `zetteldeft-new-filename-to-kill-ring'
439 | is non-nil, the filename (without extension) is added to the kill
440 | ring. When `evil' is loaded, change to insert state."
441 | (interactive (list (read-string "Note title: ")))
442 | (let* ((deft-use-filename-as-title t)
443 | (zdId (or id
444 | (zetteldeft-generate-id str)))
445 | (zdName (concat zdId zetteldeft-id-filename-separator str)))
446 | (deft-new-file-named zdName)
447 | (when zetteldeft-new-filename-to-kill-ring
448 | (kill-new zdName))
449 | (zetteldeft--insert-title str)
450 | (save-buffer)
451 | (when (featurep 'evil) (evil-insert-state))))
452 |
453 | ;;;###autoload
454 | (defun zetteldeft-new-file-and-link (str)
455 | "Create a new note and insert a link to it.
456 | Similar to `zetteldeft-new-file', but insert a link to the new file."
457 | (interactive (list (read-string "Note title: ")))
458 | (let ((zdId (zetteldeft-generate-id str)))
459 | (zetteldeft--insert-link zdId str)
460 | (zetteldeft-new-file str zdId)))
461 |
462 | ;;;###autoload
463 | (defun zetteldeft-new-file-and-backlink (str)
464 | "Create a new note and insert link and backlink."
465 | (interactive (list (read-string "Note title: ")))
466 | (let ((ogId (zetteldeft--current-id))
467 | (zdId (zetteldeft-generate-id str)))
468 | (zetteldeft--insert-link zdId str)
469 | (zetteldeft-new-file str zdId)
470 | (newline)
471 | (zetteldeft--insert-link ogId (zetteldeft--id-to-title ogId))))
472 |
473 | (defun zetteldeft-extract-region-to-note (title)
474 | "Extract the marked region to a new note with TITLE."
475 | (interactive (list (if (not (use-region-p))
476 | (user-error "No region active.")
477 | (read-string "Note title: "))))
478 | (let* ((id (zetteldeft-generate-id title))
479 | (text (kill-region (region-beginning) (region-end))))
480 | (save-excursion
481 | (zetteldeft-new-file title id)
482 | (yank)
483 | (save-buffer))
484 | (zetteldeft--insert-link id title)))
485 |
486 | ;;;###autoload
487 | (defun zetteldeft-follow-link ()
488 | "Use Avy to follow a link.
489 |
490 | Follows zetteldeft link to a file if point is on a link.
491 | Uses Avy to prompt for a link to follow with `zetteldeft-avy-file-search'
492 | if it isn't. Variable `zetteldeft-follow-at-point' controls this last
493 | option: when nil, always use Avy."
494 | (interactive)
495 | (if (and zetteldeft-follow-at-point
496 | (thing-at-point-looking-at (zetteldeft--link-regex)))
497 | (zetteldeft--search-filename
498 | (zetteldeft--lift-id (zetteldeft--get-thing-at-point)))
499 | (zetteldeft-avy-file-search)))
500 |
501 | (defcustom zetteldeft-follow-at-point t
502 | "Should `zetteldeft-follow-link' open link at point?
503 | When t, open note at point if point is on a link.
504 | When nil, always use Avy."
505 | :type 'boolean
506 | :group 'zetteldeft)
507 |
508 | ;;;###autoload
509 | (defun zetteldeft-browse ()
510 | "Browse your notes with avy.
511 | Keep calling `zetteldeft-avy-file-search' in a loop."
512 | (interactive)
513 | (let ((avy-single-candidate-jump nil)
514 | (avy-handler-function 'zetteldeft--browse-avy-handler))
515 | (while (zetteldeft-avy-file-search)
516 | (message "Browsing in Zetteldeft! [.] home [<] prev [>] next"))))
517 |
518 | (defun zetteldeft--browse-avy-handler (char)
519 | "The default handler for a bad CHAR."
520 | (let (dispatch)
521 | (cond ((setq dispatch (assoc char avy-dispatch-alist))
522 | (unless (eq avy-style 'words)
523 | (setq avy-action (cdr dispatch)))
524 | (throw 'done 'restart))
525 | ((memq char avy-escape-chars)
526 | ;; exit silently
527 | (throw 'done 'abort))
528 | ;; Go to home note
529 | ((eq char ?.)
530 | (zetteldeft-go-home)
531 | (message "Brwosing to home note.")
532 | (zetteldeft-browse)
533 | (throw 'done 'abort))
534 | ;; Previous buffer
535 | ((eq char ?<)
536 | (previous-buffer)
537 | (message "Browsing to previous buffer.")
538 | (zetteldeft-browse)
539 | (throw 'done 'abort))
540 | ((eq char ?>)
541 | (next-buffer)
542 | (message "Browsing to next buffer.")
543 | (zetteldeft-browse)
544 | (throw 'done 'abort))
545 | ((eq char ??)
546 | (avy-show-dispatch-help)
547 | (throw 'done 'restart))
548 | ((mouse-event-p char)
549 | (signal 'user-error (list "Mouse event not handled" char)))
550 | (t
551 | (message "No such candidate: %s, hit `C-g' to quit."
552 | (if (characterp char) (string char) char))))))
553 |
554 | ;;;###autoload
555 | (defun zetteldeft-avy-tag-search ()
556 | "Call on avy to jump to a tag.
557 | Tags are filtered with `zetteldeft-tag-regex'."
558 | (interactive)
559 | (save-excursion
560 | (let ((avy-all-windows nil))
561 | (when (consp (avy-jump zetteldeft-tag-regex))
562 | (zetteldeft-search-at-point)))))
563 |
564 | ;;;###autoload
565 | (defun zetteldeft-avy-file-search (&optional otherWindow)
566 | "Use `avy' to follow a zetteldeft link.
567 | Links are found via `zetteldeft-link-indicator' and `zetteldeft-id-regex'.
568 | Open that file (in another window if OTHERWINDOW)."
569 | (interactive)
570 | (save-excursion
571 | (when (consp (avy-jump (zetteldeft--link-regex)))
572 | (zetteldeft--search-filename
573 | (zetteldeft--lift-id (zetteldeft--get-thing-at-point)) otherWindow))))
574 |
575 | (declare-function aw-select "ace-window")
576 |
577 | ;;;###autoload
578 | (defun zetteldeft-avy-file-search-ace-window ()
579 | "Use `avy' to follow a zetteldeft link in another window.
580 | Similar to `zetteldeft-avy-file-search', but with window selection.
581 | When only one window is active, split it first.
582 | When more windows are active, select one via `ace-window'."
583 | (interactive)
584 | (save-excursion
585 | (when (consp (avy-jump (zetteldeft--link-regex)))
586 | (let ((ID (zetteldeft--lift-id (zetteldeft--get-thing-at-point))))
587 | (when (eq 1 (length (window-list))) (split-window))
588 | (select-window (aw-select "Select window..."))
589 | (zetteldeft--search-filename ID)))))
590 |
591 | ;;;###autoload
592 | (defun zetteldeft-avy-link-search ()
593 | "Use `avy' to perform a deft search on a zetteldeft link.
594 | Similar to `zetteldeft-avy-file-search' but performs a full
595 | text search for the link ID instead of filenames only.
596 | Opens immediately if there is only one result."
597 | (interactive)
598 | (save-excursion
599 | (when (consp (avy-jump (zetteldeft--link-regex)))
600 | (zetteldeft--search-global
601 | (zetteldeft--lift-id (zetteldeft--get-thing-at-point))))))
602 |
603 | (defun zetteldeft--list-dead-links ()
604 | "Return a list with IDs in Zetteldeft notes that have no corresponding note."
605 | (let ((dead-links '())
606 | (deft-filter-only-filenames t))
607 | (dolist (link (zetteldeft--list-all-links))
608 | (deft-filter link t)
609 | (when (eq 0 (length deft-current-files))
610 | (unless (member link dead-links)
611 | (push link dead-links))))
612 | dead-links))
613 |
614 | (defconst zetteldeft--dead-links-buffer-name "*zetteldeft-dead-links*")
615 |
616 | (defun zetteldeft-dead-links-buffer ()
617 | "Show a buffer with all dead links in Zetteldeft."
618 | (interactive)
619 | (switch-to-buffer zetteldeft--dead-links-buffer-name)
620 | (erase-buffer)
621 | (message "Finding all dead Zetteldeft links...")
622 | (let ((dead-links (zetteldeft--list-dead-links)))
623 | (insert (format "# Found %d dead links\n" (length dead-links)))
624 | (dolist (link dead-links)
625 | (insert (format " - %s in: " link))
626 | (deft-filter link t)
627 | (dolist (source (deft-current-files))
628 | (zetteldeft--insert-link (zetteldeft--lift-id source)))
629 | (insert "\n")))
630 | (unless (eq major-mode 'org-mode) (org-mode)))
631 |
632 | ;;;###autoload
633 | (defun zetteldeft-deft-new-search ()
634 | "Launch deft, clear filter and enter insert state."
635 | (interactive)
636 | (deft)
637 | (deft-filter-clear)
638 | (when (featurep 'evil) (evil-insert-state)))
639 |
640 | (defun zetteldeft--check ()
641 | "Check if the currently visited file is in `zetteldeft' territory:
642 | whether it has `deft-directory' somewhere in its path."
643 | (unless (buffer-file-name)
644 | (user-error "Buffer not visiting a file"))
645 | (unless (string-match-p
646 | (regexp-quote (file-truename deft-directory))
647 | (file-truename (buffer-file-name)))
648 | (user-error "Not in zetteldeft territory")))
649 |
650 | (defun zetteldeft--current-id ()
651 | "Retrieve ID from current file."
652 | (zetteldeft--check)
653 | (zetteldeft--lift-id
654 | (file-name-base (buffer-file-name))))
655 |
656 | (defcustom zetteldeft-title-prefix "#+TITLE: "
657 | "Prefix string included when `zetteldeft--insert-title' is called.
658 | Formatted for `org-mode' by default.
659 | Don't forget to include a space."
660 | :type 'string
661 | :group 'zetteldeft)
662 |
663 | (defcustom zetteldeft-title-suffix ""
664 | "String inserted below title when `zetteldeft--insert-title' is called.
665 | Empty by default.
666 | Don't forget to add `\\n' at the beginning to start a new line."
667 | :type 'string
668 | :group 'zetteldeft)
669 |
670 | (defun zetteldeft--insert-title (title)
671 | "Insert TITLE as title in file.
672 | Prepended by `zetteldeft-title-prefix' and appended by `zetteldeft-title-suffix'."
673 | (zetteldeft--check)
674 | (insert
675 | zetteldeft-title-prefix
676 | title
677 | zetteldeft-title-suffix))
678 |
679 | (defcustom zetteldeft-title-parsing-function #'deft-parse-title
680 | "Function used to extract a title from a note; defaults to `deft-parse-title'.
681 | The function you use here must be compatible with `deft-parse-title': the
682 | first argument is the file's fully qualified path; the second is the contents
683 | of said file."
684 | :type 'function
685 | :group 'zetteldeft)
686 |
687 | (defun zetteldeft--lift-file-title (zdFile)
688 | "Return the title of a zetteldeft note.
689 | ZDFILE should be a full path to a note."
690 | (let ((deft-use-filename-as-title nil))
691 | (funcall zetteldeft-title-parsing-function
692 | zdFile
693 | (with-temp-buffer
694 | (insert-file-contents zdFile)
695 | (buffer-string)))))
696 |
697 | ;;;###autoload
698 | (defun zetteldeft-file-rename ()
699 | "Change current file's title, and use the new title to rename the file.
700 | Use this on files in the `deft-directory'.
701 | When the file has no Zetteldeft ID, one is generated and included in the new name."
702 | (interactive)
703 | (zetteldeft--check)
704 | (let ((old-filename (buffer-file-name)))
705 | (when old-filename
706 | (let* ((old-title (zetteldeft--lift-file-title old-filename))
707 | (prompt-text (concat "Change " old-title " to: "))
708 | (new-title (read-string prompt-text old-title))
709 | (id (or (zetteldeft--lift-id (file-name-base old-filename))
710 | (zetteldeft-generate-id new-title old-filename)))
711 | (new-filename
712 | (deft-absolute-filename
713 | (concat id zetteldeft-id-filename-separator new-title))))
714 | (rename-file old-filename new-filename)
715 | (deft-update-visiting-buffers old-filename new-filename)
716 | (zetteldeft-update-title-in-file new-title)
717 | (deft-refresh)))))
718 |
719 | (defcustom zetteldeft-always-insert-title t
720 | "When renaming a note, insert title if not already present."
721 | :type 'boolean
722 | :group 'zetteldeft)
723 |
724 | (defun zetteldeft-update-title-in-file (title)
725 | "Update the title in the current note buffer to TITLE.
726 | This searches the buffer for `zetteldeft-title-prefix' and updates the current
727 | title, if present. If not present and `zetteldeft-always-insert-title' is set,
728 | this inserts a title line at the beginning of the buffer. Otherwise, no change
729 | is made."
730 | (save-excursion
731 | (let ((zetteldeft-title-suffix ""))
732 | (goto-char (point-min))
733 | (if (re-search-forward (regexp-quote zetteldeft-title-prefix) nil t)
734 | (progn (delete-region (line-beginning-position) (line-end-position))
735 | (zetteldeft--insert-title title))
736 | (when zetteldeft-always-insert-title
737 | (zetteldeft--insert-title title)
738 | (newline))))))
739 |
740 | ;;;###autoload
741 | (defun zetteldeft-count-words ()
742 | "Prints total number of words and notes in the minibuffer."
743 | (interactive)
744 | (let ((numWords 0))
745 | (dolist (deftFile deft-all-files)
746 | (with-temp-buffer
747 | (insert-file-contents deftFile)
748 | (setq numWords (+ numWords (count-words (point-min) (point-max))))))
749 | (message
750 | "Your zettelkasten contains %s notes with %s words in total."
751 | (length deft-all-files) numWords)))
752 |
753 | ;;;###autoload
754 | (defun zetteldeft-copy-id-current-file ()
755 | "Copy current ID.
756 | Add the id from the filename the buffer is currently visiting to the
757 | kill ring."
758 | (interactive)
759 | (zetteldeft--check)
760 | (let ((ID (concat zetteldeft-link-indicator
761 | (zetteldeft--lift-id (file-name-base (buffer-file-name)))
762 | zetteldeft-link-suffix)))
763 | (kill-new ID)
764 | (message "%s" ID)))
765 |
766 | (defun zetteldeft--extract-links (deftFile)
767 | "Find all links in DEFTFILE and return a list."
768 | (let ((zdLinks (list)))
769 | (with-temp-buffer
770 | (insert-file-contents deftFile)
771 | (while (re-search-forward zetteldeft-id-regex nil t)
772 | (let ((foundTag (replace-regexp-in-string " " "" (match-string 0))))
773 | ;; Add found tag to zdLinks if it isn't there already
774 | (unless (member foundTag zdLinks)
775 | (push foundTag zdLinks)))
776 | ;; Remove found tag from buffer
777 | (delete-region (point) (re-search-backward zetteldeft-id-regex))))
778 | zdLinks))
779 |
780 | (defun zetteldeft--list-all-links ()
781 | "Return a list with all IDs that appear in notes."
782 | (let ((all-links '()))
783 | (dolist (file deft-all-files)
784 | (dolist (link (zetteldeft--extract-links file))
785 | (unless (member link all-links)
786 | (push link all-links))))
787 | all-links))
788 |
789 | (defun zetteldeft--id-to-full-path (zdID)
790 | "Return full path from given zetteldeft ID ZDID.
791 | Returns nil when no files are found.
792 | Throws an error when multiple files are found."
793 | (let ((deft-filter-only-filenames t))
794 | (deft-filter zdID t))
795 | (when (> (length deft-current-files) 1)
796 | (user-error "ID Error. Multiple zetteldeft files found with ID %s" zdID))
797 | (car deft-current-files))
798 |
799 | (defun zetteldeft--id-to-title (zdId)
800 | "Turn a Zetteldeft ID into the title."
801 | (zetteldeft--lift-file-title
802 | (zetteldeft--id-to-full-path zdId)))
803 |
804 | (defcustom zetteldeft-tag-regex "[#@][[:alnum:]_-]+"
805 | "Regular expression for finding Zetteldeft tags."
806 | :type 'string
807 | :group 'zetteldeft)
808 |
809 | (defcustom zetteldeft-tag-prefix "#"
810 | "String prefix used when inserting new Zetteldeft tags."
811 | :type 'string
812 | :group 'zetteldeft)
813 |
814 | (defcustom zetteldeft-tag-line-prefix "# Tags"
815 | "String used to find the line where tags in Zetteldeft files should go."
816 | :type 'string
817 | :group 'zetteldeft)
818 |
819 | ;;;###autoload
820 | (defun zetteldeft-tag-insert-at-point (tag)
821 | "Insert TAG at point. Interactively, select an existing tag or provide new one."
822 | (interactive (list (completing-read
823 | "Tag to insert: "
824 | (zetteldeft--get-all-sorted-tags))))
825 | (unless (string-prefix-p zetteldeft-tag-prefix tag)
826 | (insert zetteldeft-tag-prefix))
827 | (insert tag))
828 |
829 | ;;;###autoload
830 | (defun zetteldeft-tag-insert ()
831 | "Select existing tag or enter new one to insert in current Zetteldeft note.
832 |
833 | The tag is appended to the first line starting with `zetteldeft-tag-line-prefix'.
834 | If this variable is nil, or tag line is not found, insert tag at point."
835 | (interactive)
836 | (zetteldeft--check)
837 | (let ((dest (when zetteldeft-tag-line-prefix
838 | (save-excursion
839 | (goto-char (point-min))
840 | (re-search-forward zetteldeft-tag-line-prefix nil t)))))
841 | (if dest
842 | (save-excursion
843 | (goto-char dest)
844 | (end-of-line)
845 | (insert " ")
846 | (call-interactively 'zetteldeft-tag-insert-at-point))
847 | (call-interactively 'zetteldeft-tag-insert-at-point))))
848 |
849 | (defun zetteldeft-tag-remove ()
850 | "Prompt for a tag to remove from the current Zetteldeft note.
851 | Only the first instance of the selected tag is removed."
852 | (interactive)
853 | (zetteldeft--check)
854 | ; Extract tags of current file into `zetteldeft--tag-list'
855 | (setq zetteldeft--tag-list (list))
856 | (save-buffer)
857 | (zetteldeft--extract-tags (buffer-file-name))
858 | ; Select a tag from that list
859 | (let* ((tag (completing-read
860 | "Tag to remove: "
861 | (seq-filter 'stringp zetteldeft--tag-list))))
862 | ; Find and remove first instance of that tag
863 | (save-excursion
864 | (goto-char (point-min))
865 | (re-search-forward tag nil t)
866 | (delete-region (point) (re-search-backward tag nil t))
867 | ; remove potential empty space before tag
868 | (backward-char)
869 | (when (looking-at " ") (delete-char 1)))))
870 |
871 | (defconst zetteldeft--tag-buffer-name "*zetteldeft-tag-buffer*")
872 |
873 | ;;;###autoload
874 | (defun zetteldeft-tag-buffer ()
875 | "Switch to the `zetteldeft-tag-buffer' and list tags."
876 | (interactive)
877 | (switch-to-buffer zetteldeft--tag-buffer-name)
878 | (erase-buffer)
879 | (let ((tagList (zetteldeft--get-all-tags)))
880 | (dolist (zdTag tagList)
881 | (when (stringp zdTag)
882 | (insert (format "%s (%d) \n"
883 | zdTag
884 | (lax-plist-get tagList zdTag)))))
885 | (unless (eq major-mode 'org-mode) (org-mode))
886 | (sort-lines nil (point-min) (point-max))
887 | (goto-char (point-min))))
888 |
889 | (defvar zetteldeft--tag-list nil
890 | "A temporary property list to store all tags.")
891 |
892 | (defun zetteldeft--get-all-tags ()
893 | "Return a plist of all the tags found in zetteldeft files."
894 | (setq zetteldeft--tag-list (list))
895 | (dolist (deftFile deft-all-files)
896 | (zetteldeft--extract-tags deftFile))
897 | zetteldeft--tag-list)
898 |
899 | (defun zetteldeft--get-all-sorted-tags ()
900 | "Return a sorted plist of all the tags found in zetteldeft files."
901 | (seq-sort 'string-lessp
902 | (seq-filter 'stringp
903 | (zetteldeft--get-all-tags))))
904 |
905 | (defun zetteldeft--tag-format ()
906 | "Adjust `zetteldeft-tag-regex' for more accurate results."
907 | (concat "\\(^\\|\s\\)" zetteldeft-tag-regex))
908 |
909 | (defun zetteldeft--extract-tags (deftFile)
910 | "Find all tags in DEFTFILE and add them to `zetteldeft--tag-list'.
911 | Increase counters as we go."
912 | (with-temp-buffer
913 | (insert-file-contents deftFile)
914 | (while (re-search-forward (zetteldeft--tag-format) nil t)
915 | (let ((foundTag (replace-regexp-in-string " " "" (match-string 0))))
916 | ;; Add found tag to zetteldeft--tag-list if it isn't there already
917 | (zetteldeft--tag-count foundTag))
918 | ;; Remove found tag from buffer
919 | (delete-region (point) (re-search-backward (zetteldeft--tag-format))))))
920 |
921 | (defun zetteldeft--tag-count (zdTag)
922 | (let ((tagCount (lax-plist-get zetteldeft--tag-list zdTag)))
923 | (if tagCount
924 | ; if the tag was there already, inc by 1
925 | (setq zetteldeft--tag-list
926 | (lax-plist-put zetteldeft--tag-list zdTag (1+ tagCount)))
927 | ; if tag was not there yet, add & set to 1
928 | (setq zetteldeft--tag-list
929 | (lax-plist-put zetteldeft--tag-list zdTag 1)))))
930 |
931 | ;;;###autoload
932 | (defun zetteldeft-insert-list-links (string)
933 | "Search for SEARCH-STRING and insert list of links to results."
934 | (interactive (list (read-string "search string: ")))
935 | (let ((result-files (zetteldeft--get-file-list string))
936 | (this-file (buffer-file-name)))
937 | (when this-file
938 | (setq result-files (delete this-file result-files)))
939 | (dolist (file result-files)
940 | (zetteldeft--list-entry-file-link file))))
941 |
942 | (defcustom zetteldeft-list-links-missing-message
943 | " No missing links with search term =%s= found\n"
944 | "Message to insert when no missing links are found.
945 | This is used by `zetteldeft-insert-list-links-missing'.
946 | %s will be replaced by the search term provided to
947 | this function."
948 | :type 'string
949 | :group 'zetteldeft)
950 |
951 | ;;;###autoload
952 | (defun zetteldeft-insert-list-links-missing (string)
953 | "Insert a list of links to all deft files with a search string ZDSRCH.
954 | In contrast to `zetteldeft-insert-list-links' only include links not
955 | yet present in the current file. Can only be called from a file in the
956 | zetteldeft directory."
957 | (interactive (list (read-string "Search string: ")))
958 | (zetteldeft--check)
959 | (let (this-id ; ID of current file
960 | current-ids ; IDs present in current buffer
961 | found-ids ; IDs of notes with search result
962 | final-ids) ; Final list of IDs for the list
963 | ;; Gather list of IDs in current buffer
964 | (setq current-ids (zetteldeft--extract-links (buffer-file-name)))
965 | ;; Execute search and push found IDs to temporary list
966 | (dolist (file (zetteldeft--get-file-list string))
967 | (push (zetteldeft--lift-id file) found-ids))
968 | ;; Keep only unique IDs on the final list
969 | (dolist (id found-ids)
970 | (unless (member id current-ids)
971 | (push id final-ids)))
972 | ;; Remove the ID of the current buffer from the final list
973 | (setq this-id (zetteldeft--lift-id (file-name-base (buffer-file-name))))
974 | (setq final-ids (delete this-id final-ids))
975 | ;; Finally insert ID and title for each element on the list
976 | (if final-ids
977 | (dolist (id final-ids)
978 | (zetteldeft--list-entry-file-link (zetteldeft--id-to-full-path id)))
979 | ;; Unless the list is empty, then insert a message
980 | (insert (format zetteldeft-list-links-missing-message string)))))
981 |
982 | (defcustom zetteldeft-list-prefix " - "
983 | "Prefix for lists created with `zetteldeft-insert-list-links'
984 | and `zetteldeft-insert-list-links-missing'."
985 | :type 'string
986 | :group 'zetteldeft)
987 |
988 | (defun zetteldeft--list-entry-file-link (file)
989 | "Insert ZDFILE as list entry."
990 | (let ((id (zetteldeft--lift-id (file-name-base file))))
991 | (insert zetteldeft-list-prefix)
992 | (when id
993 | (insert zetteldeft-link-indicator
994 | id
995 | zetteldeft-link-suffix
996 | " "))
997 | (insert (zetteldeft--lift-file-title file)
998 | "\n")))
999 |
1000 | (defun zetteldeft-list-links (str &optional missing sort)
1001 | "Crude attempt to automate `zetteldeft-insert-list-links-missing'.
1002 | Meant to be called from an Org source block.
1003 | Replaces the list below the source block.
1004 | When MISSING is t, use `zetteldeft-insert-list-links-missing'.
1005 | When SORT is t, sort the list with most recent at top."
1006 | (save-excursion
1007 | (org-forward-element)
1008 | ; Delete any existing list
1009 | (when (org-in-item-p)
1010 | (delete-region (point) (org-end-of-item-list)))
1011 | ; New line & insert links
1012 | (if missing
1013 | (progn (save-buffer)
1014 | (deft-refresh)
1015 | (zetteldeft-insert-list-links-missing str))
1016 | (zetteldeft-insert-list-links str))
1017 | (when sort
1018 | (org-backward-element)
1019 | (org-sort-list t ?A))))
1020 |
1021 | (defun zetteldeft-insert-list-links-block (str)
1022 | "Prompt for a STR to search, and insert an org-mode
1023 | source block that calls `zetteldeft-list-links'.
1024 | Also include the list of links below the block.
1025 | When called with a prefix, make use of the missing links
1026 | functions."
1027 | (interactive
1028 | (list (read-string "Search: ")))
1029 | (newline)
1030 | (insert "#+BEGIN_SRC emacs-lisp :results silent\n"
1031 | "(zetteldeft-list-links \"" str"\"")
1032 | (when current-prefix-arg (insert " t"))
1033 | (insert ")\n"
1034 | "#+END_SRC\n\n")
1035 | (if current-prefix-arg
1036 | (progn (save-buffer)
1037 | (deft-refresh)
1038 | (zetteldeft-insert-list-links-missing str))
1039 | (zetteldeft-insert-list-links str)))
1040 |
1041 | (defun zetteldeft-org-dblock-insert-links (string)
1042 | "Insert an Org Dynamic Block of the `zetteldeft-links' type.
1043 |
1044 | The Dynamic Block takes the following parameters:
1045 | - :search 'string', the search string
1046 | - :sort t, to sort the results
1047 | - :missing-only t, to only include missing links.
1048 | "
1049 | (interactive
1050 | (list (read-string "Search: ")))
1051 | (org-create-dblock (list :name "zetteldeft-links" :search string))
1052 | (org-update-dblock))
1053 |
1054 | (with-eval-after-load 'org-mode
1055 | (org-dynamic-block-define "zetteldeft-links"
1056 | 'zetteldeft-org-dblock-insert-links))
1057 |
1058 | (defun org-dblock-write:zetteldeft-links (params)
1059 | "Fill the zetteldeft search Org Dynamic Block with contents."
1060 | (let ((string (plist-get params :search))
1061 | (missing-only (plist-get params :missing-only))
1062 | (sort (plist-get params :sort)))
1063 | (if missing-only
1064 | (zetteldeft-insert-list-links-missing string)
1065 | (zetteldeft-insert-list-links string))
1066 | (when sort
1067 | (org-backward-element)
1068 | (org-sort-list t ?A))))
1069 |
1070 | (defcustom zetteldeft-export-tmp-dir
1071 | (expand-file-name "zetteldeft/tmp/" user-emacs-directory)
1072 | "Temporary directory for Zetteldeft export")
1073 |
1074 | (defun zetteldeft--export-prepare-tmp-notes (&optional ignored)
1075 | "Copy Zetteldeft files and prepare for export."
1076 | (delete-directory zetteldeft-export-tmp-dir t t)
1077 | (make-directory zetteldeft-export-tmp-dir t)
1078 | (deft-refresh)
1079 | (message
1080 | "Zetteldeft preparing notes for export at %s"
1081 | zetteldeft-export-tmp-dir)
1082 | (dolist (file (deft-find-all-files))
1083 | (zetteldeft--export-prepare-file file))
1084 | (message "Zetteldeft notes copy finished."))
1085 |
1086 | (defun zetteldeft--export-prepare-file (zdFile)
1087 | "Prepare ZDFILE for export.
1088 | Copy its contents to `zetteldeftd-export-tmp-dir' and replace links with Org
1089 | file links. ZDFILE should be the path to the file."
1090 | (with-temp-file (expand-file-name
1091 | (file-name-nondirectory zdFile)
1092 | zetteldeft-export-tmp-dir)
1093 | (insert-file-contents zdFile)
1094 | (while (re-search-forward (zetteldeft--link-regex) nil t)
1095 | (let ((zdLink (match-string 0)))
1096 | (delete-region (point)
1097 | (re-search-backward (zetteldeft--link-regex)))
1098 | (let ((filePath (or (zetteldeft--id-to-full-path
1099 | (zetteldeft--lift-id zdLink))
1100 | ; When ID doesn't return a file (a dead link)
1101 | ; use empty string
1102 | "")))
1103 | (insert
1104 | (org-make-link-string
1105 | (format "./%s" (file-name-nondirectory filePath))
1106 | zdLink)))))))
1107 |
1108 | ;;;###autoload
1109 | (defun zetteldeft-org-search-include (zdSrch)
1110 | "Insert `org-mode' syntax to include all files containing ZDSRCH.
1111 | Prompt for search string when called interactively."
1112 | (interactive (list (read-string "tag (include the #): ")))
1113 | (dolist (zdFile (zetteldeft--get-file-list zdSrch))
1114 | (zetteldeft--org-include-file zdFile)))
1115 |
1116 | ;;;###autoload
1117 | (defun zetteldeft-org-search-insert (zdSrch)
1118 | "Insert the contents of all files containing ZDSRCH.
1119 | Files are separated by `org-mode' headers with corresponding titles.
1120 | Prompt for search string when called interactively."
1121 | (interactive (list (read-string "Search term: ")))
1122 | (dolist (zdFile (zetteldeft--get-file-list zdSrch))
1123 | (zetteldeft--org-insert-file zdFile)))
1124 |
1125 | (defun zetteldeft--file-contents (zdFile &optional removeLines)
1126 | "Insert file contents of a zetteldeft note.
1127 | ZDFILE should be a full path to a note.
1128 |
1129 | Optional: leave out first REMOVELINES lines."
1130 | (with-temp-buffer
1131 | (insert-file-contents zdFile)
1132 | (when removeLines
1133 | (kill-whole-line removeLines))
1134 | (buffer-string)))
1135 |
1136 | (defun zetteldeft--org-include-file (zdFile)
1137 | "Insert code to include org file ZDFILE."
1138 | (insert
1139 | ;; Insert org-mode title
1140 | "* " (zetteldeft--lift-file-title zdFile) "\n"
1141 | ;; Insert #+INCLUDE: "file.org" :lines 2-
1142 | "#+INCLUDE: \"" zdFile "\" :lines \"2-\"\n\n"))
1143 |
1144 | (defun zetteldeft--org-insert-file (zdFile)
1145 | "Insert title and contents of ZDFILE."
1146 | (insert
1147 | ;; Insert org-mode title
1148 | "\n* " (zetteldeft--lift-file-title zdFile) "\n\n"
1149 | ;; Insert file contents (without the first 3 lines)
1150 | (zetteldeft--file-contents zdFile 3)))
1151 |
1152 | (defcustom zetteldeft-graph-syntax-begin
1153 | "#+BEGIN_SRC dot :file ./graph.pdf :cmdline -Kfdp -Tpdf
1154 | \n graph {\n"
1155 | "Syntax to be included at the start of the zetteldeft graph."
1156 | :type 'string
1157 | :group 'zetteldeft)
1158 |
1159 | (defcustom zetteldeft-graph-syntax-end
1160 | "} \n#+END_SRC\n"
1161 | "Syntax to be included at the end of the zetteldeft graph."
1162 | :type 'string
1163 | :group 'zetteldeft)
1164 |
1165 | (defvar zetteldeft--graph-links)
1166 |
1167 | ;;;###autoload
1168 | (defun zetteldeft-org-graph-search (str)
1169 | "Insert org source block for graph with zd search results.
1170 | STR should be the search the resulting notes of which should be included in the graph."
1171 | (interactive (list (read-string "search string: ")))
1172 | (setq zetteldeft--graph-links (list))
1173 | (let ((zdList (zetteldeft--get-file-list str)))
1174 | (insert zetteldeft-graph-syntax-begin)
1175 | (insert "\n // links\n")
1176 | (dolist (oneFile zdList)
1177 | (insert "\n")
1178 | (zetteldeft--graph-insert-links oneFile))
1179 | (zetteldeft--graph-insert-all-titles))
1180 | (insert zetteldeft-graph-syntax-end))
1181 |
1182 | ;;;###autoload
1183 | (defun zetteldeft-org-graph-note (deftFile)
1184 | "Create a graph starting from note DEFTFILE."
1185 | (interactive (list
1186 | (completing-read "Note to start graph from: "
1187 | (deft-find-all-files))))
1188 | (setq zetteldeft--graph-links (list))
1189 | (insert zetteldeft-graph-syntax-begin)
1190 | (insert "\n // base note and links \n")
1191 | (zetteldeft--graph-insert-links deftFile)
1192 | (zetteldeft--graph-insert-additional-links)
1193 | (zetteldeft--graph-insert-all-titles)
1194 | (insert zetteldeft-graph-syntax-end))
1195 |
1196 | (defun zetteldeft--graph-insert-links (deftFile)
1197 | "Insert links in DEFTFILE in dot graph syntax on a single line.
1198 | Any inserted ID is also stored in `zetteldeft--graph-links'."
1199 | (let ((zdId (zetteldeft--lift-id deftFile)))
1200 | (when zdId
1201 | (insert " \"" zdId "\" -- {")
1202 | (dolist (oneLink (zetteldeft--extract-links deftFile))
1203 | (zetteldeft--graph-store-link oneLink t)
1204 | (insert "\"" oneLink "\" "))
1205 | (insert "}\n")
1206 | (zetteldeft--graph-store-link deftFile))))
1207 |
1208 | (defun zetteldeft--graph-insert-title (deftFile)
1209 | "Insert the DEFTFILE title definition in a one line dot graph format."
1210 | (let ((zdTitle
1211 | (replace-regexp-in-string "\"" ""
1212 | (zetteldeft--lift-file-title deftFile)))
1213 | (zdId (zetteldeft--lift-id deftFile)))
1214 | (when zdId
1215 | (insert " \"" zdId "\""
1216 | " [label = \"" zdTitle " ("
1217 | zetteldeft-link-indicator zdId zetteldeft-link-suffix ")\"")
1218 | (insert "]" "\n"))
1219 | (zetteldeft--graph-store-link deftFile)))
1220 |
1221 | (defun zetteldeft--graph-store-link (deftFile &optional idToFile)
1222 | "Push DEFTFILE to zetteldeft--graph-links unless it's already there.
1223 | When IDTOFILE is non-nil, DEFTFILE is considered an id
1224 | and the the function first looks for the corresponding file."
1225 | (when idToFile
1226 | (let ((deft-filter-only-filenames t))
1227 | (progn
1228 | (deft-filter deftFile t)
1229 | (setq deftFile (car deft-current-files)))))
1230 | (unless (member deftFile zetteldeft--graph-links)
1231 | (push deftFile zetteldeft--graph-links)))
1232 |
1233 | (defun zetteldeft--graph-insert-additional-links ()
1234 | "Insert rest of `zetteldeft--graph-links'."
1235 | (setq zetteldeft--graph-links (cdr zetteldeft--graph-links))
1236 | (dolist (oneFile zetteldeft--graph-links)
1237 | (zetteldeft--graph-insert-links oneFile)))
1238 |
1239 | (defun zetteldeft--graph-insert-all-titles ()
1240 | "Insert graphviz title lines.
1241 | Does this for all links stored in `zetteldeft--graph-links'."
1242 | (insert "\n // titles \n")
1243 | (dolist (oneLink zetteldeft--graph-links)
1244 | ;; Sometimes, a 'nil' list item is present. Ignore those.
1245 | (when oneLink
1246 | (zetteldeft--graph-insert-title oneLink))))
1247 |
1248 | ;;;###autoload
1249 | (defun zetteldeft-set-classic-keybindings ()
1250 | "Sets global keybindings for `zetteldeft'."
1251 | (interactive)
1252 | (define-prefix-command 'zetteldeft-prefix)
1253 | (global-set-key (kbd "C-c d") 'zetteldeft-prefix)
1254 | (global-set-key (kbd "C-c d d") 'deft)
1255 | (global-set-key (kbd "C-c d D") 'zetteldeft-deft-new-search)
1256 | (global-set-key (kbd "C-c d R") 'deft-refresh)
1257 | (global-set-key (kbd "C-c d s") 'zetteldeft-search-at-point)
1258 | (global-set-key (kbd "C-c d c") 'zetteldeft-search-current-id)
1259 | (global-set-key (kbd "C-c d f") 'zetteldeft-follow-link)
1260 | (global-set-key (kbd "C-c d F") 'zetteldeft-avy-file-search-ace-window)
1261 | (global-set-key (kbd "C-c d .") 'zetteldeft-browse)
1262 | (global-set-key (kbd "C-c d h") 'zetteldeft-go-home)
1263 | (global-set-key (kbd "C-c d l") 'zetteldeft-avy-link-search)
1264 | (global-set-key (kbd "C-c d t") 'zetteldeft-avy-tag-search)
1265 | (global-set-key (kbd "C-c d T") 'zetteldeft-tag-buffer)
1266 | (global-set-key (kbd "C-c d /") 'zetteldeft-search-tag)
1267 | (global-set-key (kbd "C-c d i") 'zetteldeft-find-file-id-insert)
1268 | (global-set-key (kbd "C-c d C-i") 'zetteldeft-full-search-id-insert)
1269 | (global-set-key (kbd "C-c d I") 'zetteldeft-find-file-full-title-insert)
1270 | (global-set-key (kbd "C-c d C-I") 'zetteldeft-full-search-full-title-insert)
1271 | (global-set-key (kbd "C-c d o") 'zetteldeft-find-file)
1272 | (global-set-key (kbd "C-c d C-o") 'zetteldeft-full-search-find-file)
1273 | (global-set-key (kbd "C-c d n") 'zetteldeft-new-file)
1274 | (global-set-key (kbd "C-c d N") 'zetteldeft-new-file-and-link)
1275 | (global-set-key (kbd "C-c d B") 'zetteldeft-new-file-and-backlink)
1276 | (global-set-key (kbd "C-c d b") 'zetteldeft-backlink-add)
1277 | (global-set-key (kbd "C-c d r") 'zetteldeft-file-rename)
1278 | (global-set-key (kbd "C-c d x") 'zetteldeft-count-words)
1279 | (global-set-key (kbd "C-c d #") 'zetteldeft-tag-insert)
1280 | (global-set-key (kbd "C-c d $") 'zetteldeft-tag-remove))
1281 |
1282 | (provide 'zetteldeft)
1283 | ;;; zetteldeft.el ends here
1284 |
--------------------------------------------------------------------------------