# http://kakoune.org
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾

# Detection
# ‾‾‾‾‾‾‾‾‾

hook global BufCreate (.*/)?(\.Rprofile|.*\.[rR]) %{
    set-option buffer filetype r
}

provide-module r %§

# Highlighters & Completion
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾

add-highlighter shared/r regions
add-highlighter shared/r/code default-region group
add-highlighter shared/r/double_string region '"'   (?<!\\)(\\\\)*"  fill string
add-highlighter shared/r/single_string region "'"   (?<!\\)(\\\\)*'  fill string
add-highlighter shared/r/comment       region '#'   '$'              fill comment
add-highlighter shared/r/identifier    region '`'   (?<!\\)(\\\\)*`  fill attribute

# see base::NumericConstants
add-highlighter shared/r/code/ regex '(?i)\b(?<![.\d])\d+(\.\d*)?(e[-+]?\d+)?(?I)[iL]?(?![\d.e\w])' 0:value
add-highlighter shared/r/code/ regex '(?i)(?<![.\d\w])\.\d+(e[-+]?\d+)?(?I)[iL]?(?![\d.e])' 0:value
add-highlighter shared/r/code/ regex '(?i)\b(?<![.\d])0x[0-9a-f]+?(p[-+]?\d+)?(?I)[iL]?(?![\d.e\w])' 0:value

evaluate-commands %sh{
    # see base::Reserved
    values="TRUE|FALSE|NULL|Inf|NaN|NA|NA_integer_|NA_real_|NA_complex_|NA_character_|\.{3}|\.{2}\d+|"
    keywords="if|else|repeat|while|function|for|in|next|break"

    # Add the language's grammar to the static completion list
    printf %s\\n "hook global WinSetOption filetype=python %{
        set-option window static_words ${values} ${keywords}
    }" | tr '|' ' '

    printf %s "
        add-highlighter shared/r/code/ regex '\b(${values})\b' 0:value
        add-highlighter shared/r/code/ regex '\b(${keywords})\b' 0:keyword
    "
}

# see base::Syntax
add-highlighter shared/r/code/ regex (?<=[\w\s\d'"_)])(\$|@|\^|-|\+|%[^%]+%|\*|/|<(?![<-])|(?<![->])>|<=|>=|==|!=|!|&{1,2}|\|{1,2}|~|\?) 0:operator


# Commands
# ‾‾‾‾‾‾‾‾

define-command -hidden r-trim-indent %{
    # remove the line if it's empty when leaving the insert mode
    try %{ execute-keys -draft <a-x> 1s^(\h+)$<ret> d }
}

define-command -hidden r-indent-on-newline %< evaluate-commands -draft -itersel %<
    execute-keys <semicolon>
    try %<
        # if previous line closed a paren (possibly followed by words and a comment),
        # copy indent of the opening paren line
        execute-keys -draft k<a-x> 1s(\))(\h+\w+)*\h*(\;\h*)?(?:#[^\n]+)?\n\z<ret> m<a-semicolon>J <a-S> 1<a-&>
    > catch %<
        # else indent new lines with the same level as the previous one
        execute-keys -draft K <a-&>
    >
    # remove previous empty lines resulting from the automatic indent
    try %< execute-keys -draft k <a-x> <a-k>^\h+$<ret> Hd >
    # indent after an opening brace or parenthesis at end of line
    try %< execute-keys -draft k <a-x> s[{(]\h*$<ret> j <a-gt> >
    # indent after a statement not followed by an opening brace
    try %< execute-keys -draft k <a-x> s\)\h*(?:#[^\n]+)?\n\z<ret> \
                               <a-semicolon>mB <a-k>\A\b(if|for|while)\b<ret> <a-semicolon>j <a-gt> >
    try %< execute-keys -draft k <a-x> s \belse\b\h*(?:#[^\n]+)?\n\z<ret> \
                               j <a-gt> >
    # deindent after a single line statement end
    try %< execute-keys -draft K <a-x> <a-k>\;\h*(#[^\n]+)?$<ret> \
                               K <a-x> s\)(\h+\w+)*\h*(#[^\n]+)?\n([^\n]*\n){2}\z<ret> \
                               MB <a-k>\A\b(if|for|while)\b<ret> <a-S>1<a-&> >
    try %< execute-keys -draft K <a-x> <a-k>\;\h*(#[^\n]+)?$<ret> \
                               K <a-x> s \belse\b\h*(?:#[^\n]+)?\n([^\n]*\n){2}\z<ret> \
                               <a-S>1<a-&> >
    # align to the opening parenthesis or opening brace (whichever is first)
    # on a previous line if its followed by text on the same line
    try %< evaluate-commands -draft %<
        # Go to opening parenthesis and opening brace, then select the most nested one
        try %< execute-keys [c [({],[)}] <ret> >
        # Validate selection and get first and last char
        execute-keys <a-k>\A[{(](\h*\S+)+\n<ret> <a-K>"(([^"]*"){2})*<ret> <a-K>'(([^']*'){2})*<ret> <a-:><a-semicolon>L <a-S>
        # Remove possibly incorrect indent from new line which was copied from previous line
        try %< execute-keys -draft <space> <a-h> s\h+<ret> d >
        # Now indent and align that new line with the opening parenthesis/brace
        execute-keys 1<a-&> &
     > >
> >

define-command -hidden r-indent-on-opening-curly-brace %[
    # align indent with opening paren when { is entered on a new line after the closing paren
    try %[ execute-keys -draft -itersel h<a-F>)M <a-k> \A\(.*\)\h*\n\h*\{\z <ret> <a-S> 1<a-&> ]
]

define-command -hidden r-indent-on-closing-curly-brace %[
    # align to opening curly brace when alone on a line
    try %[
        # in case open curly brace follows a closing paren, align indent with opening paren
        execute-keys -itersel -draft <a-h><a-:><a-k>^\h+\}$<ret>hm <a-F>)M <a-k> \A\(.*\)\h\{.*\}\z <ret> <a-S>1<a-&>
    ] catch %[
        # otherwise align with open curly brace
        execute-keys -itersel -draft <a-h><a-:><a-k>^\h+\}$<ret>hm<a-S>1<a-&>
    ] catch %[]
]

define-command -hidden r-insert-on-newline %[ evaluate-commands -itersel -draft %[
    execute-keys <semicolon>
    try %[
        evaluate-commands -draft -save-regs '/"' %[
            # copy the commenting prefix
            execute-keys -save-regs '' k <a-x>1s^\h*(#+\h*)<ret> y
            try %[
                # if the previous comment isn't empty, create a new one
                execute-keys <a-x><a-K>^\h*#+\h*$<ret> j<a-x>s^\h*<ret>P
            ] catch %[
                # if there is no text in the previous comment, remove it completely
                execute-keys d
            ]
        ]
    ]
] ]

§

# Initialization
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾

hook -group r-highlight global WinSetOption filetype=r %{
    require-module r
    add-highlighter window/r ref r
    hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/r }
}

hook global WinSetOption filetype=r %~
    require-module r
    hook window ModeChange pop:insert:.* r-trim-indent
    hook window InsertChar \n        r-insert-on-newline
    hook window InsertChar \n        r-indent-on-newline
    hook window InsertChar \{        r-indent-on-opening-curly-brace
    hook window InsertChar \}        r-indent-on-closing-curly-brace

    hook -once -always window WinSetOption filetype=.* %{ remove-hooks window r-.+ }
~