%YAML 1.2
---
# AutoHotkey syntax highlighting for Fresh editor (v1 and v2)
# Based on https://github.com/SALZKARTOFFEEEL/ahk-sublime-syntax (MIT License)
name: AutoHotkey
file_extensions:
  - ahk
  - ahk2
scope: source.ahk

variables:
  # Combined v1 commands and v2 functions from official AutoHotkey references
  functions: (?i:Abs|(?:A)?(?:Sin|Cos|Tan)|AutoTrim|BlockInput|Buffer|CallbackCreate|CallbackFree|CaretGetPos|Ceil|Chr|Click|ClipboardAll|ClipWait|ComCall|ComObj(?:Active|Array|Connect|Create|Error|Flags|FromPtr|Get|Query|Type|Value)?|ComObject|ComValue|Control(?:AddItem|Choose(?:Index|String)?|Click|DeleteItem|EditPaste|FindItem|Focus|Get(?:Checked|Choice|ClassNN|Current(?:Col|Line)|Enabled|Focus|Hwnd|Index|Items|Line(?:Count)?|List|Pos|Selected|(?:Ex)?Style|Tab|Text|Visible)?|Hide(?:DropDown)?|Move|Send|Set(?:Checked|Enabled|(?:Ex)?Style|Tab|Text)?|Show(?:DropDown)?)|CoordMode|Critical|Date(?:Add|Diff)|DetectHidden(?:Text|Windows)|Dir(?:Copy|Create|Delete|Exist|Move|Select)|DllCall|Download|Drive(?:Eject|Get(?:Capacity|FileSystem|Label|List|Serial|SpaceFree|Status(?:CD)?|Type)?|Lock|Retract|SetLabel|Unlock)?|Edit(?:GetCurrentCol|GetCurrentLine|GetLine|GetLineCount|GetSelectedText|Paste)?|Env(?:Add|Div|Get|Mult|Set|Sub|Update)?|Exit(?:App)?|Exp|File(?:Append|Copy(?:Dir)?|CreateDir|CreateShortcut|Delete|Encoding|Exist|Get(?:Attrib|Shortcut|Size|Time|Version)|Install|Move(?:Dir)?|Open|Read(?:Line)?|Recycle(?:Empty)?|RemoveDir|Select(?:File|Folder)?|Set(?:Attrib|Time))|Float|Floor|Format(?:Time)?|Func|GetKey(?:Name|SC|State|VK)|GetMethod|Group(?:Activate|Add|Close|Deactivate)|Gui(?:Control(?:Get)?|Create|CtrlFromHwnd|FromHwnd)?|HasBase|HasMethod|HasProp|Hotkey|Hotstring|IL_(?:Add|Create|Destroy)|IfMsgBox|ImageSearch|Ini(?:Delete|Read|Write)|Input(?:Box|End|Hook)?|InstallKeybdHook|InstallMouseHook|InStr|Integer|Is(?:ByRef|Func|Label|Object)|Key(?:History|Wait)|List(?:Hotkeys|Lines|Vars)|ListViewGetContent|LoadPicture|Log|Ln|Map|Max|Menu(?:Bar|FromHandle|Get(?:Handle|Name)|Select)?|Min|Mod|Monitor(?:Get(?:Count|Name|Primary|WorkArea)?)|Mouse(?:Click(?:Drag)?|GetPos|Move)|MsgBox|Num(?:Get|Put)|Number|Obj(?:AddRef|BindMethod|GetBase|GetCapacity|HasOwnProp|OwnPropCount|OwnProps|RawGet|RawSet|Release|SetBase|SetCapacity)|On(?:ClipboardChange|Error|Exit|Message)|Ord|OutputDebug|Pause|Persistent|Pixel(?:GetColor|Search)|PostMessage|Process(?:Close|Exist|GetName|GetParent|GetPath|SetPriority|Wait(?:Close)?)|Progress|Random|RegCreateKey|RegEx(?:Match|Replace)|Reg(?:Delete(?:Key)?|Read|Write)|RegisterCallback|Reload|Round|Run(?:As|Wait)?|Send(?:Raw|Input|Play|Event|Level|Message|Mode)?|Set(?:BatchLines|Cap(?:sLock|lock)State|ControlDelay|DefaultMouseSpeed|Env|Format|KeyDelay|MouseDelay|Num(?:Lock|lock)State|RegView|Scroll(?:Lock|lock)State|StoreCap(?:sLock|lock)Mode|Timer|TitleMatchMode|WinDelay|WorkingDir)|Shutdown|Sleep|Sort|Sound(?:Beep|Get(?:Interface|Mute|Name|Volume|WaveVolume)?|Play|Set(?:Mute|Volume|WaveVolume)?)|SplashImage|SplashTextOn|SplashTextOff|SplitPath|Sqrt|StatusBar(?:GetText|Wait)|Str(?:Compare|Get|Len|Lower|Ptr|Put|Replace|Split|Title|Upper)|String(?:CaseSense|GetPos|Left|Len|Lower|Mid|Replace|Right|Split|TrimLeft|TrimRight|Upper)|SubStr|Suspend|Switch|SysGet(?:IPAddresses)?|Thread|ToolTip|Transform|TraySetIcon|TrayTip|Trim|RTrim|LTrim|Type|UrlDownloadToFile|VarSetCapacity|VarSetStrCapacity|VerCompare|Win(?:Activate(?:Bottom)?|Active|Close|Exist|Get(?:ActiveStats|ActiveTitle|Class|ClientPos|Controls(?:Hwnd)?|Count|(?:Ex)?Style|ID(?:Last)?|List|MinMax|PID|Process(?:Name|Path)|Pos|Text|Title|TransColor|Transparent)?|Hide|Kill|Maximize|MenuSelectItem|Minimize(?:All(?:Undo)?)?|Move(?:Bottom|Top)?|Redraw|Restore|Set(?:AlwaysOnTop|Enabled|(?:Ex)?Style|Region|Title|TransColor|Transparent)?|Show|Wait(?:Active|Close|NotActive)?))
  valid_name: (?i:\b([a-z_]|[^[:ascii:]])(\w|[^[:ascii:]])*\b)
  invalid_name: (?i:\b(([0-9](\w|[^[:ascii:]])+)|And|Contains|In|Is|New|Not|Or|Break|Catch|Continue|Else|Finally|For|Gosub|Goto|If|Loop|LoopFile|LoopParse|LoopRead|LoopReg|Return|Throw|Try|Until|While)\b)
  keys: (?i:[a-z0-9]|[LRM]Button|XButton[12]|Wheel(Up|Down|Left|Right)|CapsLock|Space|Tab|Enter|Return|Escape|Esc|Backspace|BS|ScrollLock|Delete|Del|Insert|Ins|Home|End|PgUp|PgDn|Up|Down|Left|Right|NumLock|Numpad([0-9]|Dot|Div|Mult|Add|Sub|Enter)|F([1-9]|1[0-9]|2[0-4])|[LR]Win|((L|R)?(Control|Ctrl|Alt|Shift))|Browser_(Back|Forward|Refresh|Stop|Search|Favorites|Home)|Volume_(Mute|Down|Up)|Media_(Next|Prev|Stop|Play_Pause)|Launch_(Mail|Media|App[12])|AppsKey|PrintScreen|CtrlBreak|Pause|Break|Help|Sleep|SC[0-9A-F]{3}|VK[0-9A-F]{2})(?i:\s+Up)?
  joystick_keys: (?i:[1-9]|1[0-6])?Joy([1-9]|1[0-9]|2[0-9]|3[0-2])

