;; Chetan Vaity ;; Sep 2006 ;; ;; Modified from http://two-wugs.net/emacs/mode-tutorial.html ;; (defvar ael-mode-hook nil) (defvar ael-mode-map (let ((ael-mode-map (make-keymap))) (define-key ael-mode-map "\C-j" 'newline-and-indent) ael-mode-map) "Keymap for AEL major mode") (add-to-list 'auto-mode-alist '("\\.ael\\'" . ael-mode)) ;; AEL2 keywords taken from http://www.voip-info.org/wiki/view/Asterisk+ael2 ;; Regexp for AEL2 keywords generated by the following command: ;; (regexp-opt '("abstract" "context" "macro" "globals" "ignorepat" "switch" "if" "ifTime" "else" "random" "goto" "jump" "return" "break" "continue" "regexten" "hint" "for" "while" "case" "pattern" "default" "catch" "switches" "eswitches" "includes") t) ;; Asterisk builtin commands taken from http://www.voip-info.org/wiki/index.php?page=Asterisk+-+documentation+of+application+commands ;; Regexp for Asterisk builtin commands generated by: ;; (reexp-opt '("AbsoluteTimeout" "AddQueueMember" "ADSIProg" "AgentCallbackLogin" "AgentLogin" "AgentMonitorOutgoing" "AGI""AlarmReceiver" "ALSAMonitor" "Answer" "AppendCDRUserField" "Authenticate" "BackGround" "BackgroundDetect" "Busy" "CallingPres" "ChangeMonitor" "ChanIsAvail" "ChanSpy" "CheckGroup" "Congestion" "ControlPlayback" "Curl" "Cut" "DateTime" "DBdel" "DBdeltree" "DBget" "DBput" "DeadAGI" "Dial" "Dictate" "DigitTimeout" "Directory" "DISA" "DTMFToText" "DUNDiLookup" "EAGI" "Echo" "EnumLookup" "Eval" "Festival" "Flash" "Flite" "ForkCDR" "GetCPEID" "GetGroupCount" "GetGroupMatchCount" "Gosub" "GosubIf" "Goto" "GotoIf" "GotoIfTime" "Hangup" "HasNewVoicemail" "HasVoicemail" "ICES" "ImportVar" "LookupBlacklist" "LookupCIDName" "Macro" "MailboxExists" "Math" "MeetMe" "MeetMeAdmin" "MeetMeCount" "Milliwatt" "MixMonitor" "Monitor" "MP3Player" "MusicOnHold" "MYSQL" "NBScat" "NoCDR" "NoOp" "Page" "ParkAndAnnounce" "ParkedCall" "PauseQueueMemeber" "Perl" "PickUP" "Playback" "Playtones" "PPPD" "Prefix" "PrivacyManager" "Progress" "Queue" "Random" "Read" "Record" "RemoveQueueMember" "ResetCDR" "ResponseTimeout" "RetryDial" "Return" "Ringing" "Rpt" "SayAlpha" "SayDigits" "SayNumber" "SayPhonetic" "SayUnixTime" "SendDTMF" "SendImage" "SendText" "SendURL" "Set" "SetAccount" "SetAMAflags" "SetCallerID" "SetCallerPres" "SetCDRUserField" "SetCIDName" "SetCIDNum" "SetGlobalVar" "SetGroup" "SetLanguage" "SetMusicOnHold" "SetVar" "SIPAddHeader" "SIPCallPickup" "SIPGetHeader" "SIPdtmfMode" "SMS" "SoftHangup" "Sort" "SrxEchoCan" "SrxDeflect" "SrxMWI" "Steal" "StackPop" "StopMonitor" "StopPlaytones" "StripLSD" "StripMSD" "SubString" "Suffix" "System" "Transfer" "TestClient" "TestServer" "TrySystem" "TXTCIDName" "UnpauseQueueMemeber" "UserEvent" "VMAuthenticate" "VoiceMail" "VoiceMailMain" "Wait" "WaitExten" "WaitForRing" "WaitMusicOnHold" "Zapateller" "ZapBarge" "ZapSendKeypadFacility" "ZapRAS" "ZapScan") t) ;; Added more commands at the end: Background, DBGet and VoiceMail (defconst ael-font-lock-keywords (list '("\\/\\/.*$" . font-lock-comment-face) '("\\<\\(abstract\\|break\\|c\\(a\\(se\\|tch\\)\\|ont\\(ext\\|inue\\)\\)\\|default \\|e\\(lse\\|switches\\)\\|for\\|g\\(lobals\\|oto\\)\\|hint\\|i\\(f\\(Time\\)?\\|gnorepat\\|ncludes\\)\\|jump\\|macro\\|pattern\\|r\\(andom\\|e\\(gexten\\|turn\\)\\)\\|switch\\(es\\)?\\|while\\|default\\)\\>" . font-lock-keyword-face) '("\\${[A-Za-z0-9_\\\\(\\\\)]+}" . font-lock-variable-name-face) '("[ \t]&[A-Za-z0-9_\\\\-]+" . font-lock-function-name-face) '("\\<\\(A\\(DSIProg\\|GI\\|LSAMonitor\\|bsoluteTimeout\\|ddQueueMember\\|gent\\(CallbackLogin\\|Login\\|MonitorOutgoing\\)\\|larmReceiver\\|nswer\\|ppendCDRUserField\\|uthenticate\\)\\|B\\(ack\\(Ground\\|groundDetect\\)\\|usy\\)\\|C\\(allingPres\\|h\\(an\\(IsAvail\\|Spy\\|geMonitor\\)\\|eckGroup\\)\\|on\\(gestion\\|trolPlayback\\)\\|u\\(rl\\|t\\)\\)\\|D\\(B\\(del\\(tree\\)?\\|get\\|put\\)\\|ISA\\|TMFToText\\|UNDiLookup\\|ateTime\\|eadAGI\\|i\\(al\\|ctate\\|gitTimeout\\|rectory\\)\\)\\|E\\(AGI\\|cho\\|numLookup\\|val\\)\\|F\\(estival\\|l\\(ash\\|ite\\)\\|orkCDR\\)\\|G\\(et\\(CPEID\\|Group\\(Count\\|MatchCount\\)\\)\\|o\\(sub\\(If\\)?\\|to\\(If\\(Time\\)?\\)?\\)\\)\\|Ha\\(ngup\\|s\\(NewVoicemail\\|Voicemail\\)\\)\\|I\\(CES\\|mportVar\\)\\|Lookup\\(Blacklist\\|CIDName\\)\\|M\\(P3Player\\|YSQL\\|a\\(cro\\|ilboxExists\\|th\\)\\|eetMe\\(Admin\\|Count\\)?\\|i\\(lliwatt\\|xMonitor\\)\\|onitor\\|usicOnHold\\)\\|N\\(BScat\\|o\\(CDR\\|Op\\)\\)\\|P\\(PPD\\|a\\(ge\\|rk\\(AndAnnounce\\|edCall\\)\\|useQueueMemeber\\)\\|erl\\|ickUP\\|lay\\(back\\|tones\\)\\|r\\(efix\\|ivacyManager\\|ogress\\)\\)\\|Queue\\|R\\(andom\\|e\\(ad\\|cord\\|moveQueueMember\\|s\\(etCDR\\|ponseTimeout\\)\\|t\\(ryDial\\|urn\\)\\)\\|inging\\|pt\\)\\|S\\(IP\\(AddHeader\\|CallPickup\\|GetHeader\\|dtmfMode\\)\\|MS\\|ay\\(Alpha\\|Digits\\|Number\\|Phonetic\\|UnixTime\\)\\|e\\(nd\\(DTMF\\|Image\\|Text\\|URL\\)\\|t\\(A\\(MAflags\\|ccount\\)\\|C\\(DRUserField\\|IDN\\(ame\\|um\\)\\|aller\\(ID\\|Pres\\)\\)\\|G\\(lobalVar\\|roup\\)\\|Language\\|MusicOnHold\\|Var\\)?\\)\\|o\\(ftHangup\\|rt\\)\\|rx\\(Deflect\\|EchoCan\\|MWI\\)\\|t\\(ackPop\\|eal\\|op\\(Monitor\\|Playtones\\)\\|rip\\(LSD\\|MSD\\)\\)\\|u\\(bString\\|ffix\\)\\|ystem\\)\\|T\\(XTCIDName\\|est\\(Client\\|Server\\)\\|r\\(ansfer\\|ySystem\\)\\)\\|U\\(npauseQueueMemeber\\|serEvent\\)\\|V\\(MAuthenticate\\|oiceMail\\(Main\\)?\\)\\|Wait\\(Exten\\|ForRing\\|MusicOnHold\\)?\\|Zap\\(Barge\\|RAS\\|S\\(can\\|endKeypadFacility\\)\\|ateller\\)\\|Background\\|DBGet\\|VoiceMail\\)\\>" . font-lock-builtin-face)) "Minimal highlighting expressions for AEL mode") ;; Indentation Rules ;; 1. If we are at the beginning of the buffer, indent to column 0 ;; 2. If we are currently at an }; line or } line, then de-indent relative to the previous line ;; 3. If we first see an }; line or } line before our current line, then we should indent our current line to the same indentation as the }; line. ;; 4. If we first see a "start line" like PARTICIPANT, then we need to increase our indentation relative to that start line. ;; 5. If none of the above apply, then do not indent at all. (defun ael-indent-line () "Indent current line as AEL code" (interactive) (setq default-tab-width 4) (setq indent-tabs-mode nil) (setq tab-stop-list '(4 8 12 16)) (beginning-of-line) (if (bobp) ; Check for rule 1 (indent-line-to 0) (let ((not-indented t) cur-indent) (if (looking-at "^[ \t]*\\(};?\\|default\\)") ; Check for rule 2 ; The default keyword (inside switch) is painful to handle (progn (save-excursion (forward-line -1) (setq cur-indent (- (current-indentation) default-tab-width))) (if (< cur-indent 0) ; Check - do not indent past the left margin (setq cur-indent 0))) (save-excursion (while not-indented (forward-line -1) (if (looking-at "^[ \t]*};?[ \t]*$") ; Check for rule 3 (progn (setq cur-indent (current-indentation)) (setq not-indented nil)) ; Check for rule 4 (if (looking-at "^[ \t]*\\(macro\\|if\\|else\\|}?[ \t]*else[ \t]*if\\|for\\|context\\|[a-zA-Z0-9 \t]*=>[ \t]*{\\|includes\\|switch\\|case\\|default\\|while\\|catch\\)") (progn (setq cur-indent (+ (current-indentation) default-tab-width)) (setq not-indented nil)) (if (bobp) ; Check for rule 5 (setq not-indented nil))))))) (if cur-indent (indent-line-to cur-indent) (indent-line-to 0))))) ; If we didn't see an indentation hint, then allow no indentation (define-derived-mode ael-mode fundamental-mode "AEL" "Major mode for editing Asterisk AEL files." (set (make-local-variable 'font-lock-defaults) '(ael-font-lock-keywords)) (set (make-local-variable 'indent-line-function) 'ael-indent-line)) (provide 'ael-mode)