Introduction
- Data Types
- Number
- String
- Symbol
- Pair, List and Association List
- Array
- Dictionary
- Quote and Quasiquote
- Procedures and Macros
- Procedure Definition
- Procedure Arguments
- Macros
- Core Procedures
- Special Forms
- Math
- Utilities
- TeX Writer LISP Extension
- Buffers
- Buffer Modes and Hooks
- Buffer Procedures
- Text Editing
- Built-in editing procedures
- Customize UI and Appearances
- Customize toolbars
- Customize keybars
- Add Interactive Commands
- Add Key Bindings
- Colors and Faces
- Add Token Definitions
- Appendix: sample twinit.l
(This document only applies to iOS TeX Writer 5.0+. Last updated: 2018/4/22.)
The LISP language used in TeX Writer is small. If you are already familiar with emacs or scheme, then you should be really quick to pick it up. If you are an absolute beginner, don't worry, it won't take much learning to customize the looks of TeX Writer. Skip to the end of this document, there are quick examples that show you how to set colors.
All your customizations should be put in a file twinit.l
,
which must be saved in the top Local
directory.
TeX Writer loads this file when it starts.
If you make any change to this file,
you can reload it right away using the "LOAD" button on toolbar.
Data Types
LISP supports 6 fundamental data types: number, string, symbol, pair, array and dictionary.
Number
Internally, all numbers are stored as a double precision floating number(Same as Javascript). For two numbers to be equal, they must match every bit.
996
3.14
0xfeff
(integer? 2000) => true
(= 1.0 1) => true
(eq? 1.0 1) => true
Here are common number operations.
number?
integer?
(+ a b ...)
(- a b ...)
(* a b ...)
(/ a b ...)
(< a b ...)
(> a b ...)
(= a b ...)
(<= a b ...)
(<= a b ...)
String
Strings are immutable. Manipulation of strings, like (concat), always creates new instances. String is internally represented as UTF8 bytes.
"how are you?"
"hello
world!" => "hello\nworld!"
"\t\r\n"
(eq? "hello" "hello") => true
(concat str1 str2 ...)
Return concatenation of strings.
(string-compare str1 str2)
Similar to C funtion strcmp()
.
(string-length str)
UTF8 byte count, not unicode chars.
(string->number str)
Convert a string to number. If str
is not a numeric string, returns undefined
.
(string? obj)
(substring str begin &optional end)
Symbol
Symbols are names for variables and procedures.
Some symbols evaluate to themselves, and some are reserved for special
purpose, like unquote
.
x
average
Predefined symbols. constant symbols. Evaluates to themselves.
symbols beginning with :
are also constants, mainly for used as key.
true
false
undefined
:this-is-a-key
&optional
undefined
is a special constant symbol. (car ())
returns undefined
.
Accessing out-of-bound indexes of an array, or non-existing keys in a
dictionary, results in undefined
as well.
But remember that trying to reference an undefined variable will results
in error. You can use (defined? varname)
safely though, because defined?
is a special form that works for undefined symbols.
(defined? unknown-var) => false
unknown-var => error
(define a undefined)
(defined? a) => false
The following symbols can't be redefined:
true
false
undefined
else
unquote
(define var expr)
(set! var expr)
(string->symbol str)
(symbol? obj)
Pair, List and Association List
Pairs are immutable. There is no set-car!
nor set-cdr!
. If you want to change a pair, you need to reconstruct a whole new copy. It's impossible to make a ring with pairs, except that empty list ()
is defined as (undefined . ())
, which is a pair whose cdr points to itself.
Since LISP code is also made of pairs, this immutability makes it much easier to reason about.
For example, we don't need to worry that static lists returning from a procedure will be modified by caller.
(joe . 6)
(cons 'alice 'bob)
From pairs, we can construct list and association list. Both of them are also immutable.
(1 2 3) equals to (1 . (2 . (3 . ())))
((a b) () (c)) ;; list of lists
Empty list is a special constant. In some scheme implementations, empty list ()
needs to be quoted as '()
. But in our version, the quote is unnecessary.
() => () ;; empty list evaluates to itself
(car ()) => undefined
(cdr ()) => ()
An association list (also known as a-list) is a list of pairs; each pair is an association. The car of a pair is called the key, and the cdr is called the datum.
((one . 1) (two . 2) (three . 3) (four . 19))
(cons a b)
Making a pair.
(car pair)
car of ()
is undefined
.
(cdr pair)
cdr of ()
is also ()
.
(append list another-list)
Return a new list.
(pair? obj)
(list? obj)
Test if obj is a list, that the last pair's cdr is ()
.
(null? obj)
Test if obj is empty list ()
.
(nth list index)
Return nth item of the list. index is 0-based.
(list item1 ...)
Make a list out of provided items.
(length list)
Return the length of a list.
(assoc key alist)
Returns the first association pair for key in alist, comparing key against the alist elements.
(assoc 'r '((a . b) (c . d) (r . x) (s . y) (r . z)))
=> (r . x)
If not found, return undefined
.
Array
Array supports much more efficient random access than list. Also array supports mutable operations.
(array elem1 ...)
Making an array from elements.
(array 1 2 3) can be written as [1 2 3]
.
(array->list arr)
(array-count arr)
(array-get arr index)
(array-set! arr index val)
(array-push arr obj)
(array-pop arr)
(array? obj)
Dictionary
Dictionary supports fast key-value look up. Like arrays, dictionaries are also mutable. It's a better alternative than associated list if there are many elements or if changes need to be made. However, dictionary can only use symbols as keys.
(dict k1 v1 ...)
(dict->list d)
Return an association list of the dictionary key values.
(dict-get d k)
(dict-set! d k val)
(dict? obj)
Quote and Quasiquote
Quotes and Quasiquotes are the source of LISP magic. However, they are difficult to understand for a beginner.
To make program text easier to write and read, we support the following notations. The left side is equivalent to the right side.
'a (quote a)
`a (quasiquote a)
,a (unquote a)
,@a (unquote-splicing a)
(quote obj)
quote
is a special form.
Return obj without evaluating it.
(quote alice) => alice ;; symbol alice is not evaluated, itself is returned.
(quote (1 2 3)) => (1 2 3)
(quasiquote obj)
quasiquote
is similar to quote
, except that it allows you to selectively evaluate elements of the quoted list.
(quasiquote (1 2 ,(+ 1 2))) => (1 2 3)
The comma-expression is a syntactic sugar of unquote. The following is equivalent but harder to read:
(quasiquote (1 2 (unquote (+ 1 2))))
Procedures and Macros
Procedure Definition
(define (average a b)
(/ (+ a b) 2) )
(define average
(lambda (a b) (/ (+ a b) 2)) )
Usually, they are almost equivalent ways of defining a procedure. More strictly, however, you should write:
(define average (lambda (&label average a b) ...))
Including the label average
in arguments helps our procedure to resist external redefinition
of average
. Inside procedure body, average
will be referring to the procedure itself.
Proper tail recursion is supported.
Procedure Arguments
Procedure arguments are evaluated from right to left.
(average a b)
b
is evaluated first, then a
. Other LISP implementation may use different order. When you design a program, you should not rely on a particular convention. If you want to impose a left to right order, use (begin ...)
.
For argument modifiers, we use the emacs lisp style rather than scheme.
&optional
&rest
&key
&label --- Only available in TeX Writer LISP.
&label
is a special modifier that associates a following name
with current procedure being
defined, so that it's possible to refers to itself inside lambda body.
(lambda (&label self n)
(if (> n 1)
(self (- n 1)))
Macros
Macros works like procedures, except that the arguments are passed in without evaluation, and the returned object will be evaluated again to serve as final result. Macros usually relies on quasiquotes.
(defmacro (when pred &rest body)
`(if ,pred
(begin ,@body)))
(when (> 2 1) (println "This is a sane universe."))
Core Procedures
Special Forms
(quote obj)
(quasiquote obj)
(if condition then-form else-form)
If evaluated value of condition is not false
, then-form is evaluated and the result returned, otherwise else-form is evaulated. If condition evaluates to false
but else-form is not provided, then undefined
is returned.
(cond clause ...)
Unlike if
, cond
can take one or more condition tests.
clause is like: (condition then-form ...).
It's possible to provide a fallback clause that will always be executed:
(else ...)
(true ...)
As long as condition doesn't evaluate to false
, this clause will always be evaluated and the result taken as the final result of cond
.
(not obj)
If obj is false
, then return true
, otherwise return false
.
(and ...)
Evaluates arguments from left to right.
If any of arguments evaulated to false
, then execution is terminated, and false
if returned. If none of the arguments evaluates to false
, then the last evaluated result is returned. If no arguments provided, true
is returned.
(or ...)
Evaluates arguments from left to right.
If any of arguments evaulated to a value other than false
, then execution is terminated, and the value is returned. If every arguments evaluates to false
, then false
is returned. If no arguments provided, false
is returned.
(let ((id val-expr) ...) body-expr ...)
(define x 1)
(let ((a x)) (+ a a))
Equals to
((lambda (a) (+ a a)) x)
let
also has another form:
(let label ((id val-expr) ...) body-expr ...))
In this version, label is bound to the lambda procedure itself. It's convenient to do looping. For example, the following code sums from 100 to 1.
(let loop ((sum 0) (n 100))
(if (= n 0)
sum
(loop (+ sum n) (- n 1))))
Equals to
((lambda (&label loop sum n)
(if (= n 0)
sum
(loop (+ sum n) (- n 1)))) 0 100)
(begin ...)
Arguments are evaluated from left to right. And the last evaluated result is returned.
(set! var value)
Assign value to var.
Math
These math functions are modeled after libc standard functions.
(abs x)
Return the absolute value of a floating-point number x.
(acos x)
Return the principle value of the arc cosine of x. The result is in the range [0, pi].
(asin x)
Return the principal value of the arc sine of x. The result is in the range [-pi/2, +pi/2].
(cos x)
Return cosine of x (measured in radians).
(ceil x)
Return the smallest integral value greater than or equal to x.
(floor x)
Return the largest integral value less than or equal to x.
(round x)
Return the integral value nearest to x round- ing half-way cases away from zero.
(sin x)
Return the sine of x (measured in radians).
(sqrt x)
Return the non-negative square root of x.
(tan x)
Return the tangent of x (measured in radians).
(exp x)
Return e**x, the base-e exponential of x.
(log x)
Return the value of the natural logarithm of argument x.
(truncate x)
Return the integral value nearest to but no larger in magnitude than x.
(mod x y)
Return the floating-point remainder of x / y.
Specifically, the functions return the value x-i*y
, for some integer
i such that, if y is non-zero, the result has the same sign as x and
magnitude less than the magnitude of y.
(atan x &optional y)
When y is omitted, return the principal value of the arc tangent of x. The result is in the range [-pi/2, +pi/2].
If y is presented, return the principal value of the arc tangent of x/y, using the signs of both arguments to determine the quadrant of the return value.
Utilities
(eq? a b)
Perform equivalence check:
- If a and b is of different type, return
false
; - If they are numbers, then they must has exactly same numeric value.
- If they are strings or symbols, then they must have same content.
- Otherwise, they must refer to the same object in memory.
(eval obj)
(apply proc args)
(format obj &key precision &key width)
Formatting an obj into a string.
obj is either a number or string.
If width
is larger than actual representation,
then padding is added. For numbers, whitespaces are left padded;
for strings, whitespaces are padded on the right.
(format (/ 100 3) :precision 3 :width 8) => " 33.333"
(format "Hello" :width 8) => "Hello "
(print arg1 ...)
Output provided arguments. println
will output a newline character
after all arguments are printed.
(load filename)
Load a file relative to current file running this expression.
(date &optional format unixtime)
Arguments:
format
: see strftime()unixtime
: see time(), seconds from 1970-1-1 00:00:00 UTC.
Returns a datetime string. Examples:
(date "%F %T") => 2018-03-03 12:00:00
(time)
Return system unix timestamp.
TeX Writer LISP Extension
Unlike preceding sections, procedures in this section are implemented inside TeX Writer. They are not part of the core language.
Typically you write TeX Writer LISP inside twinit.l
, which is then loaded
when app starts. You must put
twinit.l
in your Local
folder root.
Buffers
TeX Writer borrows the concept of buffer from Emacs. You can think of buffer as an opened text file. However, buffers are not only about text files. Opened PDF documents, images, websites, are all called buffers despite that you can not edit them.
There are also special buffers:
*Buffers*
is a list of all available buffers.*Messages*
buffer are reserved for holding messages generated by TeX Writer or your script. It's not editable.*Recent Files*
buffer showing a list of recently opened files
These special buffers can't be closed.
Buffer Modes and Hooks
Buffer mode includes the configuration of interactive commands, key bindings, toolbar buttons, syntax tokens, keybar buttons. Different file types require different modes.
When opening a file, TeX Writer will compare filename against auto-mode-alist
to decide which mode to apply.
(define auto-mode-alist '())
(add-to-list auto-mode-alist '("\\.tex$" . tex-mode))
(add-to-list auto-mode-alist '("\\.bib$" . bib-mode))
tex-mode
is the name of a mode function that can be applied to set up configuration of a TeX file buffer.
Hooks are run at the end of each mode.
(add-hook tex-mode-hook (lambda ()
;; Do your additional setup
))
When you add an interactive command for a particular file type, you should add it in a hook.
Colors and faces are global. You should not customize the appearance in mode hook.
Buffer Procedures
(buffer-file-name)
File path of current buffer.
(buffer-url)
URL of current buffer.
(message msg)
Show msg
in the echo area, also append it to *Messages*
buffer with a timestamp.
(save)
Save current buffer. Bound to "command-s".
(open-url url)
Open a url in app.
(system-open url)
Different from open-url
, this procedure opens a web url in external browser.
If url
is a file path, then shows an "Open in.." popover dialog.
(open path)
Open a file.
(save-as path)
Save current buffer to path
, also update the buffer title to new name.
Text Editing
Typing on touch screen is hard. By exposing its core editing functions, TeX Writer encourages you to define powerful commands to insert text automatically.
Let's begin with a real example --
the begin-env
command you can find in the tex mode.
It inserts a pair of \begin{xxx}
and \end{xxx}
,
leaving a blank line in between, and the caret on the blank line.
Its implementation is:
1 (define (begin-env env)
2 (insert "\\begin{")
3 (insert env)
4 (insert "}")
5 (insert-newline)
6 (insert-newline)
7 (insert "\\end{")
8 (insert env)
9 (insert "}")
10 (goto-line (- (line-number-at-pos (point)) 1)))
Line 1 declares the command name
and the only argument it takes --
an environment name.
Line 2, 3 and 4 together insert "\begin{env}".
Note that in line 2 two backslashes produce
only one character.
Line 5, 6 insert two newline characters, just as if
you type the return key twice. The result is
an empty line between begin and end,
where the actual body goes.
Line 7, 8 and 9 insert "\end{env}".
Line 10 looks complicated,
but if we hide the details
then it is actually saying go to previous line,
which is the empty line we've just created.
previous-line is the result of
(- (line-number-at-pos (point)) 1)
}, where
(point)returns the current caret position, and
(line-number-at-pos (point))` returns
the current line number.
Now we have defined the begin-env
command,
we need to register it before we can actually
use it from the command bar.
The interactive
function registers
a command to the command bar:
(interactive 'begin-env "Environment:%s"
'("document" "enumerate" "figure" "tabular"
"verbatim" "center"
"align" "align*"
"display" "quote" ))
The first argument of interactive
is the command name, which identifies
the command you are adding and will be displayed
on the command bar.
The name should be preceded with a single quote.
If your command doesn't take any argument, then
you are done now.
In our case, begin-env
command takes one argument,
so we need to set up a prompt for receiving input.
Following the command name, "Environment:\%s"
defines the prompt string and the type of input
we are expecting. \%s
is the indicator
for string.
The last part is a list of available suggestions.
They are shown on the right side of the command bar
before you start to type.
If one of them happens to be what you want,
simply tap on it.
Then as you type, unmatched suggestions will disappear,
leaving only the ones with same prefix
as your input so far.
Unlike begin-env,
goto-line
requires a numeric argument as the line number:
(interactive 'goto-line "Goto line:%d"
(lambda () (list 1 (line-max))))
%d
means numeric input.
The suggestions part for goto-line
is a procedure.
It means that suggestions are calculated on the fly.
We can see that the final result is a list of two numbers
--- first line and last line.
Built-in editing procedures
Here is the list of built-in functions that you can use to define your own commands.
(point-max)
The maximal value of text position, equals to the length of text. The minimal point is location of the first char, always zero.
(line-max)
The maximal value of line number, pointing to the last line. Unlike text position, the minimal line number starts at 1.
(point)
The current text position indicated by the caret. When there is selected text, caret won't be showing, then it returns the start of the selected range.
(selected-range)
Returns a pair in the form of (start
. end
).
start
points to the first selected char,
and end
points to the location after the last selected char. end
is exclusive.
When start
equals to end
,
selected text length is zero,
meaning no text is selected, start
is where the caret is.
To get the start
and end
location
as integer, you use
(car (selected-range)) => start
(cdr (selected-range)) => end
respectively.
(set-selected-range start end)
Select the text between start
and end
,
exclusively.
When start
equals to end
,
it moves caret to location start
,
which is effectively same as
(goto-char start)
.
(goto-char pos)
Move caret to the position pos
.
(string-from-range start end)
Return the text in the range.
(goto-line num)
Go to the beginning of line num
.
(next-line)
Go to the beginning of next line.
(beginning-of-line num)
Return the beginning text position of the specified line.
(line-number-at-pos pos)
Return the line number at the point.
(insert text)
Insert the text at caret position. If there is selected text, it replace the current selected text. In the latter case, if text is empty, its effect is same as delete.
(goto-line line-number)
($)
commands goes to the end of file . (courtesy to VIM).
(interactive command &optional prompt &rest suggestions)
Registers a command to the command bar.
format
is a string that contains prompt string
and input type. suggestions
can be either a list of values or
a lambda that can generate a list at run time.
(interactive* abbr-name command &optional prompt &rest suggestions)
Same as interactive
except that you can use a shorter name abbr-name
to invoke the command. Note that you can use the 'abbr-name' in either bind-key
, or toolbar and keybar item definitions.
(spell-check lang)
Enable spell check on current buffer with a specified language.
If lang is false
, then turn off spell check.
You should be able to run spell or spell-off from the interactive commands.
Supported languages is shown below the command prompt when you try to run spell
command.
Customize UI and Appearances
Before customizing TeX Writer, you should know that TeX Writer's UI consists of 4 parts, from top to bottom:
- toolbar
- work area (buffers zone)
- echo area
- keybar (its position could be taken by suggestion bar in command input mode).
Customize toolbars
It's possible to add buttons to toolbar:
(add-hook tex-mode-hook (lambda ()
(add-to-toolbar "TeX" 'insert-tex)
))
insert-tex
must be defined previously as a procedure.
Customize keybars
Text buffers have a keybar that will show up with system keyboard when they gain input focus. The keybar usually contains characters hard to find on iOS keyboard, or symbols that represent customized text input procedures.
Like with toolbar, keybar customization should be kept in a mode hook.
(add-hook tex-mode-hook (lambda ()
(set-keybar '(
("\\" . tex-insert-backslash)
("{}" . insert-braces)
("$$" . tex-insert-inline-math)
("%" . tex-insert-percent)
("_" . tex-insert-underscore)
("^" . tex-insert-circumflex)
("`" . insert-backquote)
("()" . insert-parens)
("[]" . insert-brackets)
))
(add-to-keybar "TeX" 'insert-tex)
))
In the code above, we use two different ways to customize key bar.
set-keybar
replaces existing bar items with the provided list,
and add-to-keybar
adds a new bar item.
Notice the differences between them:
- There is a '.' in
set-keybar
, but not inadd-to-keybar
. - There is a
'
quote right before the procedure name, but not inset-keybar
. Once you learn how quotes work in LISP, they will make sense to you.
Add Interactive Commands
You use (interactive 'command)
to register a new command.
Then you can invoke interactive commands in any of the following ways:
- From toolbar "VIEW => Command..."
- From keybar "cmd"
- Use external keyboard "alt-x" (yes, it's like emacs)
Add Key Bindings
You can add keyboard bindings in mode hook.
(add-hook tex-mode-hook (lambda ()
(bind-key "ctrl-u" 'some-procedure-you-defined)
))
Colors and Faces
Colors are specified with a hex color string (like in HTML).
(define background-color "#ffffff")
(define line-number-background-color "#ccc")
(define fringe-color "#eee")
(define echo-background-color "#ffffff")
(define caret-color "#ff8800")
(define status-line-background-color "#bbb")
(define status-line-highlight-background-color "#eee")
(define spell-error-color "#f00")
A face is defined as an ordinary association list.
For example, default-face
is defined as:
(define default-face '((font-name . "CourierNewPSMT")
(font-size . 16)
(color . "#000000")))
(define keyword-face '((color . "#001996")))
(define comment-face '((color . "#008019")))
(define string-literal-face '((color . "#960303")))
(define status-line-face '((color . "#000000")(font-size . 16)))
(define echo-face '((color . "#000000") (font-size . 16)))
(define line-number-face '((color . "#333") (font-size . 12)));;RGB(30,30,30)
Other face definitions don't have font name attribute,
they simply use the same font from default-face
.
You can define your own face and use it in token definition.
(add-to-face face attr value)
Update face definition. Set attr with value.
For example, the following code updates default-face
's font to "Courier".
(add-to-face default-face 'font-name "Courier")
Note that the first argument is without quote.
(set-keyboard-appearance type)
type : could be 'light
or 'dark
.
Setting keyboard appearance affects the system status bar color as well. You need to configure the rest so that the entire application looks consistent.
(add-to-face face key value)
It's simple to define a new face so that you can use it in token defintion.
(Need an image for describing various parts)
Add Token Definitions
You can define additional tokens for syntax highlighting.
(add-token token-definition)
token-definition is a pair of regular expression and face symbol. Example:
(add-token '("%.*$" . comment-face))
(add-token '("(\\${1,2}).*?\\1" . string-literal-face))
For regular expression syntax, please refer to: https://developer.apple.com/documentation/foundation/nsregularexpression
Appendix: sample twinit.l
(set! background-color "#272822")
(set! line-number-background-color "#ccc")
(set! fringe-color "#eee")
(set! echo-background-color "#ffffff")
(set! caret-color "#ff8800")
(set! status-line-background-color "#bbb")
(set! status-line-highlight-background-color "#eee")
(set! spell-error-color "#f00")
;; Set default font
(add-to-face default-face 'font-name "AmericanTypewriter")
(add-to-face default-face 'color "#f8f8f2")
(add-to-face keyword-face 'color "#001996")
(add-to-face comment-face 'color "#008019")
(add-to-face string-literal-face 'color "#960303")
(add-to-face status-line-face 'color "#000000")
(add-to-face status-line-face 'font-size 16)
(add-to-face echo-face 'color "#000000")
(add-to-face echo-face 'font-size 16)
(add-to-face line-number-face 'color "#333")
(add-to-face line-number-face 'font-size 12)
(set-keyboard-appearance 'light) ;; or 'dark
(add-hook text-mode-hook (lambda ()
(bind-key "ctrl-u" 'page-up)
(bind-key "ctrl-d" 'page-down)
))
(define (my-page-up)
(page-up)
(message "My Page Up"))
(define (insert-tex)
(insert "\\TeX\\ "))
(add-hook tex-mode-hook (lambda ()
(add-to-keybar "TeX" 'insert-tex)
(interactive 'page-up)
(bind-key "ctrl-u" 'my-page-up)
))