Displaying concepts
Lem has some concept for displaying. This document is a result of a survey about them.
Logical concept for physical displaying areas dealt by frontends; like ncurses, electron and so on.
So display has no data representation in source codes, it appears as a word.
(For now) there is one display per one frontend.
Lem doesn’t touch displays directly but touch these via method like lem-if:display-width
.
Source (for instance): /frontends/ncurses/ncurses.lisp
UI parts like minibuffers or popup windows or normal buffers, in the display(s?). They’re defined in each frontend and Lem doesn’t touch them directly.
Source (for instance): /frontends/ncurses/ncurses.lisp
Lem’s low-level displaying area. Each screens correspond to its view. Each screen has its position and size, and may have modeline.
Source: /lib/core/interface.lisp
Lem’s displaying area.
Windows have its screen, buffer and view-point
(maybe viewport).
Split area is implemented with windows and window-node
structure.
window-node
has split type (:hsplit and :vsplit), car and cdr.
Root area is stored in *window-tree*
special variable.
*window-tree*
may be an window or an window-node, by which Lem manages nest of displaying area.
If it’s an window-node it means Lem’s displaying area is splitted,
when its child (car or cdr) is an window-node that child area is also splitted, and so forth…
Source: /lib/core/window.lisp
Frames are the concept and the structure that represent whole window configuration in a display. It is introduced by this pull-request https://github.com/cxxxr/lem/pull/500 .
Note the special variable lem::*display-frame-map*
.
This mapping correspond from display-thing (for now, it’s lem:implementation
) to a frame.
It is for multi frame support in the near future.
Source: /lib/core/frame.lisp
The name frame
appears some place of /lib/core/window.lisp
as arguments of adjust-windows
.
This function seems to do a process when frame size is changed, e.g. terminal resizing.
In this case, frame
means a display described above.
Virtual frames are introduced by frame-multiplexer (called “fm-mode” at this PR).
Because, in frame-multiplexer, it is needed to store frames and to switch between them.
One virtual frame store multiple frames in it, and modify lem::*display-frame-map*
when like frame switching.
Source: /lib/core/frame-multiplexer.lisp
These displaying mechanisms are used in text buffers.
Setting the mark allows to start a visual selection of text.
It also allows to add a point to the mark ring, which further allows to circle back between marks.
It can be done with
(set-current-mark (current-point))
set-cursor-mark
is similar, and in addition it pushes the point to the mark ring.
For example, this is how the command mark-set-whole-buffer
is implemented:
(define-command (mark-set-whole-buffer (:advice-classes jump-cursor-advice)) () ()
"Select the whole buffer as a region."
(buffer-end (current-point)) ;; move the current point
(set-current-mark (current-point)) ;; set the mark to the current point, start visual selection.
(buffer-start (current-point)) ;; move the point, effectively selecting all text.
(message "Mark set whole buffer"))
Overlays allow to complement the buffer’s text with all sort of metadata. It is visible, it appears “around” the text (in the left margin, in the emply line space on the right…), but it isn’t part of the buffer’s content. It can be temporary.
For example, when your compile a Lisp form, you see a fast visual effect around it: it was an overlay that was created, and quickly deleted. When you evaluate a Lisp form, its result is showed on its right: it is an overlay.
An overlay is defined by a class, with slots: start, end, temporary, buffer, attribute, plist, alivep
.
We access those slots with:
overlay-start
overlay-end
overlay-attribute
overlay-buffer
etc
The class line-endings-overlay
extends a base overlay with more slots: text, offset
.
See:
When an overlay is created (with make-overlay
and the other corresponding
constructor functions), it is automatically pushed to the buffer’s
list of overlays, and is displayed.
;;; src/overlays.lisp
(make-overlay start end attribute
&key (start-point-kind :right-inserting)
(end-point-kind :left-inserting)
temporary)
For example:
LEM> (defparameter myoverlay (make-overlay (current-point) (current-point) 'syntax-warning-attribute))
;; => #<LEM-CORE::OVERLAY {1206A500A3}>
LEM>
To best see this snippet’s effect, run this in the REPL, in the lem
package. You will see the REPL output and the last REPL prompt printed
in red. This is because the overlay is taking effect. Continue typing,
it still has effect.
You can try with another attribute, for example 'region
will give you a light grey background.
Here we create an overlay at the line end with some text to display:
(make-line-endings-overlay (current-point) (current-point) 'syntax-warning-attribute :text "hello")
This shows “hello” in red (the color of our attribute) at the end of the current line.
Learn about attributes below.
To delete a given overlay, use delete-overlay
with your overlay object as argument.
LEM> (delete-overlay myoverlay)
To delete all the current buffer overlays, use (clear-overlays)
(you
can also call this with M-:
).
Attributes define how to display rich text: bold, italic, foreground and background colors…
They are created with define-attribute name (specs)
where specs
is a list of any number of properties for a :light
theme, a :dark
one, or all (t
).
(define-attribute filename-attribute
(t :foreground :base0D))
Built-in attributes are defined in src/attributes.lisp
, such as:
;; built-in attributes
(define-attribute cursor
(:light :background "black")
(:dark :background "white"))
(define-attribute region
(:light :foreground nil :background "#eedc82")
(:dark :foreground nil :background "blue"))
(define-attribute syntax-warning-attribute
(t :foreground "red"))
(define-attribute syntax-comment-attribute
(:light :foreground "#cd0000")
(:dark :foreground "chocolate1"))
(define-attribute document-header1-attribute
(:light :foreground "#000000" :bold t)
(:dark :foreground "#FFFFFF" :bold t))
and so on.
Text properties allow to store metadata in buffer strings. They are invisible.
For example, Legit’s status buffer displays a line of text Untracked Files
with the built-in attribute :header-marker
, like this:
(put-text-property start point :header-marker t))
;; ^^ attribute ^^^ attribute value
Thanks to it, we can implement a keyboard shortcut to navigate from one heading to another.
More functions:
(defun text-property-at (point prop &optional (offset 0))
"Return the property of 'prop' at the offset position from 'point' to 'offset'."
(defun remove-text-property (start-point end-point prop)
"Remove one property from text from START-POINT to END-POINT.
The third argument PROP is a property to remove."
See more: