2020-10-24 18:59:50 +01:00
|
|
|
;;; Cube --- Cube library of FG42 -*- lexical-binding: t; -*-
|
|
|
|
;;
|
2024-02-18 16:08:02 +00:00
|
|
|
;; Copyright (c) 2010-2024 Sameer Rahmani & Contributors
|
2020-10-24 18:59:50 +01:00
|
|
|
;;
|
|
|
|
;; Author: Sameer Rahmani <lxsameer@gnu.org>
|
2022-12-19 21:07:48 +00:00
|
|
|
;; URL: https://devheroes.codes/FG42/FG42
|
2024-04-05 21:04:36 +01:00
|
|
|
;; Version: 4.0.0
|
2020-10-24 18:59:50 +01:00
|
|
|
;;
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
;;
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
;;
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;
|
|
|
|
;;; Commentary:
|
|
|
|
;; Cubes are the building blocks of any `FG42' editor. Each `cube' is a
|
|
|
|
;; unit which defines different abilities in a deterministic and idempotent
|
|
|
|
;; way. Cubes are composable and a composition of cubes creates an editor.
|
|
|
|
;;
|
|
|
|
;;; Code:
|
2020-10-24 21:44:12 +01:00
|
|
|
(require 'seq)
|
2020-10-24 18:59:50 +01:00
|
|
|
(require 'fg42/utils)
|
2023-06-10 20:47:55 +01:00
|
|
|
(require 'fg42/themes)
|
2021-01-29 00:16:46 +00:00
|
|
|
|
2021-12-19 15:18:21 +00:00
|
|
|
(defvar fg42/after-cubes-setup-hook nil
|
2022-09-12 15:24:31 +01:00
|
|
|
"A hook that will be run after all the active cubes got setup.
|
2023-05-31 23:37:58 +01:00
|
|
|
This hook is dedicated for the codes that need to do stuff based on other cubes
|
2022-09-12 15:24:31 +01:00
|
|
|
presence. With this hook we eliminate the need for cube ordering.
|
2021-12-19 15:18:21 +00:00
|
|
|
|
|
|
|
It will be called in the `fg42-config' and the proper way to use
|
2022-09-12 15:24:31 +01:00
|
|
|
it is to use `fg42/after-cubes' macro.")
|
2021-12-19 15:18:21 +00:00
|
|
|
|
2021-01-29 00:16:46 +00:00
|
|
|
|
2021-10-12 00:35:05 +01:00
|
|
|
(defvar fg42/available-cubes '()
|
|
|
|
"A list of all the registered cubes.")
|
|
|
|
|
2023-06-10 20:47:55 +01:00
|
|
|
(defvar fg42/-cubes-body-hook nil
|
|
|
|
"A Hook that is internal to FG42 and will be used to run the cub bodies.
|
|
|
|
The execution happens after Emacs is initialized.")
|
|
|
|
|
2021-10-12 00:35:05 +01:00
|
|
|
|
2023-06-10 22:47:07 +01:00
|
|
|
(defun fg42/extract-props (body-list &optional acc)
|
|
|
|
"Extract the props pairs from BODY-LIST with an optional accumulator ACC.
|
|
|
|
|
|
|
|
It will returen a pair in form of (body . props)."
|
|
|
|
(let ((k (car body-list))
|
|
|
|
(rest (cdr body-list)))
|
|
|
|
|
|
|
|
(if (and k (keywordp k))
|
|
|
|
(fg42/extract-props
|
|
|
|
(cdr rest)
|
|
|
|
(cons (cdr rest) (plist-put (cdr acc) k (car rest))))
|
|
|
|
(cons body-list (cdr acc)))))
|
|
|
|
|
|
|
|
|
2023-06-11 12:47:00 +01:00
|
|
|
(defun fg42/run-cube-after-initialization (f)
|
|
|
|
"Run the given Cube body function F after FG42's initialization.
|
|
|
|
If FG42 is already initialized, just run F."
|
|
|
|
(if (null fg42/initialized)
|
|
|
|
(add-hook 'fg42/-cubes-body-hook f)
|
|
|
|
(funcall f)))
|
|
|
|
|
|
|
|
|
2023-06-10 22:47:07 +01:00
|
|
|
(defmacro defcube (cube-name docs &rest props-n-body)
|
|
|
|
"Define a cube with the given CUBE-NAME, DOCS and a PROPS-N-BODY.
|
|
|
|
|
|
|
|
TODO: Docs"
|
2021-10-12 00:35:05 +01:00
|
|
|
(declare (indent defun) (doc-string 2))
|
2021-04-18 16:10:02 +01:00
|
|
|
|
|
|
|
;; Make sure that props is a plist and contains the `:docs' key
|
|
|
|
;; TODO: Maybe use `cl-check-type' here
|
2021-10-12 00:35:05 +01:00
|
|
|
(when (not (stringp docs))
|
|
|
|
(error "Missing docstring for '%s' cube" cube-name))
|
|
|
|
|
2023-06-10 22:47:07 +01:00
|
|
|
(let* ((parsed-body (fg42/extract-props props-n-body))
|
|
|
|
(body (car parsed-body))
|
|
|
|
(props (cdr parsed-body)))
|
|
|
|
|
|
|
|
(when (not (plist-get props :title))
|
|
|
|
(error "Missing :titel key for '%s' cube" cube-name))
|
|
|
|
|
|
|
|
(let ((complete-props (plist-put props :docs docs))
|
|
|
|
(cube-name-internal (intern (format "%s-internal" cube-name)))
|
|
|
|
;; prop hooks
|
|
|
|
(init-hook (plist-get props :init-hook))
|
|
|
|
(ui-hook (plist-get props :ui-hook))
|
|
|
|
|
|
|
|
(params-var (intern (format "%s-params" cube-name)))
|
|
|
|
(active-var (intern (format "%s-active-p" cube-name)))
|
|
|
|
(pre-lang-server-up-hook (intern (format "%s-pre-lang-server-up-hook" cube-name)))
|
|
|
|
(post-lang-server-up-hook (intern (format "%s-post-lang-server-up-hook" cube-name)))
|
|
|
|
(pre-lang-server-down-hook (intern (format "%s-pre-lang-server-down-hook" cube-name)))
|
|
|
|
(post-lang-server-down-hook (intern (format "%s-post-lang-server-down-hook" cube-name)))
|
|
|
|
(pre-init-hook (intern (format "%s-pre-init-hook" cube-name)))
|
|
|
|
(post-init-hook (intern (format "%s-post-init-hook" cube-name)))
|
|
|
|
(post-init-hook (intern (format "%s-post-init-hook" cube-name)))
|
|
|
|
|
|
|
|
(flag-var (or (plist-get props :flag) cube-name))
|
|
|
|
(flag-docstring-var (or (plist-get props :flag-doc)
|
|
|
|
(format "The flag to enable/disable the '%s' cube." cube-name)))
|
|
|
|
(flag-default (plist-get props :flag-default))
|
|
|
|
(flag-conflict (plist-get props :conflicts-with))
|
|
|
|
(no-flag? (or (plist-get props :no-flag) ())))
|
|
|
|
|
|
|
|
(add-to-list 'fg42/available-cubes cube-name)
|
|
|
|
|
|
|
|
`(progn
|
|
|
|
|
|
|
|
;; Create a new flag for each cube to control the cubes systemwide.
|
|
|
|
(when (not ,no-flag?)
|
|
|
|
(defflag ,flag-var ,flag-docstring-var ,flag-default))
|
|
|
|
|
|
|
|
;; Params variable contains the list of params the is passed to
|
|
|
|
;; the current cube call
|
|
|
|
(defvar ,params-var nil
|
|
|
|
,(format "Parameters for the '%s' cube." cube-name))
|
|
|
|
|
|
|
|
;; * Hooks
|
|
|
|
|
|
|
|
;; This hook can be used by others to run code just before running that
|
|
|
|
;; cube's body
|
|
|
|
(defvar ,pre-init-hook nil
|
|
|
|
,(format "The hook that runs befor the '%s' cube initialization." cube-name))
|
|
|
|
|
|
|
|
|
|
|
|
;; This hook can be used by others to run code just after the body of
|
|
|
|
;; the cube
|
|
|
|
(defvar ,post-init-hook nil
|
|
|
|
,(format "The hook that runs after the '%s' cube initialization." cube-name))
|
|
|
|
|
|
|
|
;; TODO: Move language server related hooks to lang-server
|
|
|
|
;; TODO: Provide a way to let different parts of the
|
|
|
|
;; codebase to create cube hooks
|
|
|
|
|
|
|
|
;; ** Language Server
|
|
|
|
;;; The hook that enables users to change the language server configuration
|
|
|
|
;;; of the current cube before activating the server
|
|
|
|
(defvar ,pre-lang-server-up-hook nil
|
|
|
|
,(format "The hook that runs befor the '%s' cube's language server activates ." cube-name))
|
|
|
|
|
|
|
|
;;; The hook to do any post configuration for the lang server of the cube
|
|
|
|
(defvar ,post-lang-server-up-hook nil
|
|
|
|
,(format "The hook that runs after the '%s' cube's language server activates." cube-name))
|
|
|
|
|
|
|
|
;;; The hook to run code just before the language server is about to shutdown
|
|
|
|
(defvar ,pre-lang-server-down-hook nil
|
|
|
|
,(format "The hook that runs befor the '%s' cube's language server shuts down." cube-name))
|
|
|
|
|
|
|
|
;;; The hook to run code after the language server successfully shuts down
|
|
|
|
(defvar ,post-lang-server-down-hook nil
|
|
|
|
,(format "The hook that runs after the '%s' cube's language server shuts down." cube-name))
|
|
|
|
|
|
|
|
;; This way we can bypass the flag system if we really really want to.
|
|
|
|
(defun ,cube-name-internal (params)
|
|
|
|
(if (or (not (boundp (quote ,active-var)))
|
|
|
|
(not ,active-var))
|
2023-06-10 20:47:55 +01:00
|
|
|
(progn
|
2023-06-10 22:47:07 +01:00
|
|
|
;; Mark this cube as active
|
|
|
|
(setq ,active-var t)
|
|
|
|
|
|
|
|
;; Set the parameters in CUBE-NAME-params to be accessable
|
|
|
|
;; in the body
|
|
|
|
(setq ,params-var params)
|
|
|
|
|
|
|
|
;; Run the pre init hook
|
|
|
|
(run-hooks (quote ,pre-init-hook))
|
|
|
|
|
|
|
|
(fg42/info "Initializing '%s' cube." (quote ,cube-name))
|
|
|
|
;; Run the body
|
|
|
|
(let ((result (progn ,@body)))
|
|
|
|
;; Run the post init hook
|
|
|
|
(run-hooks (quote ,post-init-hook))
|
|
|
|
result))
|
|
|
|
(fg42/info "The '%s' cube is already active." ',cube-name)))
|
|
|
|
|
|
|
|
(defun ,cube-name (&rest params)
|
|
|
|
(interactive)
|
2023-06-11 12:47:00 +01:00
|
|
|
(let ((fg42/---f-sym
|
|
|
|
(lambda ()
|
|
|
|
(when (not (null ,ui-hook))
|
2024-02-11 12:45:53 +00:00
|
|
|
(when-flag ,flag-var
|
|
|
|
(add-hook 'fg42/ui-hook #',ui-hook)))
|
2023-06-11 12:47:00 +01:00
|
|
|
|
|
|
|
(when (not (null ,init-hook))
|
|
|
|
(funcall #',init-hook params))
|
|
|
|
|
|
|
|
;; Run the cube internal after initialization or
|
|
|
|
;; if Emacs is already up, just run it.
|
|
|
|
(fg42/run-cube-after-initialization
|
|
|
|
(lambda ()
|
|
|
|
(,cube-name-internal params))))))
|
|
|
|
|
2023-06-10 22:47:07 +01:00
|
|
|
(if ,no-flag?
|
2023-06-11 12:47:00 +01:00
|
|
|
;; If no flag is need to control this cube
|
|
|
|
(funcall fg42/---f-sym)
|
2023-06-10 22:47:07 +01:00
|
|
|
;; Otherwise check for the flag to be active
|
|
|
|
(if-flag ,flag-var
|
2023-06-11 12:47:00 +01:00
|
|
|
(funcall fg42/---f-sym)
|
2023-06-10 22:47:07 +01:00
|
|
|
(fg42/info "The flag for '%s' cube is disabled. Skiping." ,(symbol-name cube-name))))))
|
|
|
|
|
|
|
|
;; Set the symbol-plist of the cube-name to its props
|
|
|
|
(setplist ',cube-name ',complete-props)))))
|
2020-10-24 18:59:50 +01:00
|
|
|
|
|
|
|
|
2021-12-19 15:18:21 +00:00
|
|
|
(defmacro fg42/after-cubes (&rest body)
|
|
|
|
"Add the BODY to `fg42/after-cubes-setup-hook' hook."
|
|
|
|
(declare (indent defun))
|
|
|
|
`(add-hook 'fg42/after-cubes-setup-hook
|
|
|
|
(lambda ()
|
|
|
|
,@body)))
|
|
|
|
|
|
|
|
|
2023-06-01 00:25:05 +01:00
|
|
|
(defmacro ->cube (pkg docs &rest body)
|
|
|
|
"A wrapper to create a cube that use only a PKG.
|
2023-06-11 12:47:00 +01:00
|
|
|
It passes the BODY to `fpkg/use'.
|
|
|
|
And passes DOCS to `defcube' as the cube documentation."
|
2023-06-01 00:25:05 +01:00
|
|
|
(declare (indent defun) (doc-string 2))
|
|
|
|
`(defcube ,(intern (format "fg42/%s-cube" pkg))
|
|
|
|
,docs
|
2023-06-10 22:47:07 +01:00
|
|
|
:title ,(format "%s cube" pkg)
|
|
|
|
:flag ,pkg
|
|
|
|
:flag-default t
|
2023-06-01 00:25:05 +01:00
|
|
|
(fpkg/use ,pkg ,@body)))
|
|
|
|
|
|
|
|
|
2020-10-24 18:59:50 +01:00
|
|
|
(provide 'fg42/cube)
|
|
|
|
;;; cube.el ends here
|