deft.el 69.2 KB
Newer Older
Jason Blevins's avatar
Jason Blevins committed
1 2
;;; deft.el --- quickly browse, filter, and edit plain text notes

Jason Blevins's avatar
Jason Blevins committed
3
;;; Copyright (C) 2011-2017 Jason R. Blevins <jblevins@xbeta.org>
Jason Blevins's avatar
Jason Blevins committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
;; All rights reserved.

;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions are met:
;; 1. Redistributions of source code must retain the above copyright
;;    notice, this list of conditions and the following disclaimer.
;; 2. Redistributions in binary form must reproduce the above copyright
;;    notice, this list of conditions and the following disclaimer in the
;;    documentation  and/or other materials provided with the distribution.
;; 3. Neither the names of the copyright holders nor the names of any
;;    contributors may be used to endorse or promote products derived from
;;    this software without specific prior written permission.

;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
;; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
;; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
;; POSSIBILITY OF SUCH DAMAGE.
Jason Blevins's avatar
Jason Blevins committed
28

Jason Blevins's avatar
Jason Blevins committed
29
;;; Version: 0.8
Jason Blevins's avatar
Jason Blevins committed
30
;;; Author: Jason R. Blevins <jrblevin@xbeta.org>
31
;;; Keywords: plain text, notes, Simplenote, Notational Velocity
Jason Blevins's avatar
Jason Blevins committed
32
;;; URL: https://jblevins.org/projects/deft/
Jason Blevins's avatar
Jason Blevins committed
33

Jason Blevins's avatar
Jason Blevins committed
34 35
;; This file is not part of GNU Emacs.

Jason Blevins's avatar
Jason Blevins committed
36 37
;;; Commentary:

Jason Blevins's avatar
Jason Blevins committed
38 39
;; Deft is an Emacs mode for quickly browsing, filtering, and editing
;; directories of plain text notes, inspired by Notational Velocity.
40 41 42 43
;; It was designed for increased productivity when writing and taking
;; notes by making it fast and simple to find the right file at the
;; right time and by automating many of the usual tasks such as
;; creating new files and saving files.
Jason Blevins's avatar
Jason Blevins committed
44

