Skip to content

Instantly share code, notes, and snippets.

@nonsequitur
Created September 26, 2009 12:41
Show Gist options
  • Save nonsequitur/194210 to your computer and use it in GitHub Desktop.
Save nonsequitur/194210 to your computer and use it in GitHub Desktop.
;;; ahk-mode.el --- major mode for editing AutoHotKey scripts for X/GNU Emacs
;; Copyright (C) 2005 Robert Widhopf-Fenk
;; Addendum 2009: Hacked to play nicely with other modes.
;; Fixed setting of global variables that should be local instead.
;; Author: Robert Widhopf-Fenk
;; Keywords: AutoHotKey, major mode
;; X-URL: http://www.robf.de/Hacking/elisp
;; arch-tag: 1ae180cb-002e-4656-bd9e-a209acd4a3d4
;; Version: $Id: ahk-mode--main--1.0--patch-4$
;; This code 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 2, 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.
;;
;;; Commentary:
;;
;; AutoHotKey: Automation, Hotkeys and Scripting for Windows at
;; http://www.autohotkey.com/ is a cool tool to make daily life
;; with Windows easier or even fun!
;;
;; This is a X/GNU Emacs mode for editing AutoHotKey scripts.
;;
;; Place this file somewhere in your load-path, byte-compile it and add the
;; following line to your ~/.xemacs/init.el resp. ~/.emacs:
;;
;; (setq ahk-syntax-directory "PATHTO/AutoHotkey/Extras/Editors/Syntax/")
;; (add-to-list 'auto-mode-alist '("\\.ahk$" . ahk-mode))
;; (autoload 'ahk-mode "ahk-mode")
;;
;; The first time ahk-mode.el is started it will ask you for the path to the
;; Syntax directory if not set already. You will find it as a subdirectory
;; of your AHK installation.
;;
;; For example if you installed AHK at C:\Programms\AutoHotKey it will be
;; C:/Programms/AutoHotKey/Extras/Editors/Syntax or a corresponding cygwin
;; path!
;;
;; When opening a script file you will get:
;; - syntax highlighting
;; - indention, completion and command help (bound to "TAB")
;; - insertion of command templates (bound to "C-c C-i")
;; - electric braces (typing "{" will also insert "}" and place point in
;; between)
;; - lookup the docs on a command via w3 (place point on command and type
;; "C-c C-h")
;;
;; Please send bug-reports or feature suggestions to hackATrobfDOTde.
;;; Bugs:
;;
;; - completions is not really context aware
;; - multi-line comments are not fontified correctly while editing,
;; but only when fontifying the whole buffer. If you know how to
;; fix this, please let me know!
;;; History:
;;
;; The CHANGELOG is stored in my arch repository.
;;
;; If you wonder what arch is, take a look at http://wiki.gnuarch.org/ !
(eval-when-compile
(require 'font-lock)
(if (locate-library "w3") (require 'w3))
(require 'cl))
;;; Code:
(defgroup ahk-mode nil
"A mode for AutoHotKey"
:group 'languages
:prefix "ahk-")
(defcustom ahk-mode-hook '(ahk-mode-hook-activate-filling)
"Hook functions run by `ahk-mode'."
:type 'hook
:group 'ahk-mode)
(defcustom ahk-indetion 2
"The indetion level."
:type 'integer
:group 'ahk-mode)
(defcustom ahk-syntax-directory nil
"The indetion level."
:type 'directory
:group 'ahk-mode)
;;;###autoload
(add-to-list 'auto-mode-alist
'("\\.ahk$" . ahk-mode))
(defvar ahk-mode-syntax-table
(let ((table (make-syntax-table)))
;; these are also allowed in variable names
(modify-syntax-entry ?# "w" table)
(modify-syntax-entry ?_ "w" table)
(modify-syntax-entry ?@ "w" table)
(modify-syntax-entry ?$ "w" table)
(modify-syntax-entry ?? "w" table)
(modify-syntax-entry ?[ "w" table)
(modify-syntax-entry ?] "w" table)
;; some additional characters used in paths and switches
(modify-syntax-entry ?\\ "w" table)
; (modify-syntax-entry ?/ "w" table)
(modify-syntax-entry ?- "w" table)
(modify-syntax-entry ?: "w" table)
(modify-syntax-entry ?. "w" table)
;; for multiline comments (taken from cc-mode)
(modify-syntax-entry ?/ ". 14" table)
(modify-syntax-entry ?* ". 23" table)
;; Give CR the same syntax as newline, for selective-display
(modify-syntax-entry ?\^m "> b" table)
(modify-syntax-entry ?\n "> b" table)
table)
"Syntax table used in `ahk-mode' buffers.")
(defvar ahk-mode-abbrev-table
(let ((a (make-abbrev-table)))
a)
"Abbreviation table used in `ahk-mode' buffers.")
(defvar ahk-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "\C-c\C-h" 'ahk-www-help-at-point)
(define-key map "\C-c\C-c" 'ahk-comment-region)
(define-key map "\C-c\C-i" 'ahk-insert-command-template)
(define-key map "\t" 'ahk-indent-line-and-complete)
(define-key map "{" 'ahk-electric-brace)
(define-key map "}" 'ahk-electric-brace)
(define-key map "\r" 'ahk-electric-return)
map)
"Keymap used in `ahk-mode' buffers.")
(defvar ahk-Commands-list nil
"A list of ahk commands and parameters.
Will be initialized by `ahk-init'")
(defvar ahk-Keys-list nil
"A list of ahk key names.
Will be initialized by `ahk-init'")
(defvar ahk-Keywords-list nil
"A list of ahks keywords.
Will be initialized by `ahk-init'")
(defvar ahk-Variables-list nil
"A list of ahks variables.
Will be initialized by `ahk-init'")
(defvar ahk-mode-font-lock-keywords nil
"Syntax highlighting for `ahk-mode'.
Will be initialized by `ahk-init'")
(defvar ahk-completion-list nil
"A list of all symbols available for completion
Will be initialized by `ahk-init'")
(easy-menu-define ahk-menu ahk-mode-map "AHK Mode Commands"
'("AHK"
["Insert Command Template" ahk-insert-command-template]
["Lookup webdocs on command" ahk-www-help-at-point]
))
(defun ahk-init ()
"Initialize ahk-mode variables.
An AHK installation provides a subdirectory \"Extras/Editors/Syntax\"
containing a list of keywords, variables, commands and keys.
This directory must be specified in the variable `ahk-syntax-directory'."
(interactive)
(message "Initializing ahk-mode variables ...")
(when (null ahk-syntax-directory)
(customize-save-variable
'ahk-syntax-directory
(read-file-name "Please give the AHK-Syntax directory: "))
(custom-save-all))
(save-excursion
(set-buffer (get-buffer-create " *ahk-mode-temp*"))
;; read commands
(erase-buffer)
(insert-file-contents (expand-file-name "Commands.txt"
ahk-syntax-directory))
(setq ahk-Commands-list nil)
(goto-char 0)
(while (not (eobp))
(if (not (looking-at "\\([^;\r\n][^(\t\r\n, ]+\\)\\([^\r\n]*\\)"))
nil;; (error "Unknown file syntax")
(setq ahk-Commands-list (cons (list
(match-string 1)
(match-string 2))
ahk-Commands-list)))
(forward-line 1))
;; read keys
(erase-buffer)
(insert-file-contents (expand-file-name "Keys.txt"
ahk-syntax-directory))
(setq ahk-Keys-list nil)
(goto-char 0)
(while (not (eobp))
(if (not (looking-at "\\([^;\r\n][^\t\r\n ]+\\)"))
nil;; (error "Unknown file syntax of Keys.txt")
(setq ahk-Keys-list (cons (match-string 1) ahk-Keys-list)))
(forward-line 1))
;; read keywords
(erase-buffer)
(insert-file-contents (expand-file-name "Keywords.txt"
ahk-syntax-directory))
(setq ahk-Keywords-list nil)
(goto-char 0)
(while (not (eobp))
(if (not (looking-at "\\([^;\r\n][^\t\r\n ]+\\)"))
nil;; (error "Unknown file syntax of Keywords.txt")
(setq ahk-Keywords-list (cons (match-string 1) ahk-Keywords-list)))
(forward-line 1))
;; read variables
(erase-buffer)
(insert-file-contents (expand-file-name "Variables.txt"
ahk-syntax-directory))
(setq ahk-Variables-list nil)
(goto-char 0)
(while (not (eobp))
(if (not (looking-at "\\([^;\r\n][^\t\r\n]+\\)"))
nil;; (error "Unknown file syntax of Variables.txt")
(setq ahk-Variables-list (cons (match-string 1) ahk-Variables-list)))
(forward-line 1))
;; built completion list
(setq ahk-completion-list
(mapcar (lambda (c) (list c))
(append (mapcar 'car ahk-Commands-list)
ahk-Keywords-list
ahk-Variables-list
ahk-Keys-list)))
(setq ahk-mode-font-lock-keywords
(list
'("\\s-*;.*$" .
font-lock-comment-face)
'("^/\\*\\(.*\r?\n\\)*\\(\\*/\\)?" .
; '(ahk-fontify-comment .
font-lock-comment-face)
'("^\\([^ \t\n:]+\\):" .
(1 font-lock-builtin-face))
'("[^, %\"]*%[^% ]+%" .
font-lock-variable-name-face)
;; I get an error when using regexp-opt instead of simply
;; concatenating the keywords and I do not understand why ;-(
;; (warning/warning) Error caught in `font-lock-pre-idle-hook': (invalid-regexp Invalid preceding regular expression)
(cons
(concat "\\b\\("
(mapconcat 'regexp-quote ahk-Variables-list "\\|")
"\\)\\b")
'font-lock-variable-name-face)
(list
(concat "\\(^[ \t]*\\|::[ \t]*\\)\\("
(mapconcat 'regexp-quote (mapcar 'car ahk-Commands-list) "\\|")
"\\)")
2
'font-lock-function-name-face)
(cons
(concat "\\b\\("
(mapconcat 'regexp-quote ahk-Keywords-list "\\|")
"\\)\\b")
'font-lock-keyword-face)
(cons
(concat "\\b\\("
(mapconcat 'regexp-quote ahk-Keys-list "\\|")
"\\)\\b")
'font-lock-constant-face)
)))
(message "Initializing ahk-mode variables done."))
;; this was an attempt to get multi-line comments correctly highlighted, I
;; tried to understand how cc-mode is doing it, but I have to admit I do not
;; understand it!
(defun ahk-fontify-comment (limit)
; (setq limit (point-max))
(save-excursion
(let (start end)
; (setq bstart (save-excursion
; (re-search-backward "^/\\*" (point-min) t)))
; (setq bend (save-excursion
; (re-search-backward "^\\*/" (point-min) t)))
; (if (and bstart bend (< bend bstart))
; (goto-char bstart))
(setq start (save-excursion
(re-search-forward "^/\\*" limit t)))
(setq end (save-excursion
(re-search-forward "^\\*/" limit t)))
(cond
((and start end (< end start))
(save-excursion
(re-search-forward "^\\*/" limit t)
t))
((and start end (< start end))
(save-excursion
(re-search-forward "^/\\*\\(.*\r?\n\\)*\\*/" limit t)
t))
((and start (not end))
(save-excursion
(re-search-forward "^/\\*\\(.*\r?\n\\)*" limit t)
t))
))))
(defun ahk-mode-hook-activate-filling ()
"Activates `auto-fill-mode' and `filladapt-mode'."
(auto-fill-mode 1)
(if (locate-library "filladapt")
(filladapt-mode 1)))
;;;###autoload
(defun ahk-mode ()
"Major mode for editing AutoHotKey Scripts.
The hook functions in `ahk-mode-hook' are run after mode initialization.
Key bindings:
\\{ahk-mode-map}"
(interactive)
(if (null ahk-Commands-list)
(ahk-init))
(kill-all-local-variables)
(use-local-map ahk-mode-map)
(set-syntax-table ahk-mode-syntax-table)
(setq major-mode 'ahk-mode
mode-name "AHK"
local-abbrev-table ahk-mode-abbrev-table
comment-start ";"
font-lock-defaults '(ahk-mode-font-lock-keywords)
font-lock-keywords-case-fold-search t)
(set (make-local-variable 'indent-region-function) 'ahk-indent-region)
(if (featurep 'xemacs)
(put 'ahk-mode 'font-lock-defaults '(ahk-mode-font-lock-keywords t))
(put 'ahk-mode 'font-lock-keywords-case-fold-search t))
(easy-menu-add ahk-menu)
(force-mode-line-update)
(run-hooks 'ahk-mode-hook))
(defun ahk-indent-line ()
"Indent the current line."
(interactive)
(let ((indent 0)
(case-fold-search t))
;; do a backward search to determin the indention level
(save-excursion
(beginning-of-line)
(if (looking-at "^;")
(setq indent 0)
(skip-chars-backward " \t\n")
(beginning-of-line)
(while (and (looking-at "^;") (not (bobp)))
(forward-line -1))
(if (looking-at "^[^: ]+:")
(if (looking-at "^[^: ]+:\\([^:]*:\\)?[ \t]*$")
(setq indent ahk-indetion)
(setq indent 0))
(if (looking-at "^\\([ \t]*\\)[{(]")
(setq indent (+ (length (match-string 1)) ahk-indetion))
(if (and
(save-excursion
(forward-line 1)
(not (looking-at "^\\([ \t]*\\)[{(]")))
(looking-at "^\\([ \t]*\\)\\(If\\|Else\\)"))
(setq indent (+ (length (match-string 1)) ahk-indetion))
(if (save-excursion
(forward-line -1)
(looking-at "^\\([ \t]*\\)\\(If\\|Else\\)"))
(setq indent (+ (length (match-string 1))))
(if (looking-at "^\\([ \t]*\\)")
(setq indent (+ (length (match-string 1)))))))))))
;; check for special tokens
(save-excursion
(beginning-of-line)
(if (looking-at "^\\([ \t]*\\)[})]")
(setq indent (- indent ahk-indetion))
(if (or (looking-at "^[ \t]*[^,: \t\n]*:")
(looking-at "^;;;"))
(setq indent 0))))
(let ((p (point-marker)))
(beginning-of-line)
(if (looking-at "^[ \t]+")
(replace-match ""))
(indent-to indent)
(goto-char p)
(set-marker p nil)
(if (bolp)
(goto-char (+ (point) indent))))))
(defun ahk-indent-region (start end)
"Indent lines in region START to END."
(interactive "r")
(save-excursion
(goto-char end)
(setq end (point-marker))
(goto-char start)
(while (< (point) end)
(beginning-of-line)
(ahk-indent-line)
(forward-line 1))
(ahk-indent-line)
(set-marker end nil)))
(defun ahk-complete ()
"Indent current line when at the beginning or complete current command."
(interactive)
(if (looking-at "\\w+")
(goto-char (match-end 0)))
(let ((end (point)))
(if (and (or (save-excursion (re-search-backward "\\<\\w+"))
(looking-at "\\<\\w+"))
(= (match-end 0) end))
(let ((start (match-beginning 0))
(prefix (match-string 0))
(completion-ignore-case t)
completions)
(setq completions (all-completions prefix ahk-completion-list))
(if (eq completions nil)
nil;(error "Unknown command prefix <%s>!" prefix)
(if (> (length completions) 1)
(setq completions
(completing-read "Complete command: "
(mapcar (lambda (c) (list c))
completions)
nil t prefix)))
(if (stringp completions)
;; this is a trick to upcase "If" and other prefixes
(let ((c (try-completion completions ahk-completion-list)))
(if (stringp c)
(setq completions c))))
(delete-region start end)
(if (listp completions) (setq completions (car completions)))
(insert completions)
(let ((help (assoc completions ahk-Commands-list)))
(if help (message "%s" (mapconcat 'identity help ""))))
)))))
(defun ahk-indent-line-and-complete ()
"Combines indetion and completion."
(interactive)
(ahk-indent-line)
(ahk-complete))
(defun ahk-electric-brace (arg)
"Insert character ARG and correct line's indentation."
(interactive "p")
(if (save-excursion
(skip-chars-backward " \t")
(bolp))
nil
(ahk-indent-line)
(newline))
(self-insert-command arg)
(ahk-indent-line)
(newline)
(ahk-indent-line)
(let ((event last-input-event))
(setq event (if (featurep 'xemacs)
(event-to-character event)
(setq event (if (stringp event) (aref event 0) event))))
(when (equal event ?{)
(newline)
(ahk-indent-line)
(insert ?})
(ahk-indent-line)
(forward-line -1)
(ahk-indent-line))))
(defun ahk-electric-return ()
"Insert newline and indent."
(interactive)
(ahk-indent-line)
(newline)
(ahk-indent-line))
(defun ahk-insert-command-template ()
"Insert a command template."
(interactive)
(let ((completions (mapcar (lambda (c) (list (mapconcat 'identity c "")))
ahk-Commands-list))
(completion-ignore-case t)
(start (point))
end
command)
(setq command (completing-read "AHK command template: " completions))
(insert command)
(ahk-indent-line)
(setq end (point-marker))
(goto-char start)
(while (re-search-forward "[`][nt]" end t)
(if (string= (match-string 0) "`n")
(replace-match "\n")
(replace-match "")))
(ahk-indent-region start end)
(goto-char (1+ start))
;; jump to first parameter
(re-search-forward "\\<" end nil)
(set-marker end nil)))
(defun ahk-comment-region (start end &optional arg)
"Comment or uncomment each line in the region from START to END.
If no region is active use the current line."
(interactive (if (region-active-p)
(list (region-beginning)
(region-end)
current-prefix-arg)
(let (start end)
(beginning-of-line)
(setq start (point))
(forward-line)
(setq end (point))
(list start end current-prefix-arg))))
(save-excursion
(comment-region start end arg)))
(defun ahk-www-help-at-point ()
(interactive)
(save-excursion
(re-search-backward "\\<\\w+")
(if (looking-at "\\<\\w+")
(ahk-www-help (match-string 0)))))
(defvar ahk-www-help-alist '()
"Mapping of command to regexp for commands which are not unique.")
(defun ahk-www-help (command)
"Display online help for the given command"
(interactive (list
(completing-read "AHK command: "
ahk-completion-list nil t)))
(require 'w3)
(w3-fetch "http://www.autohotkey.com/docs/commands.htm")
(goto-char (point-min))
(when (re-search-forward (concat "^|" (regexp-quote command)))
(goto-char (+ (match-beginning 0) 1))
(save-excursion
(if (re-search-forward (concat "^|" (regexp-quote command))
(point-max) t)
(message "There is more than one occurrence of %s. Stopping at first match" command)
(widget-button-press (point))))))
(provide 'ahk-mode)
;;; ahk-mode.el ends here
@piyo
Copy link

piyo commented Aug 12, 2010

Your version seems to be based on "Version: $Id: ahk-mode--main--1.0--patch-4$" but there is a "patch-13" at the original site.
http://www.robf.de/Hacking/elisp/ahk-mode.el
Is there are reason why you did not modify patch-13?

@nonsequitur
Copy link
Author

No, I chose what seemed to be the most recent version.
If I find the time, I'll rebase the patch to version 1.0--patch-13.

@piyo
Copy link

piyo commented Aug 16, 2010

Hi again, I took the trouble to create a git-based repo from the original author's repo. Check it out on github as piyo/emacs-ahk-mode http://github.com/piyo/emacs-ahk-mode and fork as needed. I don't know which commit is patch-4 though. The last commit seems to be patch-13 so it's the latest. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment