Go Back

Contributor's Notes / FAQ

If you are interested in contributing, this is the place to start.

Table of Contents

1. What can I do to help?

2023 will be the year of Fern. See the Fern Development Roadmap.

2. Where does the development take place?

On sourcehut, here:

Sourcehut is streamlined for the mailing-list workflow.

3. How do I use the mailing lists?

There are a lot of resources provided by sourcehut itself.

Before that, one note on how sourcehut operates: everything is grouped into projects and projects may share the mailing lists and ticket trackers and even repositories.

The projects are listed here:

and the mailing lists are here:

To start a discussion, send a mail to:

  • ~project-mage/<dev>@lists.sr.ht (remove the angle brackets),

and to file a bug, send a mail here:

  • ~project-mage/<tickets>@todo.sr.ht (remove the angle brackets),

(If you are unsure that filing a ticket is what you want, just use the mailing list instead.)

To subscribe, you have to send a subscription mail to the address with +subscribe appended to the name of the list (alternatively, just find the button on the page).

So, for development, there's just one mailing list and one ticket tracker, even though there are many repositories.

To contribute code, you need to send a patchset to the mailing list. There are two ways to go about this: either use the sourcehut's web UI or git's send-mail command:

While at it, you might also want to check out:

And here's another (non-sourcehut-related) guide that you might find useful:

4. Why use mailing lists?

For one, there's no need to register (even if you send patches, in which case you can stick to git send-mail).

And with some setup like mbsync + msmtp + notmuch, you don't have to use a browser. Here's a demo of notmuch:

One can automate patch extraction from a thread, see Notmuch FAQ. Of course, any other setup is viable, even if you just stick to the web browser. But in case you are interested, here are some tutorials (but I warn you that the setup might easily consume a couple of evenings of your time):

