Emacs library to facilitate the creation of tabulated-list based UIs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

159 lines

  1. ;;; navigel-ex-fs.el --- Example of navigel to navigate the filesystem -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2019 Damien Cassou
  3. ;; Author: Damien Cassou <damien@cassou.me>
  4. ;; This program is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; GNU General Public License for more details.
  12. ;; You should have received a copy of the GNU General Public License
  13. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. ;;; Commentary:
  15. ;; This file is an example usage of navigel. It guides the reader
  16. ;; through an implementation of a tablist-based directory navigator.
  17. ;; In this example, we will implement a tablist-based UI to navigate
  18. ;; the folders of your computer. As in dired, we want one file or
  19. ;; directory per line. Pressing `RET' on a line should open the file
  20. ;; or directory at point. Pressing `m' should mark the file at point
  21. ;; while `d' should delete all marked files.
  22. ;;; Code:
  23. (require 'f)
  24. (require 'navigel)
  25. ;; Navigel is based on the notion of "entity". In our example of a
  26. ;; directory navigator, the entities will be absolute filenames.
  27. ;; Navigel requires the developer to implement a command that calls
  28. ;; `navigel-open' on the initial entity. Navigel also requires an
  29. ;; application name dynamically bound in the variable `navigel-app'.
  30. ;; This name is meant to disambiguate method definitions and is *not*
  31. ;; visible to the user. In this example, we use `navigel-ex-fs'
  32. ;; as name. The code below defines the command:
  33. (defun navigel-ex-fs-list-files (&optional directory)
  34. "List files of DIRECTORY, home directory if nil."
  35. (interactive (list (getenv "HOME")))
  36. (let ((navigel-app 'navigel-ex-fs))
  37. (navigel-open (f-expand directory) nil)))
  38. ;; For this command to display the files in the home directory (i.e.,
  39. ;; "~/"), navigel needs a way to get the children of a file entity.
  40. ;; Specifying behavior with navigel is done through method overriding.
  41. ;; How to get the children of an entity should be specified by
  42. ;; overriding the method `navigel-children':
  43. (navigel-method navigel-ex-fs navigel-children (directory callback)
  44. "Call CALLBACK with the files in DIRECTORY as parameter."
  45. (funcall callback (f-entries directory)))
  46. ;; `navigel-method' (which is syntactic sugar around `cl-defmethod')
  47. ;; is used to override the methods of navigel. To distinguish this
  48. ;; override of `navigel-children' from other overrides made by other
  49. ;; navigel clients, the first parameter to `navigel-method' must be
  50. ;; the name of the application saved in `navigel-app' in the command
  51. ;; above.
  52. ;; At this point, you should be able to type `M-x
  53. ;; navigel-ex-fs-list-files RET' to get a buffer showing all
  54. ;; files and folders in your home directory. If you move the point to
  55. ;; a folder and press `RET', a new buffer should open listing its
  56. ;; files and folders. If you type `M-x imenu RET', you can select one
  57. ;; entity of the buffer using completion: I recommend binding this
  58. ;; command or `counsel-imenu' to a key (e.g., to `M-i') because this
  59. ;; can be useful in many kinds of buffers.
  60. ;; A problem though: the absolute filenames (e.g., "/home/me/.bashrc")
  61. ;; are shown whereas a user probably expects to see basenames (e.g.,
  62. ;; ".bashrc") as in all file browsers. We can easily change that by
  63. ;; overriding the `navigel-name' method:
  64. (navigel-method navigel-ex-fs navigel-name (file)
  65. (f-filename file))
  66. ;; This is much better. With `RET', we can easily navigate from a
  67. ;; folder to its sub-folders. Nevertheless, we have no way yet to
  68. ;; navigate back, i.e., from a folder to its parent. To do that, we
  69. ;; need to override the `navigel-parent' method whose responsibility
  70. ;; is to return the parent entity of the entity passed as parameter:
  71. (navigel-method navigel-ex-fs navigel-parent (file)
  72. (f-dirname file))
  73. ;; You should now be able to press `^' to go to the parent directory
  74. ;; of the current one.
  75. ;; Pressing `RET' on a folder correctly opens the folder in another
  76. ;; navigel buffer. But, just like in `dired', you might want that
  77. ;; pressing `RET' on a file opens the file itself. This can be done
  78. ;; by overriding `navigel-open':
  79. (navigel-method navigel-ex-fs navigel-open (file _target)
  80. (if (f-file-p file)
  81. (find-file file)
  82. (cl-call-next-method)))
  83. ;; The `cl-call-next-method' call is used to express that we don't
  84. ;; have anything specific to do for a non-file first parameter and
  85. ;; that we want the default behavior. This works perfectly fine!
  86. ;; We can improve the list of files a bit by adding some more
  87. ;; information about each file. For example, we could have a first
  88. ;; column representing the size of each file. We start by
  89. ;; implementing a function returning the size of its file argument:
  90. (defun navigel-ex-fs-size (file)
  91. "Return FILE size as number of bytes."
  92. (nth 7 (file-attributes file)))
  93. ;; We now specify the column values for each file by overriding
  94. ;; `navigel-entity-to-columns':
  95. (navigel-method navigel-ex-fs navigel-entity-to-columns (file)
  96. (vector (number-to-string (navigel-ex-fs-size file))
  97. (navigel-name file)))
  98. ;; The code above specifies that the first column of a file line will
  99. ;; contain the file size and the second will contain the filename. We
  100. ;; aren't exactly done yet as we also need to specify what each column
  101. ;; should look like. This is done by overriding
  102. ;; `navigel-tablist-format':
  103. (navigel-method navigel-ex-fs navigel-tablist-format (_entity)
  104. (vector (list "Size (B)" 10 nil :right-align t)
  105. (list "Name" 0 t)))
  106. ;; This code defines the format of columns. The first column will have
  107. ;; "Size (B)" as title to indicate that the displayed numbers
  108. ;; represent the size in bytes. The first column will be 10
  109. ;; characters wide and the numbers will be right aligned. The second
  110. ;; column will have "Name as title and will take the rest of the
  111. ;; buffer width. Read the documentation of `tabulated-list-format' to
  112. ;; get more information about the column format specification.
  113. ;; As a final step, we might want to be able to delete files from the
  114. ;; file system. This can be done by overriding `navigel-delete':
  115. (navigel-method navigel-ex-fs navigel-delete (file &optional callback)
  116. (f-delete file)
  117. (funcall callback))
  118. ;; The `funcall' is here to tell navigel that deletion is
  119. ;; finished. You can now mark files with `m' and delete them with `D'.
  120. (provide 'navigel-ex-fs)
  121. ;;; navigel-ex-fs.el ends here