summaryrefslogtreecommitdiff
path: root/rc/base/ruby.kak
blob: c615c595c65c6eb9f6c543e74977ae2b6d2c7c6c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# http://ruby-lang.org
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾

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

hook global BufCreate .*(([.](rb))|(irbrc)|(pryrc)|(Capfile|[.]cap)|(Gemfile|[.]gemspec)|(Guardfile)|(Rakefile|[.]rake)|(Thorfile|[.]thor)|(Vagrantfile)) %{
    set-option buffer filetype ruby
}

# Highlighters
# ‾‾‾‾‾‾‾‾‾‾‾‾

add-highlighter shared/ regions -default code -match-capture ruby       \
    double_string '"' (?<!\\)(\\\\)*"        '' \
    single_string "'" (?<!\\)(\\\\)*'        '' \
    backtick      '`' (?<!\\)(\\\\)*`        '' \
    regex         '/' (?<!\\)(\\\\)*/[imox]* '' \
    comment       '#' '$'                    '' \
    comment       ^begin= ^=end              '' \
    literal       '%[iqrswxIQRSWX]\(' \)     \( \
    literal       '%[iqrswxIQRSWX]\{' \}     \{ \
    literal       '%[iqrswxIQRSWX]\[' \]     \[ \
    literal       '%[iqrswxIQRSWX]<'   >      < \
    heredoc '<<[-~]?(\w+)'      '^\h*(\w+)$' '' \
    division '[\w\)\]](/|(\h+/\h+))' '\w' '' # Help Kakoune to better detect /…/ literals

# Regular expression flags are: i → ignore case, m → multi-lines, o → only interpolate #{} blocks once, x → extended mode (ignore white spaces)
# Literals are: i → array of symbols, q → string, r → regular expression, s → symbol, w → array of words, x → capture shell result

add-highlighter shared/ruby/double_string fill string
add-highlighter shared/ruby/double_string regions regions interpolation \Q#{ \} \{
add-highlighter shared/ruby/double_string/regions/interpolation fill meta

add-highlighter shared/ruby/single_string fill string
add-highlighter shared/ruby/heredoc fill string

add-highlighter shared/ruby/backtick fill meta
add-highlighter shared/ruby/backtick regions regions interpolation \Q#{ \} \{
add-highlighter shared/ruby/backtick/regions/interpolation fill meta

add-highlighter shared/ruby/regex fill meta
add-highlighter shared/ruby/regex regions regions interpolation \Q#{ \} \{
add-highlighter shared/ruby/regex/regions/interpolation fill meta

add-highlighter shared/ruby/comment fill comment

add-highlighter shared/ruby/literal fill meta

add-highlighter shared/ruby/code regex \b([A-Za-z]\w*:(?!:))|([$@][A-Za-z]\w*)|((?<!:):(([A-Za-z]\w*[=?!]?)|(\[\]=?)))|([A-Z]\w*|^|\h)\K::(?=[A-Z]) 0:variable

%sh{
    # Grammar
    # Keywords are collected searching for keywords at
    # https://github.com/ruby/ruby/blob/trunk/parse.y
    keywords="alias|and|begin|break|case|class|def|defined|do|else|elsif|end"
    keywords="${keywords}|ensure|false|for|if|in|module|next|nil|not|or|private|protected|public|redo"
    keywords="${keywords}|rescue|retry|return|self|super|then|true|undef|unless|until|when|while|yield"
    attributes="attr_reader|attr_writer|attr_accessor"
    values="false|true|nil"
    meta="require|include|extend"

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

    # Highlight keywords
    printf %s "
        add-highlighter shared/ruby/code regex \b(${keywords})\b 0:keyword
        add-highlighter shared/ruby/code regex \b(${attributes})\b 0:attribute
        add-highlighter shared/ruby/code regex \b(${values})\b 0:value
        add-highlighter shared/ruby/code regex \b(${meta})\b 0:meta
    "
}

# Commands
# ‾‾‾‾‾‾‾‾

define-command ruby-alternative-file -docstring 'Jump to the alternate file (implementation ↔ test)' %{ %sh{
    case $kak_buffile in
        *spec/*_spec.rb)
            altfile=$(eval echo $(echo $kak_buffile | sed s+spec/+'*'/+';'s/_spec//))
            [ ! -f $altfile ] && echo "echo -markup '{Error}implementation file not found'" && exit
        ;;
        *.rb)
            path=$kak_buffile
            dirs=$(while [ $path ]; do echo $path; path=${path%/*}; done | tail -n +2)
            for dir in $dirs; do
                altdir=$dir/spec
                if [ -d $altdir ]; then
                    altfile=$altdir/$(realpath $kak_buffile --relative-to $dir | sed s+[^/]'*'/++';'s/.rb$/_spec.rb/)
                    break
                fi
            done
            [ ! -d $altdir ] && echo "echo -markup '{Error}spec/ not found'" && exit
        ;;
        *)
            echo "echo -markup '{Error}alternative file not found'" && exit
        ;;
    esac
    echo "edit $altfile"
}}

define-command -hidden ruby-filter-around-selections %{
    evaluate-commands -no-hooks -draft -itersel %{
        execute-keys <a-x>
        # remove trailing white spaces
        try %{ execute-keys -draft s \h + $ <ret> d }
    }
}

define-command -hidden ruby-indent-on-char %{
    evaluate-commands -no-hooks -draft -itersel %{
        # align middle and end structures to start
        try %{ execute-keys -draft <a-x> <a-k> ^ \h * (else|elsif) $ <ret> <a-\;> <a-?> ^ \h * (if)                                                       <ret> s \A | \z <ret> ) <a-&> }
        try %{ execute-keys -draft <a-x> <a-k> ^ \h * (when)       $ <ret> <a-\;> <a-?> ^ \h * (case)                                                     <ret> s \A | \z <ret> ) <a-&> }
        try %{ execute-keys -draft <a-x> <a-k> ^ \h * (rescue)     $ <ret> <a-\;> <a-?> ^ \h * (begin)                                                    <ret> s \A | \z <ret> ) <a-&> }
        try %{ execute-keys -draft <a-x> <a-k> ^ \h * (end)        $ <ret> <a-\;> <a-?> ^ \h * (begin|case|class|def|do|for|if|module|unless|until|while) <ret> s \A | \z <ret> ) <a-&> }
    }
}

define-command -hidden ruby-indent-on-new-line %{
    evaluate-commands -no-hooks -draft -itersel %{
        # preserve previous line indent
        try %{ execute-keys -draft K <a-&> }
        # filter previous line
        try %{ execute-keys -draft k : ruby-filter-around-selections <ret> }
        # indent after start structure
        try %{ execute-keys -draft k <a-x> <a-k> ^ \h * (begin|case|class|def|do|else|elsif|ensure|for|if|module|rescue|unless|until|when|while) \b <ret> j <a-gt> }
    }
}

define-command -hidden ruby-insert-on-new-line %[
    evaluate-commands -no-hooks -draft -itersel %[
        # copy _#_ comment prefix and following white spaces
        try %{ execute-keys -draft k <a-x> s '^\h*\K#\h*' <ret> y gh j P }
        # wisely add end structure
        evaluate-commands -save-regs x %[
            try %{ execute-keys -draft k <a-x> s ^ \h + <ret> \" x y } catch %{ reg x '' }
            try %[
                evaluate-commands -draft %[
                    execute-keys -draft k<a-x> <a-k>^<c-r>x(begin|case|class|def|do|for|if|module|unless|until|while)<ret> # Check if previous line opens a block
                    # Check that we do not already have an end for this indent level, or that we have another block opening at that indent level first
                    execute-keys -draft Ge <a-K>\A(^\n|^<c-r>x(?!begin)(?!case)(?!class)(?!def)(?!do)(?!for)(?!if)(?!module)(?!unless)(?!until)(?!while)[^\n]*\n)+<c-r>xend$<ret>
                ]
                execute-keys -draft o<c-r>xend<esc> # insert a new line with containing end
            ]
        ]
    ]
]

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

hook -group ruby-highlight global WinSetOption filetype=ruby %{ add-highlighter window ref ruby }

hook global WinSetOption filetype=ruby %{
    hook window InsertChar .* -group ruby-indent ruby-indent-on-char
    hook window InsertChar \n -group ruby-insert ruby-insert-on-new-line
    hook window InsertChar \n -group ruby-indent ruby-indent-on-new-line

    alias window alt ruby-alternative-file
}

hook -group ruby-highlight global WinSetOption filetype=(?!ruby).* %{ remove-highlighter window/ruby }

hook global WinSetOption filetype=(?!ruby).* %{
    remove-hooks window ruby-indent
    remove-hooks window ruby-insert

    unalias window alt ruby-alternative-file
}