diff options
Diffstat (limited to 'doc/helm-devel.org')
-rw-r--r-- | doc/helm-devel.org | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/doc/helm-devel.org b/doc/helm-devel.org new file mode 100644 index 00000000..c991e833 --- /dev/null +++ b/doc/helm-devel.org @@ -0,0 +1,495 @@ +#+title: The Helm Developer's Guide +# #+subtitle: Release {{{version}}} +#+subtitle: Release 3.6.2 +#+author: The Helm Developers +# #+date: {{{modification-time}}} +#+language: en + +# #+texinfo: @insertcopying + +* Table Of Contents :TOC_3: +- [[#introduction][Introduction]] +- [[#creating-a-helm-buffer][Creating a Helm buffer]] +- [[#helm-attributes][Helm attributes]] + - [[#looking-up-helm-attributes][Looking up Helm attributes]] + - [[#mandatory-attributes][Mandatory attributes]] + - [[#optional-but-important-attributes][Optional, but important attributes]] + - [[#helm-keywords][~helm~ keywords]] + - [[#sources][~:sources~]] + - [[#buffer][~:buffer~]] + - [[#input][~:input~]] +- [[#customizing-action-lists][Customizing Action Lists]] +- [[#creating-a-source][Creating a Source]] + - [[#helm-source-sync][~helm-source-sync~]] + - [[#helm-source-in-buffer][~helm-source-in-buffer~]] + - [[#helm-source-async][~helm-source-async~]] + - [[#helm-source-dummy][~helm-source-dummy~]] + - [[#helm-source-in-file][~helm-source-in-file~]] + - [[#help][Help]] + - [[#pre-filtering-lines-in-a-buffer][Pre-filtering lines in a buffer]] +- [[#creating-a-class][Creating a Class]] + - [[#create-your-own-class-inheriting-from-one-of-the-main-classes][Create your own class inheriting from one of the main classes]] + - [[#create-your-own-class-and-inherit-from-one-of-helm-type-classes][Create your own class and Inherit from one of helm-type classes]] + - [[#creating-a-new-class-using-as-parent-a-class-inheriting-itself-from-a-helm-type--class][Creating a new class using as parent a class inheriting itself from a ~helm-type-*~ class]] + - [[#create-your-source-from-your-own-class][Create your source from your own class]] + - [[#write-your-own-helm-type-class][Write your own helm-type class]] +- [[#writing-actions][Writing actions]] +- [[#writing-persistent-actions][Writing persistent actions]] +- [[#footnotes][Footnotes]] + +* Introduction + +The best way to learn how to create a custom Helm command is to read +the source code[fn:1] and look at examples. + +A good place to start is the source file =helm-info.el=[fn:2]. This +source file is fairly short and straightforward. + +That being said, we'll try to go over some basic ideas in this manual. + +* Creating a Helm buffer + +The ~helm~ function creates a Helm buffer with candidates to select +and/or take action on. The list of candidates is provided by one or +more *sources*. + +An example usage of ~helm~ is below: + +#+begin_src emacs-lisp + (defun my-first-helm-command () + (interactive) + (helm :sources 'my-source + :buffer "*helm my command*")) +#+end_src + +~helm~ must be called with several keywords arguments, called +*attributes*. + +* Helm attributes + +An *attribute* determines Helm behavior. + +There are a large number of attributes in Helm; some are mandatory, +while others are optional. + +NOTE: When creating sources you are using slots which are keywords +describing how to build attributes, they have generally the same name +as attributes but not always e.g the slot ~:data~ exists, but there is +no such attribute. + +** Looking up Helm attributes + +To learn about /a single/ Helm attribute, use the documentation of the +class you are using where all slots are documented. + +** Mandatory attributes + +You have to give at least a name to your source and a list of +candidates. The list of candidates is given with a variable +containing candidates, a function returning candidates or a list, the +attribute depend on which class you are using, e.g candidates for sync +sources, for in-buffer sources you have to build a buffer using +~helm-init-candidates-in-buffer~ or for conveniency you can use the +~:data~ slot which will build the candidate buffer for you. In async +sources the candidates-process attribute is used which is a function +with no arg that returns a process. + +** Optional, but important attributes + +** ~helm~ keywords + +*** ~:sources~ + +Expects a source of the form: + +- Single source (alist) + +- Symbol naming the source + +- List of sources (alist or symbol) + +Where alists are the resulting value of functions building sources, +that is all the ~helm-build-*~ function or the ~helm-make-source~ +function, don't use directly alists when writing sources. See +examples in next section. + +*** ~:buffer~ + +*Optional but important*. + +The value for the ~:buffer~ keyword helps the ~helm-resume~ command +retrieve the Helm session. + +The name of the buffer should be prefixed with =helm= (e.g. =*helm +Info*=). This is not mandatory, but it is good practice. It will, +among other things, allow Helm to automatically hide the buffer. + +*** ~:input~ + +The value for the ~:input~ keyword is used to pre-fill the input of +the helm window. + +E.g. if the user is using helm to autocomplete and runs the +autocomplete command, you could get the word at point and set ~:input~ +to that word. That word will then be pre-filled in the minibuffer. + +#+begin_src emacs-lisp + (defun my-autocomplete-command () + (interactive) + (helm :sources 'my-source + :input (word-at-point) + :buffer "*helm my autocomplete command*")) +#+end_src + +* Customizing Action Lists + +It's possible to change the default list of actions for various +existing Helm commands. The actions are typically held in variables +called ~helm-type-foo-actions~, for instance ~helm-type-file-actions~, +so search apropos for those. Each action in the list is the usual cons +of action label and action function. + +Another higher-level approach is to use a pre-defined function such as +~helm-add-action-to-source~ or ~helm-add-action-to-source-if~. These +functions accept an action label, action function, the source to +modify (as a class symbol name, such as 'helm-source-ffiles), and for +the latter, a predicate which determines if the action should be made +available for the candidate under point. + +Also see ~helm-delete-action-from-source~, and +~helm-source-add-action-to-source-if~. + +* Creating a Source + +Even if you can still create source with alists, helm provides +convenient basic classes to build sources, and allow you to create +your own classes that inherit from these basics classes. + +Here are the basic classes for creating a Helm source: + +- ~helm-source-sync~ :: Put candidates in a list + +- ~helm-source-in-buffer~ :: Put candidates in a buffer + +- ~helm-source-async~ :: Get candidates asynchronously using the + output of a process. + +- ~helm-source-dummy~ :: Use ~helm-pattern~ as candidate. + +- ~helm-source-in-file~ :: Get candidates from the lines of a named + file using ~helm-source-in-buffer~. + +For consistency, prefix your source names with =helm-source-= +(e.g. ~helm-source-info-emacs~). + +For convenience, ~helm~ provide macros prefixed by =helm-build-= to +build your sources quickly, see examples below. + +All the different slots are documented in docstring of each classes. + +** ~helm-source-sync~ + +Put candidates in a list + +#+begin_src emacs-lisp + (helm-build-sync-source "test" + :candidates '(a b c d e)) + + (helm :sources (helm-build-sync-source "test" + :candidates '(a b c d e)) + :buffer "*helm sync source*") +#+end_src + +** ~helm-source-in-buffer~ + +Put candidates in a buffer + +#+begin_src emacs-lisp + (helm-build-in-buffer-source "test1" + :data '(a b c d e)) + + (helm :sources (helm-build-in-buffer-source "test1" + :data '(a b c d e)) + :buffer "*helm buffer source*") +#+end_src + +** ~helm-source-async~ + +Get candidates asynchronously using the output of a process. + +#+begin_src emacs-lisp + (helm :sources (helm-build-async-source "test2" + :candidates-process + (lambda () + (start-process "echo" nil "echo" "a\nb\nc\nd\ne"))) + :buffer "*helm async source*") +#+end_src + +** ~helm-source-dummy~ + +Use ~helm-pattern~ as candidate + +#+begin_src emacs-lisp + (defun helm/test-default-action (candidate) + (browse-url (format + "http://www.google.com/search?q=%s" + (url-hexify-string candidate)))) + + (helm :sources (helm-build-dummy-source "test" + :action '(("Google" . helm/test-default-action))) + :buffer "*helm test*") +#+end_src + +** ~helm-source-in-file~ + +Get candidates from the lines of a named file using +~helm-source-in-buffer~. + +#+begin_src emacs-lisp + (helm :sources (helm-build-in-file-source + "test" "~/.emacs.d/init.el" + :action (lambda (candidate) + (let ((linum (with-helm-buffer + (get-text-property + 1 'helm-linum + (helm-get-selection nil 'withprop))))) + (find-file (with-helm-buffer + (helm-attr 'candidates-file))) + (goto-line linum)))) + :buffer "*helm test*") + +#+end_src + +** Help + +To give a specific help to your Helm source, create a variable +~helm-<my-source>-help-string~ and bind it in your source with the +~helm-message~ slot. It will then appear when you use {{{kbd(C-h m)}}} or +{{{kbd(C-c ?=)}}}. + +** Pre-filtering lines in a buffer + +Here's an example of an in-buffer source that pre-filters lines to +those matching a certain regular expression. Then the pre-filtered +lines are narrowed as the user types. This uses +[[https://github.com/tarsius/hl-todo][hl-todo-mode]] which provide +=hl-todo-regexp= but you could use any regexp to do the same thing. +The =:init= function switches to the automatically created buffer, +which is returned by ~(helm-candidate-buffer 'global)~, then it +deletes uninteresting lines, after which Helm presents the remaining +lines to the user. The =:get-line= function is changed to +=buffer-substring= so that the properties are preserved in the +=helm-buffer=. + +#+begin_src emacs-lisp + (defun helm-hl-todo-items () + "Show `hl-todo'-keyword items in buffer." + (helm :sources (helm-build-in-buffer-source "hl-todo items" + :init (lambda () + (with-current-buffer (helm-candidate-buffer 'global) + (insert (with-helm-current-buffer (buffer-string))) + (goto-char (point-min)) + (delete-non-matching-lines hl-todo-regexp))) + :get-line #'buffer-substring) + :buffer "*helm hl-todo*")) +#+end_src + +And it could be also written using the =:data= slot: + +#+begin_src emacs-lisp + (defun helm-hl-todo-items () + "Show `hl-todo'-keyword items in buffer." + (helm :sources (helm-build-in-buffer-source "hl-todo items" + :data (current-buffer) + :candidate-transformer (lambda (candidates) + (cl-loop for c in candidates + when (string-match hl-todo-regexp c) + collect c)) + :get-line #'buffer-substring) + :buffer "*helm hl-todo*")) +#+end_src + +* Creating a Class + +** Create your own class inheriting from one of the main classes + +#+begin_src emacs-lisp + (defclass my-helm-class (helm-source-sync) + ((candidates :initform '("foo" "bar" "baz")))) + + (helm :sources (helm-make-source "test" 'my-helm-class) + :buffer "*helm test*") + +#+end_src + +This is same as creating your source with: + +#+begin_src emacs-lisp + (helm :sources (helm-build-sync-source "test" + :candidates '("foo" "bar" "baz")) + :buffer "*helm test*") + +#+end_src + +** Create your own class and Inherit from one of helm-type classes + +Here an example from a helm user that store a list of favorite files +in a file =~/.fav= to retrieve them quickly: + +#+begin_src emacs-lisp + (defclass helm-test-fav (helm-source-in-file helm-type-file) + ((candidates-file :initform "~/.fav"))) + + (helm :sources (helm-make-source "test" 'helm-test-fav) + :buffer "*helm test*") + +#+end_src + +*** Creating a new class using as parent a class inheriting itself from a ~helm-type-*~ class + +Sometimes, you may want to inherit from a class using itself a +~helm-type-*~ class but with one or more attributes of this class +slightly modified for your needs. You may think that you only need to +create your new class inheriting from the class inheriting itself from +the ~helm-type-*~ class, but this is not enough. + +Here how to do, reusing the example above we modify +the actions predefined by helm-type-file: + +1. Create a fake class: + + #+begin_src emacs-lisp + (defclass helm-override-test-fav (helm-source) ()) + #+end_src + +2. Create a method for this class + + This method is used as a ~:PRIMARY~ method, which mean that the + similar parent method, if some will not be used, in particular the + ~helm-source-in-file~ method which calls itself the + ~helm-source-in-buffer~ method, so to be sure these important + methods are used, use ~call-next-method~, see below. + + #+begin_src emacs-lisp + (defmethod helm--setup-source ((source helm-override-test-fav)) + (call-next-method) + (let ((actions (slot-value source 'action))) + (setf (slot-value source 'action) + (helm-append-at-nth (symbol-value actions) + '(("test" . ignore)) 1)))) + + #+end_src + +3. Create now your main class inheriting from your new overriding + class + + #+begin_src emacs-lisp + (defclass helm-test-fav (helm-source-in-file helm-type-file helm-override-test-fav) + ((candidates-file :initform "~/.fav"))) + #+end_src + + Now when running helm with a source built from your new class you + should see the new action you have added in second position to the + other file action. + +** Create your source from your own class + +Once your class is created, you have to use ~helm-make-source~ to +build your source. + +#+begin_src emacs-lisp + (helm-make-source "test" 'my-class :action 'foo [...other slots]) +#+end_src + +** Write your own helm-type class + +You will find several examples in the =helm-types.el=[fn:3] file. + +The main thing to remember is to create an empty class and fill it +using two =defmethod= s, one empty which should be a primary method and +one which is a before method, use for this the slots ~:primary~ and +~:before~ of ~defmethod~. This allows you to override different slots +of the inheriting type class in your new class. + +* Writing actions + +Actions are specified in the ~:action~ slot of your class, it is an +alist composed of (ACTION_NAME . FUNCTION), it can be also a symbol +defining a customizable action alist. + +#+begin_src elisp + (defcustom helm-source-foo + '(("Do this" . foo) + ("Do that" . bar)) + "A customizable action list." + :group 'helm + :type '(alist :key-type string :value-type function)) + +#+end_src + +It is recommended however to use ~helm-make-actions~ to define your +actions easily, it allow also to create actions with condition, e.g + +#+begin_src emacs-lisp + (helm-make-actions "Do this" + 'foo + (lambda () + (when (featurep 'something) + "Do that")) + 'bar) +#+end_src + +* Writing persistent actions + +The way to specify persistent-action is to use the slot +~:persistent-action~ to specify the function Helm will run when using +{{{kbd(C-j)}}} or {{{kbd(C-z)}}}. If you don't specify this helm will +run the first action of the action. + +You can also specify additional persistent actions, which must be +bound to keys other than {{{kbd(C-j)}}} or {{{kbd(C-z)}}}. + +#+begin_src emacs-lisp + (defun helm-foo-persistent-action () + "Bind a new persistent action 'foo-action to foo function. + foo function is an action function called with one arg candidate." + (interactive) + (with-helm-alive-p + ;; never split means to not split the helm window when executing + ;; this persistent action. If your source is using full frame you + ;; will want your helm buffer to split to display a buffer for the + ;; persistent action (if it needs one to display something). + (helm-attrset 'foo-action '(foo . never-split)) + (helm-execute-persistent-action 'foo-action))) +#+end_src + +Then you bind your new persistent action to something else than +{{{kbd(C-j)}}} or {{{kbd(C-z)}}}. + +* COMMENT Conventions + +** TODO Class names + +Class names are currently a mess. Need to come up with a better +convention. + +* Export Setup :noexport: + +#+setupfile: doc-setup.org +# #+options: H:4 + +#+export_file_name: helm-devel.texi + +#+texinfo_dir_category: Emacs Add-ons +#+texinfo_dir_title: Helm Developer's Guide: (helm-devel) +#+texinfo_dir_desc: Helm Developer's Guide + +* Footnotes + +[fn:1] http://blog.codinghorror.com/learn-to-read-the-source-luke/ + +[fn:2] https://github.com/emacs-helm/helm/blob/master/helm-info.el + +[fn:3] https://github.com/emacs-helm/helm/blob/master/helm-types.el + +#+EXCLUDE_TAGS: TOC_3 |