Emacs add consistent indent rule to generic-x mode - emacs

I wrote a very simple Emacs mode for Standard ML:
;; sml syntax
(require 'generic-x)
(define-generic-mode
'sml-mode ;; name of the mode
'(("(*" . "*)")) ;; comments delimiter
'("fun" "fn" "let" "val" "datatype" "type" "case" "of" "end" "structure" "struct" "signature" "sig")
'(("=" . 'font-lock-builtin-face)
("|" . 'font-lock-builtin-face)
(">" . 'font-lock-builtin-face)
("<" . 'font-lock-builtin-face)
("-" . 'font-lock-builtin-face)
("+" . 'font-lock-builtin-face)
(";" . 'font-lock-builtin-face)
("," . 'font-lock-builtin-face)
("{" . 'font-lock-builtin-face)
("}" . 'font-lock-builtin-face)
("(" . 'font-lock-builtin-face)
(")" . 'font-lock-builtin-face)
(":" . 'font-lock-builtin-face)
("[" . 'font-lock-builtin-face)
("]" . 'font-lock-builtin-face)) ;; a built-in
'("\\.sml$") ;; files that trigger this mode
nil ;; any other functions to call
"SML highlighting mode" ;; doc string
)
However, it will not indent consistently. I can't describe exactly how it indents, but it switches inconsistently between tabs and spaces and length of the spaces. The simplest rule I can think of is to always start a new line on the same column and tabbing always takes you to the next column that is a multiple of 4. Tabs should be spaces. How can I do this using the generic mode?
As a note on the mode definition, I am using the builtin-face incorrectly because the operator-face had no coloring. It does look ugly now though.

First things first: I strongly recommend you start with define-derived-mode rather than with define-generic-mode because the former will seamlessly grow to accomodate a fully-featured major mode whereas define-generic-mode will quickly impose limits that are inconvenient to work around.
E.g. you could rewrite your code as:
(defvar sml-mode-syntax-table
(let ((st (make-syntax-table)))
;; Make (*...*) a comment.
(modify-syntax-entry ?\( "()1" st)
(modify-syntax-entry ?\) ")(4" st)
(modify-syntax-entry ?\* ". 23n" st)
st))
(defvar sml-font-lock-keywords
`((,(concat "\\_<"
(regexp-opt '("fun" "fn" "let" "val" "datatype" "type" "case" "of" "end" "structure" "struct" "signature" "sig"))
"\\_>")
(0 font-lock-keyword-face))
("[][=|><-+;,{}():]" (0 font-lock-builtin-face))))
;;;###autoload
(define-derived-mode sml-mode prog-mode "SML"
"SML major mode."
(set (make-local-variable 'comment-start) "(* ")
(set (make-local-variable 'comment-end) " *)")
(set (make-local-variable 'font-lock-defaults)
'(sml-font-lock-keywords)))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.sml\\'" . sml-mode))
W.r.t TABs and SPCs, "switching between them" is the default Emacs behavior (the attitude being that TAB is just an optimisation which we use when it's applicable). If you don't like it, then put (setq-default indent-tabs-mode nil) in your ~/.emacs and not in your major mode's definition since that is a personal choice unrelated to SML (which does not distinguish TABs and SPCs, contrary to, say, Haskell).
As for the indentation you suggest, you could start by adding (set (make-local-variable 'indent-line-function) #'indent-relative) which should make sure that by default indentation is just the same as the previous line; and for the "TAB should advance by 4 columns" maybe something like (set (make-local-variable 'tab-stop-list '(4 8 12 16 20 24 28 32 36 40 44 48 52 56 60 64)) would do the trick (in more recent Emacsen, '(4 8) is sufficient because Emacs finally learned to "auto-extend the list".
But I'm curious: why not just use the existing sml-mode that's in GNU ELPA?

Related

Customizing emacs conf mode

I am editing some proprietary config files that Emacs autodetects as Conf[JavaProp], i.e. using conf-javaprop-mode from conf-mode.el. That mode is almost perfect, except that these files don't have c or c++ style comments, i.e.
//foo
or
/* foo */
should not be highlighted as comments. Could anyone provide me with some guidance for how I can make my own extensions to conf-mode and automatically load them or whatever from my .emacs ?
You can use conf-mode-initialize to set the comment syntax. For example, here is a simple one for .ctags where # should only start a comment when preceded by spaces. Just modify which conf mode you want to inherit from, and remove the propertize function/syntax table unless you want those modified as well.
(require 'conf-mode)
(defun dotctags-propertize (start end)
(goto-char start)
(funcall
(syntax-propertize-rules
("^\\s-*#.*" (0 "<")))
(point) end))
(defvar dotctags-mode-syntax-table
(let ((st (make-syntax-table conf-windows-mode-syntax-table)))
(modify-syntax-entry ?\; "." st)
(modify-syntax-entry ?\n ">" st)
st))
;;;###autoload
(define-derived-mode dotctags-mode conf-windows-mode "Conf[cTags]"
"Conf Mode for ctags config."
:syntax-table dotctags-mode-syntax-table
(conf-mode-initialize "#")
(setq-local comment-end "")
(setq-local syntax-propertize-function #'dotctags-propertize))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.ctags\\'" . dotctags-mode))
Create your own derived mode from conf-mode and then make it the default mode for your propietary file
(define-derived-mode conf-my-mode conf-unix-mode "Conf[MyMode]"
"Conf mode of my own"
(conf-mode-initialize "#" 'conf-my-mode-font-lock-keywords))
(add-to-list 'auto-mode-alist '("\\.your-extension\\'" . conf-my-mode))

My Emacs mode does not highlight keywords

I am trying to write an Emacs major mode but it is not working; specifically, my keywords are not being displayed in keyword font face. I have tried to follow the tutorials but I must be doing something wrong. I know a little Lisp but I do not know Emacs scripting.
My Emacs mode script:
;; emacs mode for OldRope
;; does no work for some reason
(setq oldrope-directive-keywords-regexp (regexp-opt '("page" "link" "goto" "act" "end" "div" "span" "include")))
(defvar oldrope-font-lock-defaults '((
(oldrope-directive-keywords-regexp . font-lock-keyword-face))))
(define-derived-mode oldrope-mode fundamental-mode
"oldrope mode"
"Major mode for editing OldRope games"
(setq comment-start "/*")
(setq comment-end "*/")
(setq font-lock-defaults oldrope-font-lock-defaults))
(provide 'oldrope-mode)
Test file:
$[page start]$ Hello $[link]$ Click me $[act]$ That is right. $[end]$
(For context, this is part of https://github.com/martinellison/oldrope but that is not really relevant to the question).
You need this - the rest is OK:
(defvar oldrope-font-lock-defaults
`(((,oldrope-directive-keywords-regexp . font-lock-keyword-face))))
By simply quoting the list you were not evaluating oldrope-directive-keywords-regexp - your quoted list just had that symbol as its car.
Using either backquote (`) plus comma (,) or (list (list (cons oldrope-directive-keywords-regexp 'font-lock-keyword-face))) you evaluate that variable and use its value in the resulting list.

How to define whole line comment syntax in Emacs?

I want the sequence // to start a comment when it is at the beginning of a line. But inside of a line it should not start any comments.
// this is a comment
This is a URL: http://example.com
Is it possible?
I'd do it this way:
(defvar my-foo-mode-syntax-table
(let ((st (make-syntax-table)))
;; Add other entries appropriate for my-foo-mode.
(modify-syntax-entry ?/ ". 12" st)
(modify-syntax-entry ?\n "> " st)
st))
(defvar my-foo-font-lock-keywords
;; Add other rules appropriate for my-foo-mode.
())
(define-derived-mode my-foo-mode nil "My-Foo"
(setq-local font-lock-keywords '(my-foo-font-lock-keywords))
;; Add other settings appropriate for my-foo-mode.
(setq-local syntax-propertize-function
(syntax-propertize-rules ("./\\(/+\\)" (1 ".")))))
Notice: No need for any special font-lock rule since font-lock automatically highlights comments for you, based on the syntax-tables.
You can do this by writing a syntax-propertize-function
I have written an example major mode that shows this below.
Emacs built in parsing will call your syntax-propertize-function so that it can manually set the syntax-table text property on lines starting with //.
(define-derived-mode my-syntax-test-mode fundamental-mode
"A major mode where // denotes a comment but only if it is at the beginning of a line."
:syntax-table (make-syntax-table)
(setq mode-name "my syntax test")
;; our mode will use `apply-my-custom-syntax-table-appropriately' to manually set
;; the syntax-table text property on lines starting with //"
(setq syntax-propertize-function 'apply-my-custom-syntax-table-appropriately)
;; change `comment-dwim` to handle this type of comments correctly
(local-set-key [remap comment-dwim] 'my-comment-dwim))
(defvar my-custom-syntax-table
;; syntax table where // starts a comment and \n ends it
(let ((table (make-syntax-table)))
(modify-syntax-entry ?/ "< 1" table)
(modify-syntax-entry ?/ "< 2" table)
(modify-syntax-entry ?\n "> " table)
table))
(defun apply-my-custom-syntax-table-appropriately (beg end)
(save-excursion
(save-restriction
(widen)
(goto-char beg)
;; for every line between points BEG and END
(while (and (not (eobp)) (< (point) end))
(beginning-of-line)
;; if it starts with a //
(when (looking-at "^//")
;; remove current syntax-table property
(remove-text-properties (1- (line-beginning-position))
(1+ (line-end-position))
'(syntax-table))
;; set syntax-table property to our custom one
;; for the whole line including the beginning and ending newlines
(add-text-properties (1- (line-beginning-position))
(1+ (line-end-position))
(list 'syntax-table my-custom-syntax-table)))
(forward-line 1)))))
(defun my-comment-dwim (arg)
(interactive "*P")
(require 'newcomment)
(save-excursion
(let ((comment-start "//") (comment-end "")
(comment-column 0)
;; don't indent comments
(comment-style 'plain))
;; create the region containing current line if there is no active region
(unless (use-region-p)
(end-of-line)
(push-mark (line-beginning-position))
(setq mark-active t))
(comment-dwim nil))))
Answer: Use regexps
Short and simple
(define-derived-mode my-foo-mode prog-mode "My-Foo"
(setq-local font-lock-keywords t)
(setq-local syntax-propertize-function
(syntax-propertize-rules
((rx line-start (* whitespace) (group "//")) (1 "<"))
((rx (group "\n")) (1 ">")))))
That is all you need, but read on if you'd like to know more.
Explanation
This is based on #stefan's excellent solution which uses syntax-propertize-function to add to a syntax-table. While simpler isn't always better, #stefan's answer does more than what the original question asked for, so I've created this answer for people who only need a small hint or who just want to modify an existing mode.
It turns out directly manipulating a syntax table is unnecessary since the function syntax-propertize-rules makes it easy to map from regular expressions to syntax classes. For example, the syntax class < means "start of comment" and > means "end of comment". (See the Emacs lisp manual.)
I set font-lock-keywords to t as that is the minimum needed to enable syntax highlighting. If you are editing an existing mode, it likely already sets that variable and will not need to be changed.
And, finally, I use Emacs' rx function because it makes regular expressions sane in Lisp. (If you like Lisp, regular expressions, and sanity, I highly recommend using rx.)
About syntax-propertize-rules
I was going to link to the documentation for syntax-propertize-rules, but the Emacs manual does not (as of Emacs 28.1) even mention it. Until that gets remedied, I'll paste here the builtin documentation from C-hf:
syntax-propertize-rules is a Lisp macro in ‘syntax.el’.
(syntax-propertize-rules &rest RULES)
Probably introduced at or before Emacs version 24.1.
Make a function that applies RULES for use in
‘syntax-propertize-function’. The function will scan the buffer,
applying the rules where they match. The buffer is scanned a single
time, like "lex" would, rather than once per rule.
Each RULE can be a symbol, in which case that symbol’s value should
be, at macro-expansion time, a precompiled set of rules, as returned
by ‘syntax-propertize-precompile-rules’.
Otherwise, RULE should have the form (REGEXP HIGHLIGHT1 ...
HIGHLIGHTn), where REGEXP is an expression (evaluated at time of
macro-expansion) that returns a regexp, and where HIGHLIGHTs have the
form (NUMBER SYNTAX) which means to apply the property SYNTAX to the
chars matched by the subgroup NUMBER of the regular expression, if
NUMBER did match. SYNTAX is an expression that returns a value to
apply as ‘syntax-table’ property. Some expressions are handled
specially:
if SYNTAX is a string, then it is converted with ‘string-to-syntax’;
if SYNTAX has the form (prog1 EXP . EXPS) then the value returned by EXP will be applied to the buffer before running EXPS and if EXP is
a string it is also converted with ‘string-to-syntax’. The SYNTAX
expression is responsible to save the ‘match-data’ if needed for
subsequent HIGHLIGHTs. Also SYNTAX is free to move point, in which
case RULES may not be applied to some parts of the text or may be
applied several times to other parts.
Note: back-references in REGEXPs do not work.

Emacs. Abbreving "." to ending sentence

I am an emacs newbie. I am trying to expand the character "." to ". " (period with two spaces in order to be more effective in ending a sentence in emacs) with abbrevs. In other words, when I type a "." followed by a space, emacs put a ". ".
I have put the next code in my abbrevs file, but it doesn't work.
(text-mode-abbrev-table)
"." 0 ". "
Anybody can help me?
I'm not sure why you would want this, but here it is:
Put this in ~/.emacs:
(defun electric-dot ()
(interactive)
(if (and (looking-back "\\w") (not (looking-back "[0-9]")))
(progn
(self-insert-command 1)
(insert " "))
(self-insert-command 1)))
(defvar electric-dot-on-p nil)
(defun toggle-electric-dot ()
(interactive)
(global-set-key
"."
(if (setq electric-dot-on-p
(not electric-dot-on-p))
'electric-dot
'self-insert-command)))
Afterwards, use M-xtoggle-electric-dot
to make each . insert . , if it's after a word.
You can call it again to restore the default behavior.
As a side-note, there's a ton of much better ways
to improve your text input speed e.g. auto-complete-mode.
You can install it with package-install.
UPD electric-dot inserts just a dot after digits.
UPD Here's electric-space instead:
This one will insert an extra space if it's looking back
at a word followed by a dot.
(defun electric-space ()
(interactive)
(if (looking-back "\\w\\.")
(insert " "))
(self-insert-command 1))
(defvar electric-space-on-p nil)
(defun toggle-electric-space ()
(interactive)
(global-set-key
" "
(if (setq electric-space-on-p
(not electric-space-on-p))
'electric-space
'self-insert-command)))
Install the key-chords package. Then define one like this.
(key-chord-define-global ". " ". ")
Key chords accept a larger variety of keys then abbrev, so ". " might work. But the two keys have to be pressed in quick succession (which shouldn't be a problem here).
The reason your abbrev does not work is because by default, abbrev names can only be composed of word-element characters (basically, alphanumeric chars), and . is not such a character. You can circumvent this problem by changing the :regexp property of your text-mode-abbrev-table but it's really not worth the trouble. Instead you can do
(defun my-electric-space-post-insert ()
(and (eq (char-before) ?\s)
(eq (char-before (1- (point))) ?.)
(insert " ")))
(add-hook 'post-self-insert-hook #'my-electric-space-post-insert)

Highlighting quoted/backquoted parens

In Emacs is there a way to syntax-highlight the parentheses of quoted and backquoted sexps differently than the parens of other sexps so they stand out? e.g. I want these parens to be a different color than other parens:
(foo `(bar (baz)) quux)
^ ^
Have a look at mic-paren, a minor mode built as an extension to the packages paren.el and stig-paren.el for Emacs. It features recognition of "escaped" sexps.
Now for special highlighting : if you look at the code, the behaviour of quoted sexp matching is governed by the variable paren-match-quoted-paren. When finding a couple of match sexps, the typeface change is made using statements such as:
(mic-overlay-put mic-paren-backw-overlay 'face paren-mismatch-face)
(with similar alternatives for matched, unmatched). It shouldn't be too hard to define an alternative font (similarly to what is done with paren-mismatch-face), and replace those typeface-changing statements by functions that use your alternative font if paren-match-quoted-paren is true.
Note: updated links to refer to latest version
You can apply the following patch to mic-paren (follow link for latest version, 3.8) to get what you want. Customize the newly created face paren-face-quoted-match which is glaringly set up to have a green foreground and orange background for testing purposes.
Now when you're next to a matched set of parenthesis preceded by a single open quote `, you'll get the quoted face. This example uses an orange background and green foreground - most likely colors you'll want to change.
Here's a picture of it in action:
alt text http://img262.imageshack.us/img262/8866/quoted.png
--- orig-mic-paren.el 2009-11-11 17:02:42.000000000 -0800
+++ mic-paren.el 2009-11-11 17:05:35.306263000 -0800
## -561,4 +561,16 ##
:group 'mic-paren-matching)
+(defface paren-face-quoted-match
+ '((((class color)) (:foreground "green" :background "orange"))
+ (t (:reverse-video t)))
+ ""
+ :group 'faces
+ :group 'mic-paren-matching)
+
+(defcustom paren-quoted-match-face 'paren-face-quoted-match
+ "Mic-paren face used for a quoted paren"
+ :type 'face
+ :group 'mic-paren-matching)
+
;;; End of User Options
;;; ======================================================================
## -1052,5 +1064,9 ##
face (if mismatch
paren-mismatch-face
- paren-match-face)
+ (save-excursion
+ (if (progn (goto-char (- (min (point) opos) 1))
+ (looking-at "`"))
+ paren-quoted-match-face
+ paren-match-face)))
visible (when (pos-visible-in-window-p opos)
(save-excursion
To apply the patch, cut/paste the patch chunk to a file named mic.patch, and run the following:
patch mic-paren.el mic.patch