contexts:
  main:
    - include: Hotkeys
    - include: Hotstrings
    - include: Directives
    - include: Flow
    - include: Labels
    - include: Expressions

  prototype:
    - include: LineComments
    - include: BlockComments

  LineComments:
    - match: (?<=^|\s)(;)
      scope: punctuation.definition.comment.begin.ahk
      push:
        - meta_scope: comment.line.ahk
        - match: '$'
          pop: true

  BlockComments:
    - match: (^\s*/\*)
      scope: punctuation.definition.comment.begin.ahk
      push:
        - meta_scope: comment.block.ahk
        - match: (^\s*\*/|\*/\s*$)
          scope: punctuation.definition.comment.end.ahk
          pop: true

  Directives:
    - match: (^\s*#)(?=\S+)
      scope: keyword.ahk
      push:
        - match: (?i:ClipboardTimeout|HotkeyInterval|HotkeyModifierTimeout|IfTimeout|InputLevel|KeyHistory|MaxHotkeysPerInterval|MaxThreads|MaxThreadsPerHotkey)\b(\s+(((?<![\da-fA-F])(\+|\-))?(?<!\.)\b(0x|0X)?[\da-fA-F]+\b(?!\.))?)
          captures:
            0: keyword.ahk
            1: constant.numeric.integer.ahk
          pop: true
        - match: (?i:ErrorStdOut|InstallKeybdHook|InstallMouseHook|NoTrayIcon|Persistent|WinActivateForce|NoEnv|AllowSameLineComments|SuspendExempt)\b
          scope: keyword.ahk
          pop: true
        - match: (?i:MaxThreadsBuffer|UseHook)\b\s+(?i:On|Off)?
          scope: keyword.ahk
          pop: true
        - match: (?i:Hotstring)\b(\s+(?i:NoMouse|EndChars( .*)?|((?i:\*0|\*|\?0|\?|B0|B|C1|C0|C|K(-)?\d+|O0|O|P(-)?\d+|R0|R|SI|SP|SE|Z0|Z)+)))?
          captures:
            0: keyword.ahk
            2: string.unquoted.ahk
            3: hotstring.option.ahk
          pop: true
        - match: (?i:(?:Hot)?If\b)
          scope: keyword.ahk
          pop: true
          push: Expressions
        - match: (?i:IfWin(Not)?(Active|Exist)\b)(\s+(.*))?
          captures:
            0: keyword.ahk
            4: string.unquoted.ahk
          pop: true
        - match: (?i:(Include)\b\s+(<(.*)>\s*([^;]*)?))
          captures:
            1: keyword.ahk
            2: string.unquoted.ahk
            3: support.function.ahk
            4: invalid.illegal.ahk
          pop: true
        - match: (?i:Include(Again)?(\s+([^;]*)())?)
          captures:
            0: keyword.ahk
            3: string.unquoted.ahk
          pop: true
        - match: (?i:(MenuMaskKey)\b\s+({{keys}})\b)
          captures:
            0: keyword.ahk
            2: hotkey.key.ahk
          pop: true
        - match: (?i:(SingleInstance)\b(\s+(Force|Ignore|Off))?)
          scope: keyword.ahk
          pop: true
        - match: (?i:(Warn)\b(\s+(UseUnset(Local|Global)|LocalSameAsGlobal|All)?(\s*,\s*)?(MsgBox|StdOut|OutputDebug|Off)?)?)
          scope: keyword.ahk
          pop: true
        - match: (?i:(Requires)\b.*)
          scope: keyword.ahk
          pop: true
        - match: (?i:(DllLoad)\b.*)
          scope: keyword.ahk
          pop: true
        - match: $
          pop: true

  Flow:
    - meta_include_prototype: false
    - match: (?i:(Break|Continue)(?=\s|$)(\s+((\d+)|([^\s,`;][^\s,`]*)))?)
      captures:
        1: keyword.control.flow.ahk
        4: constant.numeric.integer.ahk
        5: label.name.ahk

    - match: (?i:(Else\s+)?If)(?=\s|$)
      scope: keyword.control.conditional.ahk
    - match: (?i:(Else))(?=\s|$)
      captures:
        1: keyword.control.conditional.ahk

    - match: (?i:(Return))(?=\s|,|$)
      scope: keyword.control.flow.ahk

    - match: (?i:(Gosub|Goto)(?=\s|$)(\s+([^\s,`]+))?)
      captures:
        1: keyword.control.flow.ahk
        3: label.name.ahk

    - match: (?i:(Exit(App)?))(?=\s|$)
      scope: keyword.control.flow.ahk

    - match: (?i:(Try|Throw|Catch|Finally))(?=\s|$)
      scope: keyword.control.flow.ahk

    - match: (?i:(Critical))(?=\s|$)
      scope: keyword.control.flow.ahk

    - match: (?i:(Loop\s+(Files|Parse|Read|Reg),\s*))(?=\s|$)
      scope: keyword.control.flow.ahk
    - match: (?i:(Loop|While))(?=\s|$)
      scope: keyword.control.flow.ahk
    - match: (?i:(For)(\s+({{valid_name}})(\s*,\s*({{valid_name}}))?\s+(in)\s+)?)(?=\s|$)
      captures:
        1: keyword.control.flow.ahk
        3: variable.language.ahk
        7: variable.language.ahk
        10: keyword.operator.word.ahk
    - match: (?i:Until)(?=\s)
      scope: keyword.control.flow.ahk
    - match: (?i:(Switch))(?=\s|$)
      scope: keyword.control.flow.ahk
    - match: (?i:(Case))(?=\s|$)
      scope: keyword.control.flow.ahk
    - match: (?i:(Default))(?=\s*:)
      scope: keyword.control.flow.ahk

  Hotkeys:
    # remapping hotkey
    - match: ^\s*(({{keys}})(::)({{keys}}(?!\s*[^\s;])))
      captures:
        1: hotkey.ahk
        2: hotkey.key.ahk
        13: hotkey.doublecolon.ahk
        14: hotkey.key.ahk
    # regular hotkey
    - match: ^\s*((\*|~|\$|(<|>)?(#|!|\^|\+))*({{keys}}|{{joystick_keys}}))(::)
      captures:
        1: hotkey.ahk
        2: hotkey.modifier.ahk
        3: hotkey.modifier.LeftRight.ahk
        4: hotkey.modifier.key.ahk
        5: hotkey.key.ahk
        17: hotkey.doublecolon.ahk
    # custom combination hotkey
    - match: ^\s*(({{keys}})\s+(&)\s+({{keys}}))(::)
      captures:
        1: hotkey.ahk
        2: hotkey.key.ahk
        13: hotkey.ampersand.ahk
        14: hotkey.key.ahk
        25: hotkey.doublecolon.ahk

  Hotstrings:
    - match: ^\s*((:)(((?i:\*0|\*|\?0|\?|B0|B|C1|C0|C|K(-)?\d+|O0|O|P(-)?\d+|R0|R|SI|SP|SE|Z0|Z)\s*)*)?(:)(.*)(::))([^;]*)?
      captures:
        1: hotstring.ahk
        2: hotstring.colon.ahk
        3: hotstring.option.ahk
        7: hotstring.colon.ahk
        8: hotstring.trigger.ahk
        9: hotstring.colon.ahk
        10: string.unquoted.ahk

  Labels:
    - match: (^\s*[^\s,`]+)(:)(?!\s*[^\s;])
      captures:
        1: label.name.ahk
        2: label.colon.ahk

  Expressions:
    - include: double_quoted_strings
    - include: single_quoted_strings
    - include: numbers
    - include: operators
    - include: class_definitions
    - include: class_calls
    - include: predefined_function_calls
    - include: custom_function_calls
    - include: variables

    - match: ((?<=^|\s)%(?=\s))
      scope: invalid.deprecated.ahk

  double_quoted_strings:
    - include: escaped
    - match: ("(?!.*[^`]*"))
      scope: invalid.illegal.ahk

    - match: ("(?=.*[^`]*"))
      scope: punctuation.definition.string.begin.ahk
      push:
        - include: escaped
        - meta_scope: string.quoted.double.ahk
        - match: (")
          scope: punctuation.definition.string.end.ahk
          pop: true

  single_quoted_strings:
    - include: escaped
    - match: ('(?!.*[^`]*'))
      scope: invalid.illegal.ahk

    - match: ('(?=.*[^`]*'))
      scope: punctuation.definition.string.begin.ahk
      push:
        - include: escaped
        - meta_scope: string.quoted.single.ahk
        - match: (')
          scope: punctuation.definition.string.end.ahk
          pop: true

  escaped:
    - match: (`[`;nrbtsvaf"']?)
      scope: constant.character.escape.ahk

  operators:
    - match: (\*\*|\/\/|\+\+|--|\*|\/|\+|-)
      scope: keyword.operator.arithmetic.ahk
    - match: ((<<|>>|\/\/|:|\+|-|\*|\/|\.|\||&|\^)=)
      scope: keyword.operator.assignment.ahk
    - match: (<<|>>|~|&|\^|\|)
      scope: keyword.operator.bitwise.ahk
    - match: (~=|<=|>=|==|<>|!=|&&|\|\||<|>|=|!)
      scope: keyword.operator.logical.ahk
    - match: (?<=^|\s)(?i:IS|IN|CONTAINS|NOT|AND|OR)(?=$|\s)
      scope: keyword.operator.word.ahk
    - match: ((?<=^|\s)\.(?=\s))
      scope: keyword.operator.concatenate.ahk
    - match: (\?)
      scope: keyword.operator.ternary.if.ahk
      push:
        - include: Expressions
        - match: (:)
          scope: keyword.operator.ternary.else.ahk
          pop: true
        - match: $
          pop: true

  numbers:
    - include: decimal_integers
    - include: hexadecimal_integers
    - include: decimal_floats

  decimal_integers:
    - match: (?<!\.|\w)(\+|-)?\b(\d+)\b(?!\.\d)
      scope: constant.numeric.integer.ahk
  hexadecimal_integers:
    - match: (?<!\w)(\+|-)?\b(?i:(0x[\dA-F]+)\b(?!\.[\dA-F]))
      scope: constant.numeric.hex.ahk
  decimal_floats:
    - match: (?<!\w)(\+|-)?(\d+\.\d+|(?<!\w|\.)\.\d+)
      scope: constant.numeric.float.ahk

  class_definitions:
    - match: (?i:^\s*Class)
      scope: storage.type.ahk
      push:
        - meta_scope: meta.class.ahk
        - match: (?i:({{valid_name}})(\s+(extends)\s+({{valid_name}}))?)
          captures:
            1: entity.name.class.ahk
            5: storage.type.ahk
            6: entity.name.class.ahk
          pop: true

  class_calls:
    - match: ({{valid_name}}(?=\.\S))
      scope: support.class.ahk
    - match: ((\.)({{valid_name}}))(?=\()
      captures:
        3: support.class.method.ahk
    - match: ((\.)({{valid_name}}))
      captures:
        3: support.class.property.ahk
    - match: (?i:(new)\s+({{valid_name}}))
      captures:
        1: storage.type.ahk
        2: entity.name.class.ahk

  predefined_function_calls:
    # v2 function call syntax: Func(args)
    - match: ({{functions}})(?=\()
      scope: support.function.builtin.ahk
      push:
        - include: Expressions
        - meta_scope: meta.function-call.ahk
        - match: (\))
          pop: true

    # v2 function call or v1 command with space-separated args: Func arg1
    - match: ({{functions}})(?=\s+)
      scope: support.function.builtin.ahk
      push:
        - include: Expressions
        - meta_scope: meta.function-call.ahk
        - match: ($)
          pop: true

    # v1 command syntax: Command, arg1, arg2
    - match: ({{functions}})(?=,)
      scope: support.function.builtin.ahk

  custom_function_calls:
    - match: ({{valid_name}})(?=\()
      scope: support.function.ahk
      push:
        - include: Expressions
        - meta_scope: meta.function-call.ahk
        - match: (\))
          pop: true

  variables:
    - match: (True|False)
      scope: constant.language.ahk

    # v1 %variable% dereference syntax
    - match: (%)([\w]+)(%)
      captures:
        1: punctuation.definition.variable.begin.ahk
        2: variable.other.ahk
        3: punctuation.definition.variable.end.ahk

    - match: \b(?i:A_(Space|Tab|Args|(Initial)?WorkingDir|Script(Dir|Name|FullPath|Hwnd)|Line(Number|File)|This(Func|Label)|Ahk(Version|Path)|IsCompiled|IsUnicode|YYYY|M{2,4}|D{2,4}|WDay|YDay|YWeek|Hour|Min|(M)?Sec|Now(UTC)?|TickCount|Is(Suspended|Paused|Critical)|TitleMatchMode(Speed)?|DetectHidden(Windows|Text)|StringCaseSense|FileEncoding|Send(Mode|Level)|StoreCapslockMode|Key(Delay|Duration)(Play)?|(Win|Control)Delay|MouseDelay(Play)?|DefaultMouseSpeed|CoordMode(ToolTip|Pixel|Mouse|Caret|Menu)|RegView|Icon(Hidden|Tip|File|Number)|TimeIdle(Physical|Keyboard|Mouse)?|This(Menu(Item(Pos)?)?|Hotkey)|Prior(Hotkey|Key)|TimeSince(This|Prior)Hotkey|EndChar|ComSpec|Temp|OS(Type|Version)|Is64bitOS|PtrSize|Language|(Computer|User)Name|WinDir|ProgramFiles|(AppData|Desktop|StartMenu|Programs|Startup)(Common)?|MyDocuments|IsAdmin|Screen(Width|Height|DPI)|IPAddress[1-4]|Cursor|Caret(X|Y)|EventInfo|LastError|Index|LoopFile((Short)?Name|Ext|(Full|Short)?Path|Dir|Time(Modified|Created|Accessed)|Attrib|Size(KB|MB)?)|LoopReg(Name|Type|Key|TimeModified)|Loop(ReadLine|Field)|TrayMenu|AllowMainWindow|MenuMaskKey|ClipboardAll))\b
      scope: variable.language.ahk

    # v1 built-in variables without A_ prefix
    - match: \b(?i:Clipboard|ErrorLevel)\b
      scope: variable.language.ahk

    - match: ({{invalid_name}})
      scope: invalid.illegal.ahk

    - match: (?i:Global|Static)\b
      scope: storage.type.ahk

    - match: ({{valid_name}})
      scope: variable.language.ahk