Jason Blevins's avatar
Jason Blevins committed
45
;; ![Deft Screencast](https://jblevins.org/projects/deft/deft-v0.6.gif)
46 47 48 49

;; Obtaining Deft
;; --------------

50
;; Deft is open source software and may be freely distributed and
51
;; modified under the BSD license.  The latest stable release is
Jason Blevins's avatar
Jason Blevins committed
52
;; version 0.8, released on January 12, 2018.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

;; **Installation via MELPA Stable**

;; The recommended way to install Deft is to obtain the stable version
;; from [MELPA Stable](https://stable.melpa.org/#/deft) using
;; `package.el'.  First, configure `package.el' and the MELPA Stable
;; repository by adding the following to your `.emacs', `init.el', or
;; equivalent startup file:

;;     (require 'package)
;;     (add-to-list 'package-archives
;;                  '("melpa-stable" . "https://stable.melpa.org/packages/"))
;;     (package-initialize)

;; Then, after restarting Emacs or evaluating the above statements, issue
;; the following command: `M-x package-install RET deft RET`.

;; [MELPA Stable]: http://stable.melpa.org/

;; **Direct Download**

;; Alternatively you can manually download and install Deft.
;; First, download the latest stable version of and save the file
;; where Emacs can find it---a directory in your `load-path':
77

Jason Blevins's avatar
Jason Blevins committed
78
;;   * [deft.el](https://jblevins.org/projects/deft/deft.el)
79

80
;; Then, add the following line to your startup file:
81

82
;;     (require 'deft)
Jason Blevins's avatar
Jason Blevins committed
83

84
;; **Development Version**
Jason Blevins's avatar
Jason Blevins committed
85

86 87
;; To follow or contribute to Deft development, you can browse or
;; clone the Git repository [on GitHub](https://github.com/jrblevin/deft):
88

89 90
;;     git clone https://github.com/jrblevin/deft.git

91 92 93 94 95 96 97 98 99 100
;; If you prefer to install and use the development version, which may
;; become unstable at some times, you can either clone the Git
;; repository as above or install Deft from
;; [MELPA](https://melpa.org/#/deft).

;; If you clone the repository directly, then make sure that Emacs can
;; find it by adding the following line to your startup file:

;;     (add-to-list 'load-path "/path/to/deft/repository")

101 102
;; Overview
;; --------
103

104 105 106 107
;; The Deft buffer is simply a file browser which lists the titles of
;; all text files in the Deft directory followed by short summaries
;; and last modified times.  The title is taken to be the first line
;; of the file and the summary is extracted from the text that
108 109
;; follows.  Files are, by default, sorted in terms of the last
;; modified date, from newest to oldest.
Jason Blevins's avatar
Jason Blevins committed
110

111 112 113 114 115 116
;; All Deft files or notes are simple plain text files where the first
;; line contains a title.  As an example, the following directory
;; structure generated the screenshot above.
;;
;;     % ls ~/.deft
;;     about.txt    browser.txt     directory.txt   operations.txt
Jason Blevins's avatar
Jason Blevins committed
117
;;     ack.txt      completion.txt  extensions.org
118
;;     binding.txt  creation.txt    filtering.txt
119
;;
120
;;     % cat ~/.deft/about.txt
Jason Blevins's avatar
Jason Blevins committed
121
;;     # About
122
;;
123 124
;;     An Emacs mode for slicing and dicing plain text files.

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
;; Deft's primary operation is searching and filtering.  The list of
;; files can be limited or filtered using a search string, which will
;; match both the title and the body text.  To initiate a filter,
;; simply start typing.  Filtering happens on the fly.  As you type,
;; the file browser is updated to include only files that match the
;; current string.

;; To open the first matching file, simply press `RET`.  If no files
;; match your search string, pressing `RET` will create a new file
;; using the string as the title.  This is a very fast way to start
;; writing new notes.  The filename will be generated automatically.
;; If you prefer to provide a specific filename, use `C-RET` instead.

;; To open files other than the first match, navigate up and down
;; using `C-p` and `C-n` and press `RET` on the file you want to open.
140 141
;; When opening a file, Deft searches forward and leaves the point
;; at the end of the first match of the filter string.
142

143 144 145 146 147
;; You can also press `C-o` to open a file in another window, without
;; switching to the other window.  Issue the same command with a prefix
;; argument, `C-u C-o`, to open the file in another window and switch
;; to that window.

148
;; To edit the filter string, press `DEL` (backspace) to remove the
149 150
;; last character or `M-DEL` to remove the last "word".  To yank
;; (paste) the most recently killed (cut or copied) text into the
151 152 153 154 155 156 157 158 159 160 161
;; filter string, press `C-y`.  Press `C-c C-c` to clear the filter
;; string and display all files and `C-c C-g` to refresh the file
;; browser using the current filter string.

;; For more advanced editing operations, you can also edit the filter
;; string in the minibuffer by pressing `C-c C-l`.  While in the
;; minibuffer, the history of previous edits can be cycled through by
;; pressing `M-p` and `M-n`.  This form of static, one-time filtering
;; (as opposed to incremental, on-the-fly filtering) may be preferable
;; in some situations, such as over slow connections or on systems
;; where interactive filtering performance is poor.
Jason Blevins's avatar
Jason Blevins committed
162

163 164 165
;; By default, Deft filters files in incremental string search mode,
;; where "search string" will match all files containing both "search"
;; and "string" in any order.  Alternatively, Deft supports direct
166 167 168 169 170
;; regexp filtering, where the filter string is interpreted as a
;; formal regular expression.  For example, `^\(foo\|bar\)` matches
;; foo or bar at the beginning of a line.  Pressing `C-c C-t` will
;; toggle between incremental and regexp search modes.  Regexp
;; search mode is indicated by an "R" in the mode line.
171

172 173 174 175 176
;; Common file operations can also be carried out from within Deft.
;; Files can be renamed using `C-c C-r` or deleted using `C-c C-d`.
;; New files can also be created using `C-c C-n` for quick creation or
;; `C-c C-m` for a filename prompt.  You can leave Deft at any time
;; with `C-c C-q`.
Jason Blevins's avatar
Jason Blevins committed
177

Jason Blevins's avatar
Jason Blevins committed
178
;; Unused files can be archived by pressing `C-c C-a`.  Files will be
Jason Blevins's avatar
Jason Blevins committed
179 180
;; moved to `deft-archive-directory', which is a directory named
;; `archive` within your `deft-directory' by default.
Jason Blevins's avatar
Jason Blevins committed
181

Jason Blevins's avatar
Jason Blevins committed
182 183 184 185
;; Files opened with deft are automatically saved after Emacs has been
;; idle for a customizable number of seconds.  This value is a floating
;; point number given by `deft-auto-save-interval' (default: 1.0).

186 187 188
;; Getting Started
;; ---------------

189 190 191
;; Once you have installed Deft following one of the above methods,
;; you can simply run `M-x deft` to start Deft.  It is useful
;; to create a global keybinding for the `deft' function (e.g., a
192 193 194 195 196 197
;; function key) to start it quickly (see below for details).

;; When you first run Deft, it will complain that it cannot find the
;; `~/.deft` directory.  You can either create a symbolic link to
;; another directory where you keep your notes or run `M-x deft-setup`
;; to create the `~/.deft` directory automatically.
198 199 200

;; One useful way to use Deft is to keep a directory of notes in a
;; Dropbox folder.  This can be used with other applications and
Jason Blevins's avatar
Jason Blevins committed
201 202 203
;; mobile devices, for example, [nvALT][], [Notational Velocity][], or
;; [Simplenote][] on OS X or [Editorial][], [Byword][], or [1Writer][]
;; on iOS.
204

Jason Blevins's avatar
Jason Blevins committed
205 206 207 208 209 210 211 212 213
;; [nvALT]: http://brettterpstra.com/projects/nvalt/
;; [Notational Velocity]: http://notational.net/
;; [Simplenote]: http://simplenote.com/
;; [Editorial]: https://geo.itunes.apple.com/us/app/editorial/id673907758?mt=8&uo=6&at=11l5Vs&ct=deft
;; [Byword]: https://geo.itunes.apple.com/us/app/byword/id482063361?mt=8&uo=6&at=11l5Vs&ct=deft
;; [1Writer]: https://geo.itunes.apple.com/us/app/1writer-note-taking-writing/id680469088?mt=8&uo=6&at=11l5Vs&ct=deft

;; Basic Customization
;; -------------------
Jason Blevins's avatar
Jason Blevins committed
214

Jason Blevins's avatar
Jason Blevins committed
215 216
;; You can customize items in the `deft` group to change the default
;; functionality.
Jason Blevins's avatar
Jason Blevins committed
217 218

;; By default, Deft looks for notes by searching for files with the
Jason Blevins's avatar
Jason Blevins committed
219
;; extensions `.txt`, `.text`, `.md`, `.markdown`, or `.org` in the
220 221 222 223
;; `~/.deft` directory.  You can customize both the file extension and
;; the Deft directory by running `M-x customize-group` and typing
;; `deft`.  Alternatively, you can configure them in your `.emacs`
;; file:
224

225
;;     (setq deft-extensions '("txt" "tex" "org"))
226
;;     (setq deft-directory "~/Dropbox/notes")
227

228 229
;; The first element of `deft-extensions' (or in Lisp parlance, the
;; car) is the default extension used to create new files.
230

231
;; By default, Deft only searches for files in `deft-directory' but
Jason Blevins's avatar
Jason Blevins committed
232
;; not in any subdirectories.  All files in `deft-directory' with one
233
;; of the specified extensions will be included except for those
Jason Blevins's avatar
Jason Blevins committed
234
;; matching `deft-ignore-file-regexp'.  Set `deft-recursive' to a
235 236
;; non-nil value to enable searching for files in subdirectories
;; (those not matching `deft-recursive-ignore-dir-regexp'):
237 238 239

;;     (setq deft-recursive t)

Jason Blevins's avatar
Jason Blevins committed
240 241 242 243 244 245
;; You can easily set up a global keyboard binding for Deft.  For
;; example, to bind it to F8, add the following code to your `.emacs`
;; file:

;;     (global-set-key [f8] 'deft)

246 247 248 249 250 251 252 253 254 255 256 257
;; If you manage loading packages with [use-package][], then you can
;; configure by adding a declaration such as this one to your init
;; file:

;;     (use-package deft
;;       :bind ("<f8>" . deft)
;;       :commands (deft)
;;       :config (setq deft-directory "~/Dropbox/notes"
;;                     deft-extensions '("md" "org")))

;; [use-package]: https://github.com/jwiegley/use-package

Jason Blevins's avatar
Jason Blevins committed
258 259 260 261
;; Reading Files
;; -------------

;; The displayed title of each file is taken to be the first line of
Jason Blevins's avatar
Jason Blevins committed
262
;; the file, with certain characters removed from the beginning.  Hash
Jason Blevins's avatar
Jason Blevins committed
263 264
;; characters, as used in Markdown headers, and asterisks, as in Org
;; Mode headers, are removed.  Additionally, Org mode `#+TITLE:` tags,
265
;; MultiMarkdown `Title:` tags, LaTeX comment markers, and
Jason Blevins's avatar
Jason Blevins committed
266 267 268 269 270 271 272 273 274 275 276 277 278
;; Emacs mode-line declarations (e.g., `-*-mode-*-`) are stripped from
;; displayed titles.  This can be customized by changing
;; `deft-strip-title-regexp'.

;; More generally, the title post-processing function itself can be
;; customized by setting `deft-parse-title-function', which accepts
;; the first line of the file as an argument and returns the parsed
;; title to display in the file browser.  The default function is
;; `deft-strip-title', which removes all occurrences of
;; `deft-strip-title-regexp' as described above.

;; For compatibility with other applications which use the filename as
;; the title of a note (rather than the first line of the file), set the
Jason Blevins's avatar
Jason Blevins committed
279
;; `deft-use-filename-as-title' flag to a non-`nil' value.  Deft will then
Jason Blevins's avatar
Jason Blevins committed
280
;; use note filenames to generate the displayed titles in the Deft
Jason Blevins's avatar
Jason Blevins committed
281
;; file browser.  To enable this, add the following to your `.emacs` file:
Jason Blevins's avatar
Jason Blevins committed
282 283 284

;;     (setq deft-use-filename-as-title t)

285
;; Finally, the short summary that is displayed following the file
Jason Blevins's avatar
Jason Blevins committed
286
;; title can be customized by changing `deft-strip-summary-regexp'.  By
287 288 289
;; default, this is set to remove certain org-mode metadata statements
;; such as `#+OPTIONS:` and `#+AUTHOR:'.

Jason Blevins's avatar
Jason Blevins committed
290 291 292 293 294 295 296 297 298 299 300
;; Creating Files
;; --------------

;; Filenames for newly created files are generated by Deft automatically.
;; The process for doing so is determined by the variables
;; `deft-use-filename-as-title' and `deft-use-filter-string-for-filename'
;; as well as the rules in the `deft-file-naming-rules' alist.
;; The possible cases are as follows:

;; 1.  **Default** (`deft-use-filename-as-title' and
;;     `deft-use-filter-string-for-filename' are both `nil'):
301 302 303 304 305 306 307 308 309
;;
;;     The filename will be automatically generated using an short,
;;     ISO-like timestamp as in `2016-05-12T09:00.txt'.  The format
;;     can be customized by setting the variable
;;     `deft-new-file-format'.  The filter string will be inserted as
;;     the first line of the file (which is also used as the display
;;     title).  In case of file name conflicts, an underscore and a
;;     numerical suffix (e.g., `_2') will be appended before the
;;     extension.
Jason Blevins's avatar
Jason Blevins committed
310 311 312 313 314

;; 2.  **Filenames as titles** (`deft-use-filename-as-title' is non-`nil'):

;;     When `deft-use-filename-as-title' is non-`nil', the filter string
;;     will be used as the filename for new files (with the appropriate
315 316 317 318 319 320
;;     file extension appended to the end).  An example of new file creation
;;     in this case:

;;       * Filter string: "My New Project"
;;       * File name: "My New Project.txt"
;;       * File contents: [empty]
Jason Blevins's avatar
Jason Blevins committed
321 322 323 324 325 326

;; 3.  **Readable filenames** (`deft-use-filename-as-title' is
;;     `nil' but `deft-use-filter-string-for-filename' is non-`nil'):

;;     In this case you can choose to display the title as parsed from
;;     the first line of the file while also generating readable
Jason Blevins's avatar
Jason Blevins committed
327
;;     filenames for new files based on the filter string.  The
Jason Blevins's avatar
Jason Blevins committed
328 329
;;     variable `deft-use-filter-string-for-filename' controls this
;;     behavior and decouples the title display
Jason Blevins's avatar
Jason Blevins committed
330
;;     (`deft-use-filename-as-title') from the actual filename.  New
Jason Blevins's avatar
Jason Blevins committed
331 332
;;     filenames will be generated from the filter string and
;;     processed according to the rules defined in the
Jason Blevins's avatar
Jason Blevins committed
333
;;     `deft-file-naming-rules' alist.  By default, slashes are removed
Jason Blevins's avatar
Jason Blevins committed
334
;;     and replaced by hyphens, but many other options are possible
Jason Blevins's avatar
Jason Blevins committed
335
;;     (camel case, replacing spaces by hyphens, and so on).  See the
Jason Blevins's avatar
Jason Blevins committed
336 337 338
;;     documentation for `deft-file-naming-rules' for additional
;;     details.

339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
;;     As an example, with the following value for
;;     `deft-file-naming-rules', Deft will replace all slashes and
;;     spaces with hyphens and will convert the file name to
;;     lowercase:

;;         (setq deft-file-naming-rules
;;               '((noslash . "-")
;;                 (nospace . "-")
;;                 (case-fn . downcase)))

;;     Below is an example in this case, with the above file naming
;;     rules.  Notice that the filter string is inserted as the first
;;     line of the file but it is also used to generate a "readable"
;;     file name.

;;       * Filter string: "My New Project"
;;       * File name: "my-new-project.txt"
;;       * File contents: "My New Project"

Jason Blevins's avatar
Jason Blevins committed
358 359 360 361 362 363
;; Titles inserted into files from the filter string can also be
;; customized for two common modes, `markdown-mode' and `org-mode', by
;; setting the following variables:

;; * `deft-markdown-mode-title-level' - When set to a positive
;;   integer, determines how many hash marks will be added to titles
Jason Blevins's avatar
Jason Blevins committed
364
;;   in new Markdown files.  In other words, setting
Jason Blevins's avatar
Jason Blevins committed
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
;;   `deft-markdown-mode-title-level' to `2` will result in new files
;;   being created with level-2 headings of the form `## Title`.

;; * `deft-org-mode-title-prefix' - When non-nil, automatically
;;   generated titles in new `org-mode' files will be prefixed with
;;   `#+TITLE:`.

;; Other Customizations
;; --------------------

;; Deft, by default, lists files from newest to oldest.  You can set
;; `deft-current-sort-method' to 'title to sort by file titles, case
;; ignored.  Or, you can toggle sorting method using
;; `deft-toggle-sort-method'.

;; Incremental string search is the default method of filtering on
;; startup, but you can set `deft-incremental-search' to nil to make
;; regexp search the default.

384 385
;; Deft also provides a function for opening files without using the
;; Deft buffer directly.  Calling `deft-find-file' will prompt for a
386 387 388 389 390
;; file to open, much like `find-file', but limits consideration to
;; files in `deft-directory' that are known to Deft (i.e., those files
;; matching `deft-extensions`).  Unlike `find-file`, a list of all
;; such files is provided and the desired file name can be completed
;; using `completing-read' (and, as a result, `deft-find-file` will
Jason Blevins's avatar
Jason Blevins committed
391
;; read/complete filenames using ido, helm, etc.  when enabled).  If
392 393 394 395
;; the selected file is in `deft-directory', it is opened with the
;; usual Deft features (automatic saving, automatic updating of the
;; Deft buffer, etc.).  Otherwise, the file will be opened by
;; `find-file' as usual.  Therefore, you can set up a global
396 397 398
;; keybinding for this function to open Deft files anywhere.  For
;; example, to use `C-x C-g`, a neighbor of `C-x C-f`, use the
;; following:
399 400 401

;;     (global-set-key (kbd "C-x C-g") 'deft-find-file)

402 403 404 405 406
;; The faces used for highlighting various parts of the screen can
;; also be customized.  By default, these faces inherit their
;; properties from the standard font-lock faces defined by your current
;; color theme.

407 408 409 410
;; Deft also provides several hooks: `deft-mode-hook',
;; `deft-filter-hook', and `deft-open-file-hook'.  See the
;; documentation for these variables for further details.

411 412
;; Acknowledgments
;; ---------------
Jason Blevins's avatar
Jason Blevins committed
413

Jason Blevins's avatar
Jason Blevins committed
414
;; Thanks to Konstantinos Efstathiou for writing simplenote.el, from
Jason Blevins's avatar
Jason Blevins committed
415
;; which I borrowed liberally, and to Zachary Schneirov for writing
Jason Blevins's avatar
Jason Blevins committed
416 417
;; Notational Velocity, whose functionality and spirit I wanted to
;; bring to Emacs.
Jason Blevins's avatar
Jason Blevins committed
418 419 420 421

;; History
;; -------

Jason Blevins's avatar
Jason Blevins committed
422
;; Version 0.8 (2018-01-12):
Jason Blevins's avatar
Jason Blevins committed
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443

;; * Limit `deft-find-file' to files known to Deft and support
;;   completing-read.
;; * Keep subdirectory portion when displaying filenames.
;; * New variable `deft-width-offset' for custom summary line width
;;   offset.
;; * Attempt to restore point after refreshing browser and preserve
;;   position while filtering.
;; * Add hooks: `deft-filter-hook' for filter string changes and
;;   `deft-open-file-hook' which runs after opening a file.
;; * Prevent spurious Deft browser refreshes, which fixes an issue
;;   with `sublimity-mode'.
;; * More reliable browser updates when window size changes.
;; * Only update width when buffer is visible.
;; * Lazily update the Deft buffer after saving files.
;; * Close open buffer when deleting a file.
;; * Initialize width even when started in background.
;; * Omit files generated from org or markdown.
;; * Custom format string `deft-new-file-format' for new file names.
;; * Reduce summary line width when there is no fringe.
;; * Support Org links.
Jason Blevins's avatar
Jason Blevins committed
444
;; * Option `deft-filter-only-filenames' to filter only on file names.
Jason Blevins's avatar
Jason Blevins committed
445

Jason Blevins's avatar
Jason Blevins committed
446 447 448
;; Version 0.7 (2015-12-21):

;; * Add custom regular expression `deft-strip-summary-regexp' for
Jason Blevins's avatar
Jason Blevins committed
449
;;   stripping extraneous text for generating the summary line.  Strip
Jason Blevins's avatar
Jason Blevins committed
450 451
;;   all `org-mode' metadata by default.
;; * New customizable regular expressions for ignoring files and
Jason Blevins's avatar
Jason Blevins committed
452
;;   directories.  See `deft-recursive-ignore-dir-regexp' and
Jason Blevins's avatar
Jason Blevins committed
453 454 455 456 457 458 459 460 461 462
;;   `deft-ignore-file-regexp'.
;; * Bug fix: Prevent lines from wrapping in console mode.
;; * Bug fix: Setup `deft-extensions` and `deft-default-extension` at
;;   load time.
;; * Bug fix: Try to prevent false title matches in org-mode notes
;;   where the string `#+TITLE:` might also appear in the body.
;; * Bug fix: Use `with-current-buffer` instead of `save-excursion`
;;   while auto-saving files since we do not want to save the point.
;; * Bug fix: Don't escape quotes in `deft-file-naming-rules'.

Jason Blevins's avatar
Jason Blevins committed
463 464
;; Version 0.6 (2015-06-26):

Jason Blevins's avatar
Jason Blevins committed
465
;; * Recursive search in subdirectories (optional).  Set
Jason Blevins's avatar
Jason Blevins committed
466 467 468 469
;;   `deft-recursive' to a non-nil value to enable.
;; * Support for multiple extensions via the `deft-extensions' list.
;;   As such, `deft-extension' is now deprecated.
;; * New variable `deft-create-file-from-filter-string' can enable
Jason Blevins's avatar
Jason Blevins committed
470
;;   generation of new filenames based on the filter string.  This decouples
Jason Blevins's avatar
Jason Blevins committed
471 472 473 474 475 476 477 478 479 480 481 482
;;   the title display (`deft-use-filename-as-title') from the actual filename
;;   generation.
;; * New variable `deft-file-naming-rules' allows customizing generation
;;   of filenames with regard to letter case and handling of spaces.
;; * New variables `deft-markdown-mode-title-level' and
;;   `deft-org-mode-title-prefix' for automatic insertion of title markup.
;; * Archiving of files in `deft-archive-directory'.
;; * Ability to sort by either title or modification time via
;;   `deft-current-sort-method'.
;; * Update default `deft-strip-title-regexp' to remove the following:
;;     - org-mode `#+TITLE:` tags
;;     - MultiMarkdown `Title:` tags
483
;;     - LaTeX comment markers
Jason Blevins's avatar
Jason Blevins committed
484 485 486 487 488 489 490 491 492 493 494 495
;;     - Emacs mode-line declarations (e.g., `-*-mode-*-`)
;; * Remove leading and trailing whitespace from titles.
;; * Disable visual line mode to prevent lines from wrapping.
;; * Enable line truncation to avoid displaying truncation characters.
;; * Show the old filename as the default prompt when renaming a file.
;; * Call `hack-local-variables' to read file-local variables when
;;   opening files.
;; * Fixed several byte-compilation warnings.
;; * Bug fix: more robust handling of relative and absolute filenames.
;; * Bug fix: use width instead of length of strings for calculations.
;; * Bug fix: fix `string-width' error with empty file.

Jason Blevins's avatar
Jason Blevins committed
496 497
;; Version 0.5.1 (2013-01-28):

498
;; * Bug fix: creating files with `C-c C-n` when both the filter string and
Jason Blevins's avatar
Jason Blevins committed
499 500 501
;;   `deft-use-filename-as-title' are non-nil resulted in an invalid path.
;; * Bug fix: killed buffers would persist in `deft-auto-save-buffers'.

Jason Blevins's avatar
Jason Blevins committed
502 503
;; Version 0.5 (2013-01-25):

Jason Blevins's avatar
Jason Blevins committed
504
;; * Implement incremental string search (default) and regexp search.
Jason Blevins's avatar
Jason Blevins committed
505 506 507 508 509 510 511 512 513 514 515 516 517 518
;;   These search modes can be toggled by pressing `C-c C-t`.
;; * Default search method can be changed by setting `deft-incremental-search'.
;; * Support custom `deft-parse-title-function' for post-processing titles.
;; * The default `deft-parse-title-function' simply strips occurrences of
;;   `deft-strip-title-regexp', which removes Markdown and Org headings.
;; * Open files in another window with `C-o`.  Prefix it with `C-u` to
;;   switch to the other window.
;; * For symbolic links, use modification time of taget for sorting.
;; * When opening files, move point to the end of the first match of
;;   the filter string.
;; * Improved filter editing: delete (`DEL`), delete word (`M-DEL`),
;;   and yank (`C-y`).
;; * Advanced filter editing in minibuffer (`C-c C-l`).

Jason Blevins's avatar
Jason Blevins committed
519 520 521 522 523 524
;; Version 0.4 (2011-12-11):

;; * Improved filtering performance.
;; * Optionally take title from filename instead of first line of the
;;   contents (see `deft-use-filename-as-title').
;; * Dynamically resize width to fit the entire window.
Jason Blevins's avatar
Jason Blevins committed
525
;; * Customizable time format (see `deft-time-format').
Jason Blevins's avatar
Jason Blevins committed
526 527
;; * Handle `deft-directory' properly with or without a trailing slash.

Jason Blevins's avatar
Jason Blevins committed
528 529 530 531
;; Version 0.3 (2011-09-11):

;; * Internationalization: support filtering with multibyte characters.

Jason Blevins's avatar
Jason Blevins committed
532
;; Version 0.2 (2011-08-22):
Jason Blevins's avatar
Jason Blevins committed
533

Jason Blevins's avatar
Jason Blevins committed
534 535 536 537
;; * Match filenames when filtering.
;; * Automatically save opened files (optional).
;; * Address some byte-compilation warnings.

Jason Blevins's avatar
Jason Blevins committed
538
;; Deft was originally written by [Jason Blevins](https://jblevins.org/).
Jason Blevins's avatar
Jason Blevins committed
539
;; The initial version, 0.1, was released on August 6, 2011.
Jason Blevins's avatar
Jason Blevins committed
540 541 542

;;; Code:

543
(require 'cl)
Jason Blevins's avatar
Jason Blevins committed
544
(require 'widget)
Ivan Kanis's avatar
Ivan Kanis committed
545
(require 'wid-edit)
Jason Blevins's avatar
Jason Blevins committed
546 547 548

;; Customization

549 550 551 552
(defgroup deft nil
  "Emacs Deft mode."
  :group 'local)

Jason Blevins's avatar
Jason Blevins committed
553 554 555 556 557 558
(defcustom deft-directory (expand-file-name "~/.deft/")
  "Deft directory."
  :type 'directory
  :safe 'stringp
  :group 'deft)

559 560 561 562 563 564
(make-obsolete-variable 'deft-extension 'deft-extensions "v0.6")

(defcustom deft-extensions
  (if (boundp 'deft-extension)
      (cons deft-extension '())
    '("txt" "text" "md" "markdown" "org"))
Jason Blevins's avatar
Jason Blevins committed
565 566 567 568
  "Files with these extensions will be listed.
The first element of the list is used as the default file
extension of newly created files, if `deft-default-extension' is
not set."
569
  :type '(repeat string)
Jason Blevins's avatar
Jason Blevins committed
570 571
  :group 'deft)

Jason Blevins's avatar
Jason Blevins committed
572 573 574 575 576 577
(defcustom deft-auto-save-interval 1.0
  "Idle time in seconds before automatically saving buffers opened by Deft.
Set to zero to disable."
  :type 'float
  :group 'deft)

578
(defcustom deft-time-format " %Y-%m-%d %H:%M"
Jason Blevins's avatar
Jason Blevins committed
579
  "Format string for modification times in the Deft browser.
580 581
Set to nil to hide."
  :type '(choice (string :tag "Time format")
Jason Blevins's avatar
Jason Blevins committed
582
                 (const :tag "Hide" nil))
583 584
  :group 'deft)

585 586 587 588 589 590 591 592 593
(defcustom deft-new-file-format "%Y-%m-%dT%H:%M"
  "Format string for new file names.
The default value yields a short ISO-like timestamp, as in
\"2016-05-12T09:00\".  To use a full ISO 8601 time stamp, for
example, set this variable to \"%FT%T%z\".  See
`format-time-string' for possible format controls."
  :type 'string
  :group 'deft)

594
(defcustom deft-use-filename-as-title nil
595 596 597 598
  "Use filename as title in the *Deft* buffer."
  :type 'boolean
  :group 'deft)

599
(defcustom deft-use-filter-string-for-filename nil
600 601 602 603
  "Use the filter string to generate name for the new file."
  :type 'boolean
  :group 'deft)

604 605 606 607 608
(defcustom deft-markdown-mode-title-level 0
  "Prefix titles in new Markdown files with required number of hash marks."
  :type 'integer
  :group 'deft)

609
(defcustom deft-org-mode-title-prefix t
Jason Blevins's avatar
Jason Blevins committed
610
  "Prefix the generated title in new `org-mode' files with #+TITLE:."
611
  :type 'boolean
612 613
  :group 'deft)

Simon Belak's avatar
Simon Belak committed
614
(defcustom deft-incremental-search t
615
  "Use incremental string search when non-nil and regexp search when nil.
616 617
During incremental string search, substrings separated by spaces are
treated as subfilters, each of which must match a file.  They need
618
not be adjacent and may appear in any order.  During regexp search, the
619
entire filter string is interpreted as a single regular expression."
Simon Belak's avatar
Simon Belak committed
620 621 622
  :type 'boolean
  :group 'deft)

623 624 625 626 627
(defcustom deft-recursive nil
  "Recursively search for files in subdirectories when non-nil."
  :type 'boolean
  :group 'deft)

628 629 630 631 632
(defcustom deft-recursive-ignore-dir-regexp
  (concat "\\(?:"
          "\\."
          "\\|\\.\\."
          "\\)$")
Jason Blevins's avatar
Jason Blevins committed
633 634 635
  "Regular expression for subdirectories to be ignored.
This variable is only effective when searching for files
recursively, that is, when `deft-recursive' is non-nil."
636 637 638 639 640 641 642 643 644 645 646 647 648
  :type 'regexp
  :safe 'stringp
  :group 'deft)

(defcustom deft-ignore-file-regexp
  (concat "\\(?:"
          "^$"
          "\\)")
  "Regular expression for files to be ignored."
  :type 'regexp
  :safe 'stringp
  :group 'deft)

649 650 651 652 653
(defcustom deft-parse-title-function 'deft-strip-title
  "Function for post-processing file titles."
  :type 'function
  :group 'deft)

654 655 656 657 658 659
(defcustom deft-strip-title-regexp
  (concat "\\(?:"
          "^%+" ; line beg with %
          "\\|^#\\+TITLE: *" ; org-mode title
          "\\|^[#* ]+" ; line beg with #, * and/or space
          "\\|-\\*-[[:alpha:]]+-\\*-" ; -*- .. -*- lines
Jason Blevins's avatar
Jason Blevins committed
660
          "\\|^Title:[\t ]*" ; MultiMarkdown metadata
661 662
          "\\|#+" ; line with just # chars
          "$\\)")
663 664 665 666 667
  "Regular expression to remove from file titles.
Presently, it removes leading LaTeX comment delimiters, leading
and trailing hash marks from Markdown ATX headings, leading
astersisks from Org Mode headings, and Emacs mode lines of the
form -*-mode-*-."
668 669 670 671
  :type 'regexp
  :safe 'stringp
  :group 'deft)

672 673 674
(defcustom deft-strip-summary-regexp
  (concat "\\("
           "[\n\t]" ;; blank
675
           "\\|^#\\+[[:upper:]_]+:.*$" ;; org-mode metadata
676
           "\\)")
677
   "Regular expression to remove file contents displayed in summary.
Jason Blevins's avatar
Jason Blevins committed
678
Presently removes blank lines and `org-mode' metadata statements."
679 680 681 682
   :type 'regexp
   :safe 'stringp
   :group 'deft)

683
(defcustom deft-archive-directory "archive/"
Jason Blevins's avatar
Jason Blevins committed
684
  "Deft archive directory.
685
This may be a relative path from `deft-directory', or an absolute path."
Jason Blevins's avatar
Jason Blevins committed
686 687 688 689
  :type 'directory
  :safe 'stringp
  :group 'deft)

690
(defcustom deft-file-naming-rules '( (noslash . "-") )
691 692
  "Alist of cons cells (SYMBOL . VALUE) for `deft-absolute-filename'.

693 694
Supported cons car values: `noslash', `nospace', `case-fn'.

Jason Blevins's avatar
Jason Blevins committed
695 696 697 698
Value of `slash' is a string which should replace the forward
slash characters in the file name.  The default behavior is to
replace slashes with hyphens in the file name.  To change the
replacement charcter to an underscore, one could use:
699 700

   (setq deft-file-naming-rules '((noslash . \"_\")))
701

Jason Blevins's avatar
Jason Blevins committed
702 703 704
Value of `nospace' is a string which should replace the space
characters in the file name.  Below example replaces spaces with
underscores in the file names:
705 706 707

   (setq deft-file-naming-rules '((nospace . \"_\")))

Jason Blevins's avatar
Jason Blevins committed
708 709 710
Value of `case-fn' is a function name that takes a string as
input that has to be applied on the file name.  Below example
makes the file name all lower case:
711 712 713

   (setq deft-file-naming-rules '((case-fn . downcase)))

Jason Blevins's avatar
Jason Blevins committed
714 715
It is also possible to use a combination of the above cons cells
to get file name in various case styles like,
716 717 718

snake_case:

719 720
    (setq deft-file-naming-rules '((noslash . \"_\")
                                   (nospace . \"_\")
721 722 723 724
                                   (case-fn . downcase)))

or CamelCase

725 726
    (setq deft-file-naming-rules '((noslash . \"\")
                                   (nospace . \"\")
727 728 729 730
                                   (case-fn . capitalize)))

or kebab-case

731 732
    (setq deft-file-naming-rules '((noslash . \"-\")
                                   (nospace . \"-\")
Jason Blevins's avatar
Jason Blevins committed
733
                                   (case-fn . downcase)))"
734
  :type '(alist :key-type symbol :value-type sexp)
735 736
  :group 'deft)

737
(defcustom deft-generation-rules '(("org" . "tex") ("md" . "tex"))
738 739
  "Rules for omitting automatically generated files.
For example, .tex files may be generated from `org-mode' or Pandoc."
740 741 742
  :type '(repeat (cons string string))
  :group 'deft)

743 744 745 746 747
(defcustom deft-filter-only-filenames nil
  "Filter on file names only."
  :type 'boolean
  :group 'deft)

Jason Blevins's avatar
Jason Blevins committed
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764
;; Faces

(defgroup deft-faces nil
  "Faces used in Deft mode"
  :group 'deft
  :group 'faces)

(defface deft-header-face
  '((t :inherit font-lock-keyword-face :bold t))
  "Face for Deft header."
  :group 'deft-faces)

(defface deft-filter-string-face
  '((t :inherit font-lock-string-face))
  "Face for Deft filter string."
  :group 'deft-faces)

765 766 767 768 769
(defface deft-filter-string-error-face
  '((t :inherit font-lock-warning-face))
  "Face for Deft filter string when regexp is invalid."
  :group 'deft-faces)

Jason Blevins's avatar
Jason Blevins committed
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784
(defface deft-title-face
  '((t :inherit font-lock-function-name-face :bold t))
  "Face for Deft file titles."
  :group 'deft-faces)

(defface deft-separator-face
  '((t :inherit font-lock-comment-delimiter-face))
  "Face for Deft separator string."
  :group 'deft-faces)

(defface deft-summary-face
  '((t :inherit font-lock-comment-face))
  "Face for Deft file summary strings."
  :group 'deft-faces)

785 786 787 788 789
(defface deft-time-face
  '((t :inherit font-lock-variable-name-face))
  "Face for Deft last modified times."
  :group 'deft-faces)

Jason Blevins's avatar
Jason Blevins committed
790 791
;; Constants

Jason Blevins's avatar
Jason Blevins committed
792
(defconst deft-version "0.8")
Jason Blevins's avatar
Jason Blevins committed
793

Jason Blevins's avatar
Jason Blevins committed
794 795 796 797 798 799
(defconst deft-buffer "*Deft*"
  "Deft buffer name.")

(defconst deft-separator " --- "
  "Text used to separate file titles and summaries.")

800 801 802
(defconst deft-empty-file-title "[Empty file]"
  "Text to use as title for empty files.")

Jason Blevins's avatar
Jason Blevins committed
803 804
;; Global variables

Jason Blevins's avatar
Jason Blevins committed
805 806
(defvar deft-mode-hook nil
  "Hook run when entering Deft mode.")
Jason Blevins's avatar
Jason Blevins committed
807

808 809 810
(defvar deft-filter-hook nil
  "Hook run when the Deft filter string changes.")

811 812 813
(defvar deft-open-file-hook nil
  "Hook run after Deft opens a file.")

Jason Blevins's avatar
Jason Blevins committed
814
(defvar deft-filter-regexp nil
815 816 817 818 819 820 821 822 823 824
  "A list of string representing the current filter used by Deft.

In incremental search mode, when `deft-incremental-search' is
non-nil, the elements of this list are the individual words of
the filter string, in reverse order.  That is, the car of the
list is the last word in the filter string.

In regexp search mode, when `deft-incremental-search' is nil,
this list has a single element containing the entire filter
regexp.")
Jason Blevins's avatar
Jason Blevins committed
825 826 827 828

(defvar deft-current-files nil
  "List of files matching current filter.")

829 830 831 832
(defvar deft-current-sort-method 'mtime
  "Current file soft method.
Available methods are 'mtime and 'title.")

Jason Blevins's avatar
Jason Blevins committed
833
(defvar deft-all-files nil
834
  "List of all files in `deft-directory'.")
Jason Blevins's avatar
Jason Blevins committed
835 836 837 838 839 840 841 842 843 844 845 846 847

(defvar deft-hash-contents nil
  "Hash containing complete cached file contents, keyed by filename.")

(defvar deft-hash-mtimes nil
  "Hash containing cached file modification times, keyed by filename.")

(defvar deft-hash-titles nil
  "Hash containing cached file titles, keyed by filename.")

(defvar deft-hash-summaries nil
  "Hash containing cached file summaries, keyed by filename.")

Jason Blevins's avatar
Jason Blevins committed
848 849 850
(defvar deft-auto-save-buffers nil
  "List of buffers that will be automatically saved.")

851 852 853
(defvar deft-window-width nil
  "Width of Deft buffer.")

854 855 856
(defvar deft-filter-history nil
  "History of interactive filter strings.")

857 858 859
(defvar deft-regexp-error nil
  "Flag for indicating invalid regexp errors.")

860
(defvar deft-default-extension (copy-sequence (car deft-extensions))
861 862
  "Default file extension of newly created files.")

863 864 865
(defvar deft-pending-updates nil
  "Indicator of pending updates due to automatic saves, etc.")

866 867
(make-obsolete-variable 'deft-width-offset nil "v0.8")

Ivan Kanis's avatar
Ivan Kanis committed
868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
;; Keymap definition

(defvar deft-mode-map
  (let ((i 0)
        (map (make-keymap)))
    ;; Make multibyte characters extend the filter string.
    (set-char-table-range (nth 1 map) (cons #x100 (max-char))
                          'deft-filter-increment)
    ;; Extend the filter string by default.
    (setq i ?\s)
    (while (< i 256)
      (define-key map (vector i) 'deft-filter-increment)
      (setq i (1+ i)))
    ;; Handle backspace and delete
    (define-key map (kbd "DEL") 'deft-filter-decrement)
    (define-key map (kbd "M-DEL") 'deft-filter-decrement-word)
    ;; Handle return via completion or opening file
    (define-key map (kbd "RET") 'deft-complete)
    ;; Filtering
    (define-key map (kbd "C-c C-l") 'deft-filter)
    (define-key map (kbd "C-c C-c") 'deft-filter-clear)
    (define-key map (kbd "C-y") 'deft-filter-yank)
    ;; File creation
    (define-key map (kbd "C-c C-n") 'deft-new-file)
    (define-key map (kbd "C-c C-m") 'deft-new-file-named)
    (define-key map (kbd "<C-return>") 'deft-new-file-named)
    ;; File management
    (define-key map (kbd "C-c C-d") 'deft-delete-file)
    (define-key map (kbd "C-c C-r") 'deft-rename-file)
    (define-key map (kbd "C-c C-f") 'deft-find-file)
    (define-key map (kbd "C-c C-a") 'deft-archive-file)
    ;; Settings
    (define-key map (kbd "C-c C-t") 'deft-toggle-incremental-search)
901
    (define-key map (kbd "C-c C-s") 'deft-toggle-sort-method)
Ivan Kanis's avatar
Ivan Kanis committed
902 903 904 905 906 907 908 909 910 911 912 913 914
    ;; Miscellaneous
    (define-key map (kbd "C-c C-g") 'deft-refresh)
    (define-key map (kbd "C-c C-q") 'quit-window)
    ;; Widgets
    (define-key map [down-mouse-1] 'widget-button-click)
    (define-key map [down-mouse-2] 'widget-button-click)
    (define-key map (kbd "<tab>") 'widget-forward)
    (define-key map (kbd "<backtab>") 'widget-backward)
    (define-key map (kbd "<S-tab>") 'widget-backward)
    (define-key map (kbd "C-o") 'deft-open-file-other-window)
    map)
  "Keymap for Deft mode.")

Simon Belak's avatar
Simon Belak committed
915 916 917 918 919 920
;; Helpers

(defun deft-whole-filter-regexp ()
  "Join incremental filters into one."
  (mapconcat 'identity (reverse deft-filter-regexp) " "))

921
(defun deft-search-forward (str)
Jason Blevins's avatar
Jason Blevins committed
922
  "Function to use when matching files against filter strings STR.
923 924 925 926 927 928
This function calls `search-forward' when `deft-incremental-search'
is non-nil and `re-search-forward' otherwise."
  (if deft-incremental-search
      (search-forward str nil t)
    (re-search-forward str nil t)))

929
(defun deft-set-mode-name ()
Jason Blevins's avatar
Jason Blevins committed
930
  "Set the mode line text based on search mode."
931 932 933 934 935 936 937 938 939 940
  (if deft-incremental-search
      (setq mode-name "Deft")
    (setq mode-name "Deft/R")))

(defun deft-toggle-incremental-search ()
  "Toggle the `deft-incremental-search' setting."
  (interactive)
  (cond
   (deft-incremental-search
    (setq deft-incremental-search nil)
941
    (message "Regexp search"))
942 943 944
   (t
    (setq deft-incremental-search t)
    (message "Incremental string search")))
945
  (deft-filter (deft-whole-filter-regexp) t)
946
  (deft-set-mode-name))
947

948 949 950 951 952 953 954
(defun deft-toggle-sort-method ()
  "Toggle file sorting method defined in `deft-current-sort-method'."
  (interactive)
  (setq deft-current-sort-method
        (if (eq deft-current-sort-method 'mtime) 'title 'mtime))
  (deft-refresh))

955 956 957 958 959 960 961 962 963
(defun deft-filter-regexp-as-regexp ()
  "Return a regular expression corresponding to the current filter string.
When `deft-incremental-search' is non-nil, we must combine each individual
whitespace separated string.  Otherwise, the `car' of `deft-filter-regexp'
is the complete regexp."
  (if deft-incremental-search
      (mapconcat 'regexp-quote (reverse deft-filter-regexp) "\\|")
    (car deft-filter-regexp)))

Jason Blevins's avatar
Jason Blevins committed
964 965 966 967
;; File processing

(defun deft-chomp (str)
  "Trim leading and trailing whitespace from STR."
968
  (replace-regexp-in-string "\\(^[[:space:]\n]*\\|[[:space:]\n]*$\\)" "" str))
Jason Blevins's avatar
Jason Blevins committed
969 970

(defun deft-base-filename (file)
971 972 973 974
  "Strip `deft-directory' and `deft-extension' from filename FILE."
  (let* ((deft-dir (file-name-as-directory (expand-file-name deft-directory)))
         (len (length deft-dir))
         (file (substring file len)))
975
    (file-name-sans-extension file)))
Jason Blevins's avatar
Jason Blevins committed
976 977

(defun deft-find-all-files ()
978 979
  "Return a list of all files in the Deft directory.

980 981 982
See `deft-find-files'."
  (deft-find-files deft-directory))

983 984 985 986 987 988 989 990
(defun deft-find-all-files-no-prefix ()
  "List files in Deft directory with the Deft directory prefix removed.
See `deft-find-files' and `deft-find-all-files'."
  (let* ((dir (expand-file-name deft-directory))
         (files (mapcar (lambda (f) (replace-regexp-in-string dir "" f))
                        (deft-find-all-files))))
    files))

991 992 993
(defun deft-find-files (dir)
  "Return a list of all files in the directory DIR.

994 995 996 997
It is important to note that the return value is a list of
absolute filenames.  These absolute filenames are used as keys
for the various hash tables used for storing file metadata and
contents.  So, any functions looking up values in these hash
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
tables should use `expand-file-name' on filenames first.

If `deft-recursive' is non-nil, then search recursively in
subdirectories of `deft-directory' (with the exception of
`deft-archive-directory').

See `deft-find-all-files'."
  (if (file-exists-p dir)
      (let ((archive-dir (expand-file-name (concat deft-directory "/"
                                                   deft-archive-directory "/")))
            (files (directory-files dir t "." t))
            result)
1010
        (dolist (file files)
1011 1012 1013 1014 1015
          (cond
           ;; Recurse into subdirectory if `deft-recursive' is non-nil
           ;; and the directory is not ".", "..", or `deft-archive-directory'.
           ((file-directory-p file)
            (when (and deft-recursive
1016
                       (not (string-match deft-recursive-ignore-dir-regexp file))
1017 1018 1019 1020 1021
                       (not (string-prefix-p archive-dir
                                             (expand-file-name (concat file "/")))))
              (setq result (append (deft-find-files file) result))))
           ;; Collect names of readable files ending in `deft-extension'
           ((and (file-readable-p file)
1022
                 (not (string-match deft-ignore-file-regexp file))
Jason Blevins's avatar
Jason Blevins committed
1023
                 (not (backup-file-name-p file))
1024
                 (member (file-name-extension file) deft-extensions))
1025
            (setq result (cons file result)))))
1026 1027 1028
        (deft-apply-generation-rules result))))

(defun deft-apply-generation-rules (lst)
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
  "Apply `deft-generation-rules' to each file in LST.
Remove files which were likely automatically generated from others."
  (if deft-generation-rules
      (let ((result nil))
        (dolist (file lst)
          (when (not (deft-generated-file? file lst))
            (setq result (cons file result))))
        result)
    lst))

(defun deft-generated-file? (file-name files)
  "Determine whether FILE-NAME was likely generated from another in LST.
See `deft-generation-rules'."
1042
  (let ((val nil))
1043
    (dolist (rule deft-generation-rules)
1044 1045 1046
      (let* ((orig-file-ext (file-name-extension file-name)))
        (when (equal (cdr rule) orig-file-ext)
          (let* ((new-file-ext (car rule))
1047 1048
                 (new-file-name (concat (file-name-sans-extension file-name)
                                        "." new-file-ext)))
1049
            (when (not val)
1050 1051 1052
              (when (member new-file-name files)
                (setq val t)))))))
    val))
Jason Blevins's avatar
Jason Blevins committed
1053

1054
(defun deft-strip-title (title)
1055
  "Remove all strings matching `deft-strip-title-regexp' from TITLE."
1056
  (deft-chomp (replace-regexp-in-string deft-strip-title-regexp "" title)))
1057

1058
(defun deft-parse-title (file contents)
1059
  "Parse the given FILE and CONTENTS and determine the title.
Jason Blevins's avatar
Jason Blevins committed
1060 1061
If `deft-use-filename-as-title' is nil, the title is taken to
be the first non-empty line of the FILE.  Else the base name of the FILE is
1062
used as title."
1063 1064 1065
  (if deft-use-filename-as-title
      (deft-base-filename file)
    (let ((begin (string-match "^.+$" contents)))
1066
      (if begin
1067
          (funcall deft-parse-title-function
1068
                   (substring contents begin (match-end 0)))))))
Jason Blevins's avatar
Jason Blevins committed
1069 1070 1071 1072

(defun deft-parse-summary (contents title)
  "Parse the file CONTENTS, given the TITLE, and extract a summary.
The summary is a string extracted from the contents following the
1073
title."
1074 1075
  (let ((summary (let ((case-fold-search nil))
                   (replace-regexp-in-string deft-strip-summary-regexp " " contents))))
1076 1077 1078
    (deft-chomp
      (if (and title
               (not deft-use-filename-as-title)
1079 1080 1081 1082 1083
               (string-match (regexp-quote
                              (if deft-org-mode-title-prefix
                                  (concat "^#+TITLE: " title)
                                title))
                             summary))
1084 1085
          (substring summary (match-end 0) nil)
        summary))))
Jason Blevins's avatar
Jason Blevins committed
1086 1087 1088 1089

(defun deft-cache-file (file)
  "Update file cache if FILE exists."
  (when (file-exists-p file)
1090
    (add-to-list 'deft-all-files file)
Jason Blevins's avatar
Jason Blevins committed
1091
    (let ((mtime-cache (deft-file-mtime file))
1092
          (mtime-file (nth 5 (file-attributes (file-truename file)))))
Jason Blevins's avatar
Jason Blevins committed
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
      (if (or (not mtime-cache)
              (time-less-p mtime-cache mtime-file))
          (deft-cache-newer-file file mtime-file)))))

(defun deft-cache-newer-file (file mtime)
  "Update cached information for FILE with given MTIME."
  ;; Modification time
  (puthash file mtime deft-hash-mtimes)
  (let (contents title)
    ;; Contents
    (with-current-buffer (get-buffer-create "*Deft temp*")
      (insert-file-contents file nil nil nil t)
      (setq contents (concat (buffer-string))))
    (puthash file contents deft-hash-contents)
    ;; Title
1108
    (setq title (deft-parse-title file contents))
Jason Blevins's avatar
Jason Blevins committed
1109 1110 1111 1112 1113 1114
    (puthash file title deft-hash-titles)
    ;; Summary
    (puthash file (deft-parse-summary contents title) deft-hash-summaries))
  (kill-buffer "*Deft temp*"))

(defun deft-file-newer-p (file1 file2)
Jason Blevins's avatar
Jason Blevins committed
1115
  "Return non-nil if FILE1 was modified since FILE2 and nil otherwise."
Jason Blevins's avatar
Jason Blevins committed
1116 1117 1118 1119 1120
  (let (time1 time2)
    (setq time1 (deft-file-mtime file1))
    (setq time2 (deft-file-mtime file2))
    (time-less-p time2 time1)))

1121
(defun deft-file-title-lessp (file1 file2)
Jason Blevins's avatar
Jason Blevins committed
1122
  "Return non-nil if FILE1 title is lexicographically less than FILE2's.
1123 1124 1125 1126 1127 1128
Case is ignored."
  (let ((t1 (deft-file-title file1))
        (t2 (deft-file-title file2)))
    (string-lessp (and t1 (downcase t1))
                  (and t2 (downcase t2)))))

Jason Blevins's avatar
Jason Blevins committed
1129
(defun deft-cache-initialize ()
Jason Blevins's avatar
Jason Blevins committed
1130
  "Initialize hash tables for caching files."
Jason Blevins's avatar
Jason Blevins committed
1131 1132 1133 1134 1135
  (setq deft-hash-contents (make-hash-table :test 'equal))
  (setq deft-hash-mtimes (make-hash-table :test 'equal))
  (setq deft-hash-titles (make-hash-table :test 'equal))
  (setq deft-hash-summaries (make-hash-table :test 'equal)))

1136 1137
(defun deft-cache-update-all ()
  "Update file list and update cached information for each file."
Jason Blevins's avatar
Jason Blevins committed
1138 1139 1140 1141
  (setq deft-all-files (deft-find-all-files))             ; List all files
  (mapc 'deft-cache-file deft-all-files)                  ; Cache contents
  (setq deft-all-files (deft-sort-files deft-all-files))) ; Sort by mtime

1142
(defun deft-cache-update-file (file)
Jason Blevins's avatar
Jason Blevins committed
1143
  "Update cached information for a single file named FILE."
1144 1145 1146
  (deft-cache-file file)                                  ; Cache contents
  (setq deft-all-files (deft-sort-files deft-all-files))) ; Sort by mtime

Jason Blevins's avatar
Jason Blevins committed
1147 1148 1149
;; Cache access

(defun deft-file-contents (file)
Jason Blevins's avatar
Jason Blevins committed
1150
  "Retrieve complete contents of FILE from cache."
Jason Blevins's avatar
Jason Blevins committed
1151 1152 1153
  (gethash file deft-hash-contents))

(defun deft-file-mtime (file)
Jason Blevins's avatar
Jason Blevins committed
1154
  "Retrieve modified time of FILE from cache."
Jason Blevins's avatar
Jason Blevins committed
1155 1156 1157
  (gethash file deft-hash-mtimes))

(defun deft-file-title (file)
Jason Blevins's avatar
Jason Blevins committed
1158
  "Retrieve title of FILE from cache."
Jason Blevins's avatar
Jason Blevins committed
1159 1160 1161
  (gethash file deft-hash-titles))

(defun deft-file-summary (file)
Jason Blevins's avatar
Jason Blevins committed
1162
  "Retrieve summary of FILE from cache."
Jason Blevins's avatar
Jason Blevins committed
1163 1164 1165 1166 1167
  (gethash file deft-hash-summaries))

;; File list display

(defun deft-print-header ()
Jason Blevins's avatar
Jason Blevins committed
1168
  "Prints the *Deft* buffer header."
Jason Blevins's avatar
Jason Blevins committed
1169 1170 1171 1172 1173
  (if deft-filter-regexp
      (progn
        (widget-insert
         (propertize "Deft: " 'face 'deft-header-face))
        (widget-insert
1174 1175 1176 1177
         (propertize (deft-whole-filter-regexp) 'face
                     (if (and (not deft-incremental-search) deft-regexp-error)
                         'deft-filter-string-error-face
                       'deft-filter-string-face))))
Jason Blevins's avatar
Jason Blevins committed
1178 1179
    (widget-insert
         (propertize "Deft" 'face 'deft-header-face)))
Jason Blevins's avatar
Jason Blevins committed
1180 1181
  (widget-insert "\n\n"))

1182
(defun deft-current-window-width ()
1183 1184 1185 1186
  "Return current width of window displaying `deft-buffer'.
If the frame has a fringe, it will absorb the newline.
Otherwise, we reduce the line length by a one-character offset."
  (let* ((window (get-buffer-window deft-buffer))
1187 1188
         (fringe-right (ceiling (or (cadr (window-fringes)) 0)))
         (offset (if (> fringe-right 0) 0 1)))
1189 1190
    (when window
      (- (window-text-width window) offset))))
1191

1192 1193 1194
(defun deft-buffer-setup (&optional refresh)
  "Render the file browser in the *Deft* buffer.
When REFRESH is true, attempt to restore the point afterwards."
1195 1196
  (let ((orig-line (line-number-at-pos))
        (orig-col (current-column)))
1197 1198
    (when (deft-buffer-visible-p)
      (setq deft-window-width (deft-current-window-width)))
1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
    (let ((inhibit-read-only t))
      (erase-buffer))
    (remove-overlays)
    (deft-print-header)

    ;; Print the files list
    (if (not (file-exists-p deft-directory))
        (widget-insert (deft-no-directory-message))
      (if deft-current-files
          (progn
            (mapc 'deft-file-widget deft-current-files))
        (widget-insert (deft-no-files-message))))

    (use-local-map deft-mode-map)
    (widget-setup)
1214
    (setq deft-pending-updates nil)
1215 1216 1217 1218 1219

    ;; Position or reposition point
    (goto-char (point-min))
    (forward-line (if refresh (1- orig-line) 2))
    (forward-char (if refresh orig-col 0))))
Jason Blevins's avatar
Jason Blevins committed
1220

1221
(defun deft-string-width (str)
1222 1223 1224 1225
  "Return 0 if STR is nil and call `string-width` otherwise.
This is simply a wrapper function for `string-width' which
handles nil values gracefully."
  (if str (string-width str) 0))
1226

Jason Blevins's avatar
Jason Blevins committed
1227
(defun deft-file-widget (file)
Jason Blevins's avatar
Jason Blevins committed
1228
  "Add a line to the file browser for the given FILE."
Jason Blevins's avatar
Jason Blevins committed
1229
  (when file
1230
    (let* ((key (file-name-nondirectory file))
Jason Blevins's avatar
Jason Blevins committed
1231 1232 1233 1234 1235
           (text (deft-file-contents file))
           (title (deft-file-title file))
           (summary (deft-file-summary file))
           (mtime (when deft-time-format
                    (format-time-string deft-time-format (deft-file-mtime file))))
1236
           (mtime-width (deft-string-width mtime))
1237
           (line-width (- deft-window-width mtime-width))
1238 1239
           (title-width (min line-width (deft-string-width title)))
           (summary-width (min (deft-string-width summary)
Jason Blevins's avatar
Jason Blevins committed
1240 1241 1242
                               (- line-width
                                  title-width
                                  (length deft-separator)))))
Jason Blevins's avatar
Jason Blevins committed
1243 1244 1245
      (widget-create 'link
                     :button-prefix ""
                     :button-suffix ""
Jason Blevins's avatar
Jason Blevins committed
1246
                     :button-face 'deft-title-face
Jason Blevins's avatar
Jason Blevins committed
1247 1248 1249 1250 1251
                     :format "%[%v%]"
                     :tag file
                     :help-echo "Edit this file"
                     :notify (lambda (widget &rest ignore)
                               (deft-open-file (widget-get widget :tag)))
1252
                     (if title (truncate-string-to-width title title-width)
1253
                       deft-empty-file-title))
1254
      (when (> summary-width 0)
Jason Blevins's avatar
Jason Blevins committed
1255
        (widget-insert (propertize deft-separator 'face 'deft-separator-face))
1256
        (widget-insert (propertize (truncate-string-to-width summary summary-width)
Jason Blevins's avatar
Jason Blevins committed
1257
                                   'face 'deft-summary-face)))
1258
      (when mtime
Jason Blevins's avatar
Jason Blevins committed
1259 1260 1261
        (while (< (current-column) line-width)
          (widget-insert " "))
        (widget-insert (propertize mtime 'face 'deft-time-face)))
Jason Blevins's avatar
Jason Blevins committed
1262 1263
      (widget-insert "\n"))))

1264 1265 1266 1267
(defun deft-buffer-visible-p ()
  "Return non-nil if a window is displaying `deft-buffer'."
  (get-buffer-window deft-buffer))

1268 1269
(defun deft-window-size-change-function (frame)
  "Possibly refresh Deft buffer when size of a window in FRAME is changed.
1270 1271 1272
If there are pending updates, refresh the filtered files list and
update the Deft browser.  Otherwise, if the window width changed,
only update the Deft browser."
1273
  (when (deft-buffer-visible-p)
1274 1275 1276
    (cond (deft-pending-updates (deft-refresh-filter))
          ((/= deft-window-width (deft-current-window-width))
           (deft-refresh-browser)))))
1277

1278
(defun deft-window-configuration-change-function ()
1279 1280
  "Possibly refresh Deft browser when window configuration is changed."
  (deft-window-size-change-function nil))
1281

Jason Blevins's avatar
Jason Blevins committed
1282
(defun deft-refresh ()
1283
  "Update the file cache, reapply the filter, and refresh the *Deft* buffer."
Jason Blevins's avatar
Jason Blevins committed
1284
  (interactive)
1285 1286 1287 1288 1289 1290 1291 1292 1293
  (deft-cache-update-all)
  (deft-refresh-filter))

(defun deft-refresh-filter ()
  "Reapply the filter and refresh the *Deft* buffer.
Call this after any actions which update the cache."
  (interactive)
  (deft-filter-update)
  (deft-refresh-browser))
Jason Blevins's avatar
Jason Blevins committed
1294

1295
(defun deft-refresh-browser ()
1296 1297
  "Refresh the *Deft* buffer in the background.
Call this function after any actions which update the filter and file list."
1298 1299
  (when (get-buffer deft-buffer)
    (with-current-buffer deft-buffer
1300
      (deft-buffer-setup t))))
1301

Jason Blevins's avatar
Jason Blevins committed
1302
(defun deft-no-directory-message ()
Jason Blevins's avatar
Jason Blevins committed
1303
  "Return a short message to display when the Deft directory does not exist."
Jason Blevins's avatar
Jason Blevins committed
1304 1305 1306
  (concat "Directory " deft-directory " does not exist.\n"))

(defun deft-no-files-message ()
Jason Blevins's avatar
Jason Blevins committed
1307
  "Return a short message to display if no files are found."
Jason Blevins's avatar
Jason Blevins committed
1308 1309 1310 1311 1312 1313
  (if deft-filter-regexp
      "No files match the current filter string.\n"
    "No files found."))

;; File list file management actions

1314 1315
(defun deft-absolute-filename (slug &optional extension)
  "Return an absolute filename to file named SLUG with optional EXTENSION.
1316
If EXTENSION is not given, `deft-default-extension' is assumed.
1317 1318 1319 1320

Refer to `deft-file-naming-rules' for setting rules for formatting the file
name."
  (let* ((slug (deft-chomp slug)) ; remove leading/trailing spaces
1321
         (slash-replacement (cdr (assq 'noslash deft-file-naming-rules)))
1322 1323
         (space-replacement (cdr (assq 'nospace deft-file-naming-rules)))
         (case-fn           (cdr (assq 'case-fn deft-file-naming-rules))))
1324 1325
    (when slash-replacement
      (setq slug (replace-regexp-in-string "\/" slash-replacement slug)))
1326 1327 1328 1329 1330
    (when space-replacement
      (setq slug (replace-regexp-in-string " " space-replacement slug)))
    (when case-fn
      (setq slug (funcall case-fn slug)))
    (concat (file-name-as-directory (expand-file-name deft-directory))
1331
            slug "." (or extension deft-default-extension))))
1332 1333 1334

(defun deft-unused-slug ()
  "Return an unused filename slug (short name) in `deft-directory'."
1335 1336 1337
  (let* ((slug (format-time-string deft-new-file-format))
         (fmt (concat slug "_%d"))
         (counter 1)
1338
         (file (deft-absolute-filename slug)))
1339 1340 1341
    (while (or (file-exists-p file) (get-file-buffer file))
      (setq counter (1+ counter))
      (setq slug (format fmt counter))
1342
      (setq file (deft-absolute-filename slug)))
1343 1344
    slug))

1345 1346 1347 1348 1349 1350
(defun deft-update-visiting-buffers (old new)
  "Rename visited file of buffers visiting file OLD to NEW."
  (let ((buffer (get-file-buffer old)))
    (when buffer
      (with-current-buffer (get-file-buffer old)
        (set-visited-file-name new nil t)
1351
        (hack-local-variables)))))
1352

1353 1354 1355
(defun deft-open-file (file &optional other switch)
  "Open FILE in a new buffer and setting its mode.
When OTHER is non-nil, open the file in another window.  When
1356
OTHER and SWITCH are both non-nil, switch to the other window.
1357
FILE must be a relative or absolute path, with extension."
1358 1359
  (let ((buffer (find-file-noselect file)))
    (with-current-buffer buffer
1360
      (hack-local-variables)
1361
      (when deft-filter-regexp
Ivan Kanis's avatar
Ivan Kanis committed
1362
        (goto-char (point-min))
1363
        (re-search-forward (deft-filter-regexp-as-regexp) nil t))
1364 1365 1366 1367 1368
      ;; Ensure that Deft has been initialized
      (when (not (get-buffer deft-buffer))
        (with-current-buffer (get-buffer-create deft-buffer)
          (deft-mode)))
      ;; Set up auto save hooks
1369 1370 1371 1372
      (add-to-list 'deft-auto-save-buffers buffer)
      (add-hook 'after-save-hook
                (lambda () (save-excursion
                             (deft-cache-update-file buffer-file-name)
1373 1374 1375
                             (if (deft-buffer-visible-p)
                                 (deft-refresh-filter)
                               (setq deft-pending-updates t))))
1376
                nil t))
1377
    (run-hooks 'deft-open-file-hook)
1378 1379 1380 1381
    (if other
        (if switch
            (switch-to-buffer-other-window buffer)
          (display-buffer buffer other))
1382
      (switch-to-buffer buffer))))
Jason Blevins's avatar
Jason Blevins committed
1383

1384
;;;###autoload
Jason Blevins's avatar
Jason Blevins committed
1385
(defun deft-find-file (file)
1386 1387 1388 1389
  "Find FILE interactively using the minibuffer.
FILE must exist and be a relative or absolute path, with extension.
If FILE is not inside `deft-directory', fall back to using `find-file'."
  (interactive
1390 1391 1392 1393 1394 1395
   (list (completing-read "Deft find file: " (deft-find-all-files-no-prefix))))
  (let* ((dir (expand-file-name deft-directory)))
    ;; If missing, add full deft-directory prefix back
    (unless (string-match (concat "^" dir) file)
      (setq file (concat dir file)))
    (deft-open-file file)))
Jason Blevins's avatar
Jason Blevins committed
1396

1397
(defun deft-auto-populate-title-maybe (file)
Jason Blevins's avatar
Jason Blevins committed
1398 1399 1400 1401
  "Possibly populate title line for FILE using filter string.
If the filter string is non-nil and `deft-use-filename-as-title'
is nil, then use the filter string to populate the title line in
the newly created FILE."
1402
  (when (and deft-filter-regexp (not deft-use-filename-as-title))
1403 1404 1405 1406 1407
    (write-region
     (concat
      (cond
       ((and (> deft-markdown-mode-title-level 0)
             (string-match "^\\(txt\\|text\\|md\\|mdown\\|markdown\\)"
Jason Blevins's avatar
Jason Blevins committed
1408
                           deft-default-extension))
1409 1410
        (concat (make-string deft-markdown-mode-title-level ?#) " "))
       ((and deft-org-mode-title-prefix
Jason Blevins's avatar
Jason Blevins committed
1411 1412
             (string-equal deft-default-extension "org"))
        "#+TITLE: "))
1413 1414 1415
      (deft-whole-filter-regexp)
      "\n\n")
     nil file nil)))
1416

1417 1418
(defun deft-new-file-named (slug)
  "Create a new file named SLUG.
Jason Blevins's avatar
Jason Blevins committed
1419
SLUG is the short file name, without a path or a file extension."
Jason Blevins's avatar
Jason Blevins committed
1420
  (interactive "sNew filename (without extension): ")
1421 1422 1423
  (let ((file (deft-absolute-filename slug)))
    (if (file-exists-p file)
        (message "Aborting, file already exists: %s" file)
1424
      (deft-auto-populate-title-maybe file)
1425 1426 1427 1428 1429
      (deft-cache-update-file file)
      (deft-refresh-filter)
      (deft-open-file file)
      (with-current-buffer (get-file-buffer file)
        (goto-char (point-max))))))
Jason Blevins's avatar
Jason Blevins committed
1430

1431
;;;###autoload
Jason Blevins's avatar
Jason Blevins committed
1432
(defun deft-new-file ()
1433
  "Create a new file quickly.
1434 1435 1436
Use either an automatically generated filename or the filter string if non-nil
and `deft-use-filter-string-for-filename' is set.  If the filter string is
non-nil and title is not from filename, use it as the title."
Jason Blevins's avatar
Jason Blevins committed
1437
  (interactive)
1438
  (let (slug)
1439
    (if (and deft-filter-regexp deft-use-filter-string-for-filename)
1440 1441
        ;; If the filter string is non-emtpy and titles are taken from
        ;; filenames is set, construct filename from filter string.
1442
        (setq slug (deft-whole-filter-regexp))
1443 1444
      ;; If the filter string is empty, or titles are taken from file
      ;; contents, then use an automatically generated unique filename.
1445 1446
      (setq slug (deft-unused-slug)))
    (deft-new-file-named slug)))
Jason Blevins's avatar
Jason Blevins committed
1447

1448
(defun deft-filename-at-point ()
1449 1450
  "Return the name of the file represented by the widget at the point.
Return nil if the point is not on a file widget."
1451 1452
  (widget-get (widget-at) :tag))

1453
(defun deft-open-file-other-window (&optional arg)
Jason Blevins's avatar
Jason Blevins committed
1454 1455
  "When the point is at a widget, open the file in the other window.
The argument ARG is passed to `deft-open-file'."
1456
  (interactive "P")
1457
  (let ((file (deft-filename-at-point)))
1458 1459 1460
    (when file
      (deft-open-file file t arg))))

Jason Blevins's avatar
Jason Blevins committed
1461
(defun deft-delete-file ()
Jason Blevins's avatar
Jason Blevins committed
1462 1463 1464
  "Delete the file represented by the widget at the point.
If the point is not on a file widget, do nothing.  Prompts before
proceeding."
Jason Blevins's avatar
Jason Blevins committed
1465
  (interactive)
1466
  (let ((filename (deft-filename-at-point)))
Jason Blevins's avatar
Jason Blevins committed
1467 1468 1469
    (when filename
      (when (y-or-n-p
             (concat "Delete file " (file-name-nondirectory filename) "? "))
1470 1471
        (let ((buffer (get-file-buffer filename)))
          (when buffer (kill-buffer buffer)))
Jason Blevins's avatar
Jason Blevins committed
1472 1473 1474 1475 1476 1477
        (delete-file filename)
        (delq filename deft-current-files)
        (delq filename deft-all-files)
        (deft-refresh)))))

(defun deft-rename-file ()
Jason Blevins's avatar
Jason Blevins committed
1478 1479
  "Rename the file represented by the widget at the point.
If the point is not on a file widget, do nothing."
Jason Blevins's avatar
Jason Blevins committed
1480
  (interactive)
1481
  (let ((old-filename (deft-filename-at-point))
1482 1483
        (deft-dir (file-name-as-directory deft-directory))