5. What are the requirements for code?

  • 80 chars per line. It's fine for closing parens to exceed this limit, though. Comments should be within the limit (for now anyway).

    In Emacs, you can do

    (custom-set-default 'fill-column 80)

    or

    (set-fill-column 80).

  • The standard LOOP construct is not to be used anywhere. Only iterate. It's extensible and is way more pleasant to work with. In addition, the project uses a special itr alias (instead of iter).
  • There should be test coverage where sensible. Parachute is used. The project uses a custom test macro. Look at some existing examples to get the hang of it, it's pretty handy. One thing to note is that it doesn't execute at the compilation step, only when you manually evaluate it or call (parachute:test :some-package). This means that merely loading a project does not execute the tests.

    A function you may find convenient to bind (for Sly):

    (defun sly-test-local-package ()
      (interactive)
      (sly-interactive-eval "(parachute:test cl:*package*)"))
    
  • The mage and fern repositories consist of submodules and don't carry any actual code themselves. Submodules are independent repositories. For instance, fern contains geometry as a submodule. When you commit a change in geometry, then git status on fern will indicate that one of its submodules has changed. Just ignore that, no need to commit and push those.
  • The project uses some unicode symbols, such as λ for lambda. To deal with input, you could install xah-math-input.

    (use-package xah-math-input)

    With some binding like this:

    (global-set-key (kbd "M-5") 'xah-math-input-change-to-symbol)

    you can then type lamdba, do M-5 (or what have you), and the symbol will get converted to λ.

    Alternatively, just add this to your emacs:

    ;; https://www.emacswiki.org/emacs/AbbrevMode
    ;; note that abbreviations must be lower-case and non-punctuation, if you add
    ;; your own
    (define-abbrev-table
        'global-abbrev-table
        '(("xi" "Ξ")
          ("zeta" "ζ")
          ("gv" "←")
          ("sv" "→")
          ("uu" "↑")
          ("dd" "↓")
          ("ddd" "↓↓")
          ("ld" "λ")
          ("0vec" "0ᵥ")
          ("1vec" "1ᵥ")
          ("xunit" "x̂")
          ("yunit" "ŷ")))
    
    (add-hook 'lisp-mode-hook 'abbrev-mode)
    (add-hook 'text-mode-hook 'abbrev-mode)
    (add-hook 'minibuffer-mode-hook 'abbrev-mode)
    
    (global-set-key (kbd "M-1") 'expand-abbrev) ; for other modes
    

    Now, when you type xi, it will automatically turn Ξ. I might be adding some other abbreviations here later on. Only some of these are relevant at the moment, the vector ones and the lambda, I will document the rest later.

    (Note: I don't know why this doesn't work with isearch. I use helm-do-grep-ag, where it's fine. If you get it working, let me know.)

  • The project offers a custom readtable that allows lambda shortcuts:

    (mage.utils:in-standard-readtable)
    
    (#2λ(+ x0 x1) 1 3)      ; => 4
    

    mage.utils also exports λ:

    ((λ (x y) (+ x y)) 5 2) ; => 7
    

    The readtable example requires you to evaluate the readtable expression before you can interactively use it in any given Emacs buffer. So, if you are using Sly, you may find this of use:

    (defun try-mage-readtable ()
      "Find if the mage readtable is declared in the file and eval it."
      (when (string-match (rx "in-standard-readtable)")
                          (buffer-string))
        (sly-interactive-eval "(mage.utils:in-standard-readtable)")))
    
    (defadvice sly-eval-region (before readtable activate)
      (try-mage-readtable))
    
    (defadvice sly-compile-region (before readtable activate)
      (try-mage-readtable))
    
  • Code sectioning: you can use ;; * Section name syntax to partition the code, and the number of stars indicates depth:

    ;; * Commentary
    
    ;; This or that is accomplished in this file...
    
    ;; * Some big section
    
    <some code>
    
    ;; ** A few functions belonging to the big section
    
    <the functions>
    
    ;; * Another section
    
    <...>
    
  • If you are using Sly, the utils have a function mage.utils::repl which sends an object to the REPL so you can easily inspect it (instead of using print statements).
  • You can use the `function-or-variable-name-or-value' markup in the docstrings (and Emacs autohighlights these). It's also customary within the docstrings to designate argument names with CAPITALS.
  • It's not necessary, but if you want to specify the types of the arguments that the function takes or of the return value, you can use the serapeum:-> macro (place it right above the definition of the function). Apply sparingly, only where it really is useful.
  • Emacs's numeric subscripts: an efficient hack to display subscripts, e.g. x0 as x₀, you get the idea. Just for the comfort of reading.

    (defcustom *subscript-symbol-exceptions*
      '(prin1 x11 SDL2 sdl2)
      "What sexp-at-point must not equal to for a number to be shown as subscript.")
    
    (defun setup-subscripts-display ()
      "Make numbers next to identifiers (like x0 or y4) appear like
    subscripts. Uses `prettify-symbols-mode'."
      (setf prettify-symbols-unprettify-at-point 'right-edge)
      (mapc (lambda (x) (push x prettify-symbols-alist))
            '(("0" . "₀")
              ("1" . "₁")
              ("2" . "₂")
              ("3" . "₃")
              ("4" . "₄")
              ("5" . "₅")
              ("6" . "₆")
              ("7" . "₇")
              ("8" . "₈")
              ("9" . "₉")))
      (cl-macrolet ((save-start (&rest body) `(save-excursion (goto-char start) ,@body))
                    (digit-p (c) `(<= 48 ,c 57)))
        (setf prettify-symbols-compose-predicate
              (lambda (start end _match)
                (if (save-start (digit-p (following-char)))
                    (or
                     ;; #nλ subscripts
                     (and (save-start ; #
                           (loop while (digit-p (following-char))
                                 do (backward-char))
                           (eql (following-char) ?#))
                          (save-start ; λ
                           (forward-char)
                           (eql (following-char) ?λ)))
                     ;; regular subscripts
                     (and (save-start ; excepted symbols
                           (not (member (sexp-at-point) *subscript-symbol-exceptions*)))
                          (save-start ; on the left, ensure there's some letter
                           (loop while (digit-p (following-char))
                                 do (backward-char))
                           (string-match-p (rx letter) (char-to-string (following-char))))
                          (save-start ; on the right, there must be not other letters
                           (loop while (digit-p (following-char))
                                 do (forward-char))
                           (string-match-p (rx (or white ")" 10 "."))
                                           (char-to-string (following-char))))))
                  ;; default predicate
                  (prettify-symbols-default-compose-p start end _match)))))
      (prettify-symbols-mode))
    
    (dolist (h '(lisp-mode-hook emacs-lisp-mode-hook))
      (add-hook h (lambda () (setup-subscripts-display))))