forked from zsh-users/zsh-syntax-highlighting
-
Notifications
You must be signed in to change notification settings - Fork 0
/
zsh-syntax-highlighting.zsh
440 lines (372 loc) · 17.4 KB
/
zsh-syntax-highlighting.zsh
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# -------------------------------------------------------------------------------------------------
# Copyright (c) 2010-2016 zsh-syntax-highlighting contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted
# provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this list of conditions
# and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice, this list of
# conditions and the following disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors
# may be used to endorse or promote products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# -------------------------------------------------------------------------------------------------
# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
# vim: ft=zsh sw=2 ts=2 et
# -------------------------------------------------------------------------------------------------
# First of all, ensure predictable parsing.
zsh_highlight__aliases=`builtin alias -Lm '[^+]*'`
# In zsh <= 5.2, `alias -L` emits aliases that begin with a plus sign ('alias -- +foo=42')
# them without a '--' guard, so they don't round trip.
#
# Hence, we exclude them from unaliasing:
builtin unalias -m '[^+]*'
# Set $0 to the expected value, regardless of functionargzero.
0=${(%):-%N}
if true; then
# $0 is reliable
typeset -g ZSH_HIGHLIGHT_VERSION=$(<"${0:A:h}"/.version)
typeset -g ZSH_HIGHLIGHT_REVISION=$(<"${0:A:h}"/.revision-hash)
if [[ $ZSH_HIGHLIGHT_REVISION == \$Format:* ]]; then
# When running from a source tree without 'make install', $ZSH_HIGHLIGHT_REVISION
# would be set to '$Format:%H$' literally. That's an invalid value, and obtaining
# the valid value (via `git rev-parse HEAD`, as Makefile does) might be costly, so:
ZSH_HIGHLIGHT_REVISION=HEAD
fi
fi
# -------------------------------------------------------------------------------------------------
# Core highlighting update system
# -------------------------------------------------------------------------------------------------
# Use workaround for bug in ZSH?
# zsh-users/zsh@48cadf4 http://www.zsh.org/mla/workers//2017/msg00034.html
autoload -Uz is-at-least
if is-at-least 5.4; then
zsh_highlight__pat_static_bug=false
else
zsh_highlight__pat_static_bug=true
fi
# Array declaring active highlighters names.
typeset -ga ZSH_HIGHLIGHT_HIGHLIGHTERS
# Update ZLE buffer syntax highlighting.
#
# Invokes each highlighter that needs updating.
# This function is supposed to be called whenever the ZLE state changes.
_zsh_highlight()
{
# Store the previous command return code to restore it whatever happens.
local ret=$?
# Remove all highlighting in isearch, so that only the underlining done by zsh itself remains.
# For details see FAQ entry 'Why does syntax highlighting not work while searching history?'.
# This disables highlighting during isearch (for reasons explained in README.md) unless zsh is new enough
# and doesn't have the pattern matching bug
if [[ $WIDGET == zle-isearch-update ]] && { $zsh_highlight__pat_static_bug || ! (( $+ISEARCHMATCH_ACTIVE )) }; then
region_highlight=()
return $ret
fi
# Before we 'emulate -L', save the user's options
local -A zsyh_user_options
if zmodload -e zsh/parameter; then
zsyh_user_options=("${(@kv)options}")
else
local canonical_options onoff option raw_options
raw_options=(${(f)"$(emulate -R zsh; set -o)"})
canonical_options=(${${${(M)raw_options:#*off}%% *}#no} ${${(M)raw_options:#*on}%% *})
for option in $canonical_options; do
[[ -o $option ]]
# This variable cannot be eliminated c.f. workers/42101.
onoff=${${=:-off on}[2-$?]}
zsyh_user_options+=($option $onoff)
done
fi
typeset -r zsyh_user_options
emulate -L zsh
setopt localoptions warncreateglobal
local REPLY # don't leak $REPLY into global scope
# Do not highlight if there are more than 300 chars in the buffer. It's most
# likely a pasted command or a huge list of files in that case..
[[ -n ${ZSH_HIGHLIGHT_MAXLENGTH:-} ]] && [[ $#BUFFER -gt $ZSH_HIGHLIGHT_MAXLENGTH ]] && return $ret
# Do not highlight if there are pending inputs (copy/paste).
[[ $PENDING -gt 0 ]] && return $ret
# Reset region highlight to build it from scratch
typeset -ga region_highlight
region_highlight=();
{
local cache_place
local -a region_highlight_copy
# Select which highlighters in ZSH_HIGHLIGHT_HIGHLIGHTERS need to be invoked.
local highlighter; for highlighter in $ZSH_HIGHLIGHT_HIGHLIGHTERS; do
# eval cache place for current highlighter and prepare it
cache_place="_zsh_highlight__highlighter_${highlighter}_cache"
typeset -ga ${cache_place}
# If highlighter needs to be invoked
if ! type "_zsh_highlight_highlighter_${highlighter}_predicate" >&/dev/null; then
echo "zsh-syntax-highlighting: warning: disabling the ${(qq)highlighter} highlighter as it has not been loaded" >&2
# TODO: use ${(b)} rather than ${(q)} if supported
ZSH_HIGHLIGHT_HIGHLIGHTERS=( ${ZSH_HIGHLIGHT_HIGHLIGHTERS:#${highlighter}} )
elif "_zsh_highlight_highlighter_${highlighter}_predicate"; then
# save a copy, and cleanup region_highlight
region_highlight_copy=("${region_highlight[@]}")
region_highlight=()
# Execute highlighter and save result
{
"_zsh_highlight_highlighter_${highlighter}_paint"
} always {
: ${(AP)cache_place::="${region_highlight[@]}"}
}
# Restore saved region_highlight
region_highlight=("${region_highlight_copy[@]}")
fi
# Use value form cache if any cached
region_highlight+=("${(@P)cache_place}")
done
# Re-apply zle_highlight settings
# region
if (( REGION_ACTIVE == 1 )); then
_zsh_highlight_apply_zle_highlight region standout "$MARK" "$CURSOR"
elif (( REGION_ACTIVE == 2 )); then
() {
local needle=$'\n'
integer min max
if (( MARK > CURSOR )) ; then
min=$CURSOR max=$MARK
else
min=$MARK max=$CURSOR
fi
# CURSOR and MARK are 0 indexed between letters like region_highlight
# Do not include the newline in the highlight
(( min = ${BUFFER[(Ib:min:)$needle]} ))
(( max = ${BUFFER[(ib:max:)$needle]} - 1 ))
_zsh_highlight_apply_zle_highlight region standout "$min" "$max"
}
fi
# yank / paste (zsh-5.1.1 and newer)
(( $+YANK_ACTIVE )) && (( YANK_ACTIVE )) && _zsh_highlight_apply_zle_highlight paste standout "$YANK_START" "$YANK_END"
# isearch
(( $+ISEARCHMATCH_ACTIVE )) && (( ISEARCHMATCH_ACTIVE )) && _zsh_highlight_apply_zle_highlight isearch underline "$ISEARCHMATCH_START" "$ISEARCHMATCH_END"
# suffix
(( $+SUFFIX_ACTIVE )) && (( SUFFIX_ACTIVE )) && _zsh_highlight_apply_zle_highlight suffix bold "$SUFFIX_START" "$SUFFIX_END"
return $ret
} always {
typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER="$BUFFER"
typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=$CURSOR
}
}
# Apply highlighting based on entries in the zle_highlight array.
# This function takes four arguments:
# 1. The exact entry (no patterns) in the zle_highlight array:
# region, paste, isearch, or suffix
# 2. The default highlighting that should be applied if the entry is unset
# 3. and 4. Two integer values describing the beginning and end of the
# range. The order does not matter.
_zsh_highlight_apply_zle_highlight() {
local entry="$1" default="$2"
integer first="$3" second="$4"
# read the relevant entry from zle_highlight
#
# ### In zsh≥5.0.8 we'd use ${(b)entry}, but we support older zsh's, so we don't
# ### add (b). The only effect is on the failure mode for callers that violate
# ### the precondition.
local region="${zle_highlight[(r)${entry}:*]-}"
if [[ -z "$region" ]]; then
# entry not specified at all, use default value
region=$default
else
# strip prefix
region="${region#${entry}:}"
# no highlighting when set to the empty string or to 'none'
if [[ -z "$region" ]] || [[ "$region" == none ]]; then
return
fi
fi
integer start end
if (( first < second )); then
start=$first end=$second
else
start=$second end=$first
fi
region_highlight+=("$start $end $region")
}
# -------------------------------------------------------------------------------------------------
# API/utility functions for highlighters
# -------------------------------------------------------------------------------------------------
# Array used by highlighters to declare user overridable styles.
typeset -gA ZSH_HIGHLIGHT_STYLES
# Whether the command line buffer has been modified or not.
#
# Returns 0 if the buffer has changed since _zsh_highlight was last called.
_zsh_highlight_buffer_modified()
{
[[ "${_ZSH_HIGHLIGHT_PRIOR_BUFFER:-}" != "$BUFFER" ]]
}
# Whether the cursor has moved or not.
#
# Returns 0 if the cursor has moved since _zsh_highlight was last called.
_zsh_highlight_cursor_moved()
{
[[ -n $CURSOR ]] && [[ -n ${_ZSH_HIGHLIGHT_PRIOR_CURSOR-} ]] && (($_ZSH_HIGHLIGHT_PRIOR_CURSOR != $CURSOR))
}
# Add a highlight defined by ZSH_HIGHLIGHT_STYLES.
#
# Should be used by all highlighters aside from 'pattern' (cf. ZSH_HIGHLIGHT_PATTERN).
# Overwritten in tests/test-highlighting.zsh when testing.
_zsh_highlight_add_highlight()
{
local -i start end
local highlight
start=$1
end=$2
shift 2
for highlight; do
if (( $+ZSH_HIGHLIGHT_STYLES[$highlight] )); then
region_highlight+=("$start $end $ZSH_HIGHLIGHT_STYLES[$highlight]")
break
fi
done
}
# -------------------------------------------------------------------------------------------------
# Setup functions
# -------------------------------------------------------------------------------------------------
# Helper for _zsh_highlight_bind_widgets
# $1 is name of widget to call
_zsh_highlight_call_widget()
{
builtin zle "$@" &&
_zsh_highlight
}
# Rebind all ZLE widgets to make them invoke _zsh_highlights.
_zsh_highlight_bind_widgets()
{
setopt localoptions noksharrays
typeset -F SECONDS
local prefix=orig-s$SECONDS-r$RANDOM # unique each time, in case we're sourced more than once
# Load ZSH module zsh/zleparameter, needed to override user defined widgets.
zmodload zsh/zleparameter 2>/dev/null || {
print -r -- >&2 'zsh-syntax-highlighting: failed loading zsh/zleparameter.'
return 1
}
# Override ZLE widgets to make them invoke _zsh_highlight.
local -U widgets_to_bind
widgets_to_bind=(${${(k)widgets}:#(.*|run-help|which-command|beep|set-local-history|yank|yank-pop)})
# Always wrap special zle-line-finish widget. This is needed to decide if the
# current line ends and special highlighting logic needs to be applied.
# E.g. remove cursor imprint, don't highlight partial paths, ...
widgets_to_bind+=(zle-line-finish)
# Always wrap special zle-isearch-update widget to be notified of updates in isearch.
# This is needed because we need to disable highlighting in that case.
widgets_to_bind+=(zle-isearch-update)
local cur_widget
for cur_widget in $widgets_to_bind; do
case ${widgets[$cur_widget]:-""} in
# Already rebound event: do nothing.
user:_zsh_highlight_widget_*);;
# The "eval"'s are required to make $cur_widget a closure: the value of the parameter at function
# definition time is used.
#
# We can't use ${0/_zsh_highlight_widget_} because these widgets are always invoked with
# NO_function_argzero, regardless of the option's setting here.
# User defined widget: override and rebind old one with prefix "orig-".
user:*) zle -N $prefix-$cur_widget ${widgets[$cur_widget]#*:}
eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }"
zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;;
# Completion widget: override and rebind old one with prefix "orig-".
completion:*) zle -C $prefix-$cur_widget ${${(s.:.)widgets[$cur_widget]}[2,3]}
eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget ${(q)prefix}-${(q)cur_widget} -- \"\$@\" }"
zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;;
# Builtin widget: override and make it call the builtin ".widget".
builtin) eval "_zsh_highlight_widget_${(q)prefix}-${(q)cur_widget}() { _zsh_highlight_call_widget .${(q)cur_widget} -- \"\$@\" }"
zle -N $cur_widget _zsh_highlight_widget_$prefix-$cur_widget;;
# Incomplete or nonexistent widget: Bind to z-sy-h directly.
*)
if [[ $cur_widget == zle-* ]] && (( ! ${+widgets[$cur_widget]} )); then
_zsh_highlight_widget_${cur_widget}() { :; _zsh_highlight }
zle -N $cur_widget _zsh_highlight_widget_$cur_widget
else
# Default: unhandled case.
print -r -- >&2 "zsh-syntax-highlighting: unhandled ZLE widget ${(qq)cur_widget}"
print -r -- >&2 "zsh-syntax-highlighting: (This is sometimes caused by doing \`bindkey <keys> ${(q-)cur_widget}\` without creating the ${(qq)cur_widget} widget with \`zle -N\` or \`zle -C\`.)"
fi
esac
done
}
# Load highlighters from directory.
#
# Arguments:
# 1) Path to the highlighters directory.
_zsh_highlight_load_highlighters()
{
setopt localoptions noksharrays bareglobqual
# Check the directory exists.
[[ -d "$1" ]] || {
print -r -- >&2 "zsh-syntax-highlighting: highlighters directory ${(qq)1} not found."
return 1
}
# Load highlighters from highlighters directory and check they define required functions.
local highlighter highlighter_dir
for highlighter_dir ($1/*/(/)); do
highlighter="${highlighter_dir:t}"
[[ -f "$highlighter_dir${highlighter}-highlighter.zsh" ]] &&
. "$highlighter_dir${highlighter}-highlighter.zsh"
if type "_zsh_highlight_highlighter_${highlighter}_paint" &> /dev/null &&
type "_zsh_highlight_highlighter_${highlighter}_predicate" &> /dev/null;
then
# New (0.5.0) function names
elif type "_zsh_highlight_${highlighter}_highlighter" &> /dev/null &&
type "_zsh_highlight_${highlighter}_highlighter_predicate" &> /dev/null;
then
# Old (0.4.x) function names
if false; then
# TODO: only show this warning for plugin authors/maintainers, not for end users
print -r -- >&2 "zsh-syntax-highlighting: warning: ${(qq)highlighter} highlighter uses deprecated entry point names; please ask its maintainer to update it: https://github.com/zsh-users/zsh-syntax-highlighting/issues/329"
fi
# Make it work.
eval "_zsh_highlight_highlighter_${(q)highlighter}_paint() { _zsh_highlight_${(q)highlighter}_highlighter \"\$@\" }"
eval "_zsh_highlight_highlighter_${(q)highlighter}_predicate() { _zsh_highlight_${(q)highlighter}_highlighter_predicate \"\$@\" }"
else
print -r -- >&2 "zsh-syntax-highlighting: ${(qq)highlighter} highlighter should define both required functions '_zsh_highlight_highlighter_${highlighter}_paint' and '_zsh_highlight_highlighter_${highlighter}_predicate' in ${(qq):-"$highlighter_dir${highlighter}-highlighter.zsh"}."
fi
done
}
# -------------------------------------------------------------------------------------------------
# Setup
# -------------------------------------------------------------------------------------------------
# Try binding widgets.
_zsh_highlight_bind_widgets || {
print -r -- >&2 'zsh-syntax-highlighting: failed binding ZLE widgets, exiting.'
return 1
}
# Resolve highlighters directory location.
_zsh_highlight_load_highlighters "${ZSH_HIGHLIGHT_HIGHLIGHTERS_DIR:-${${0:A}:h}/highlighters}" || {
print -r -- >&2 'zsh-syntax-highlighting: failed loading highlighters, exiting.'
return 1
}
# Reset scratch variables when commandline is done.
_zsh_highlight_preexec_hook()
{
typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER=
typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=
}
autoload -Uz add-zsh-hook
add-zsh-hook preexec _zsh_highlight_preexec_hook 2>/dev/null || {
print -r -- >&2 'zsh-syntax-highlighting: failed loading add-zsh-hook.'
}
# Load zsh/parameter module if available
zmodload zsh/parameter 2>/dev/null || true
# Initialize the array of active highlighters if needed.
[[ $#ZSH_HIGHLIGHT_HIGHLIGHTERS -eq 0 ]] && ZSH_HIGHLIGHT_HIGHLIGHTERS=(main)
# Restore the aliases we unned
eval "$zsh_highlight__aliases"
builtin unset zsh_highlight__aliases
# Set $?.
true