pattern.bash (10187B)
1 # ble/contrib/layer/pattern.bash (C) 2023, akinomyoga 2 # 3 # @fn ble/highlight/layer:{pattern}/declare name [type] 4 # Define a new layer named `name`. 5 # 6 # @param[in] name 7 # The name of the layer. 8 # @param[in,opt] type 9 # This specifies the type of the pattern of this layer. One of the 10 # following values. The default is `regexp`. 11 # 12 # regexp 13 # Regular expression 14 # glob 15 # Extended glob pattern 16 # glob-shortest 17 # Extended glob pattern. This tries to find the shortest match. 18 # 19 # @fn ble/highlight/layer:{pattern}/register name pattern gspec 20 # Register a pattern to the layer specified by `name`. 21 # 22 # @param[in] name 23 # The name of the layer. 24 # @param[in] pattern 25 # The pattern. The type of the pattern is specified by the argument `type` 26 # when the layer is created by `ble/highlight/layer:{pattern}/declare`. 27 # @param[in] gspec 28 # A string specifying the graphic style. See the description of 29 # ble/color/gspec2g. 30 # 31 # 32 # ```bash 33 # # blerc 34 # 35 # ble-import layer/pattern 36 # 37 # ble/highlight/layer:{pattern}/declare pattern1 38 # ble/highlight/layer:{pattern}/register pattern1 'rm -rf [^;&|]*' 'fg=white,bold,bg=red' 39 # ble/array#insert-after _ble_highlight_layer_list syntax pattern1 40 # 41 # ble/highlight/layer:{pattern}/declare pattern2 42 # ble/highlight/layer:{pattern}/register pattern2 "$USER" 'fg=blue,bold' 43 # ble/highlight/layer:{pattern}/register pattern2 "$HOSTNAME" 'fg=green,bold' 44 # ble/highlight/layer:{pattern}/register pattern2 '[0-9]+' 'bg=216,fg=black' 45 # ble/array#insert-after _ble_highlight_layer_list pattern1 pattern2 46 # ``` 47 48 function ble/highlight/layer:{pattern}/declare { 49 local layer_name=$1 type=${2-regexp} 50 case $type in 51 (regexp | glob) ;; 52 (glob-shortest) type=sglob ;; 53 (*) 54 ble/util/print "$FUNCNAME: unrecognized pattern type '$type'." >&2 55 return 2 ;; 56 esac 57 58 # define dynamic variables 59 local layer_prefix=_ble_highlight_layer_${layer_name}_ 60 ble/highlight/layer:{selection}/declare "$layer_name" 61 ble/array#push "${layer_prefix}VARNAMES" "${layer_prefix}text" 62 ble/util/set "${layer_prefix}text" '' 63 64 # define settings 65 local keys=${layer_prefix}keys 66 local dict=${layer_prefix}dict 67 builtin eval -- " 68 ${layer_prefix}type=$type 69 $keys=() 70 ${_ble_util_gdict_declare//NAME/$dict}" 71 72 # define functions 73 local _ble_local_script=' 74 function ble/highlight/layer:LAYER/initialize-vars { 75 ble/highlight/layer:{pattern}/initialize-vars LAYER 76 } 77 function ble/highlight/layer:LAYER/update { 78 ble/highlight/layer:{pattern}/update LAYER "$@" 79 } 80 function ble/highlight/layer:LAYER/getg { 81 ble/highlight/layer:{pattern}/getg LAYER "$@" 82 }' 83 builtin eval -- "${_ble_local_script//LAYER/$layer_name}" 84 } 85 86 function ble/highlight/layer:{pattern}/initialize-vars { 87 local layer_name=$1 88 ble/highlight/layer:{selection}/initialize-vars "$layer_name" 89 ble/util/set "_ble_highlight_layer_${layer_name}_text" '' 90 } 91 92 function ble/highlight/layer:{pattern}/register { 93 local layer_name=$1 pattern=$2 spec=${3-} 94 local keys=_ble_highlight_layer_${layer_name}_keys 95 local dict=_ble_highlight_layer_${layer_name}_dict 96 if [[ ${3+set} ]]; then 97 local ret 98 ble/color/gspec2g "$spec" 99 ble/gdict#has "$dict" "$pattern" || 100 ble/array#push "$keys" "$pattern" 101 ble/gdict#set "$dict" "$pattern" "$ret" 102 else 103 ble/array#remove "$keys" "$pattern" 104 ble/gdict#unset "$dict" "$pattern" 105 fi 106 } 107 108 ##----------------------------------------------------------------------------- 109 ## Pattern types 110 ## 111 ## Each pattern layer instance is associated with a pattern type, and the 112 ## pattern type defines how the pattern specified to layer:{pattern}/register 113 ## should be treated. Currently, three types `regexp`, `glob`, and `sglob` are 114 ## defined. To define a new pattern type, the following three functions should 115 ## be prepared. 116 ## 117 ## @fn ble/highlight/layer:{pattern}/pattern:<TYPE>/create-gpat 118 ## This function composes a pattern matching any of the registered patterns 119 ## 120 ## @arr[in] keys 121 ## The list of patterns registered to the current layer. 122 ## @var[out] gpat 123 ## Stores a pattern that matches any of the registered pattern. 124 ## 125 ## @fn ble/highlight/layer:{pattern}/pattern:<TYPE>/match text pat 126 ## This function tries to match PAT in TEXT and, if matching, stores the 127 ## matched range in [MBEG, MEND) and returns the unmatched suffix in 128 ## NEW_TAIL. 129 ## 130 ## @param[in] text 131 ## The string where a matching substring is searched. 132 ## @param[in] pat 133 ## The pattern 134 ## @var[out] mbeg mend 135 ## Stores the matched range in TEXT. MBEG and MEND are the beginning and 136 ## the end of the range, respectively. 137 ## @var[out] new_tail 138 ## Stores the unmatched remaining part of TEXT. In particular, the 139 ## substring after MEND. 140 ## @exit 141 ## 0 if a matching is found, or otherwise 1. 142 ## 143 ## @fn ble/highlight/layer:{pattern}/pattern:regexp/match1 str pat 144 ## This function tests if the specified string exactly matches the pattern. 145 ## 146 ## @param[in] str 147 ## The string to be matched. 148 ## @param[in] pat 149 ## The pattern to matched STR. 150 ## @exit 151 ## 0 if the string matches the pattern, or otherwise 1 152 ## 153 154 # pattern type: regexp 155 156 function ble/highlight/layer:{pattern}/pattern:regexp/create-gpat { 157 IFS='|' builtin eval -- 'gpat="(${keys[*]})"' 158 } 159 function ble/highlight/layer:{pattern}/pattern:regexp/match { 160 ble/string#match "$1" "$2(.*)\$" || return 1 161 new_tail=${BASH_REMATCH[${#BASH_REMATCH[@]}-1]} 162 mbeg=$((${#1}-${#BASH_REMATCH})) 163 mend=$((${#1}-${#new_tail})) 164 return 0 165 } 166 function ble/highlight/layer:{pattern}/pattern:regexp/match1 { 167 [[ $1 =~ ^($2)$ ]] 168 } 169 170 # pattern type: glob (longest extended glob matching) 171 172 function ble/highlight/layer:{pattern}/pattern:glob/create-gpat { 173 IFS='|' builtin eval -- 'gpat="@(${keys[*]})"' 174 } 175 function ble/highlight/layer:{pattern}/pattern:glob/match { 176 local extglob= 177 shopt -q extglob && extglob=1 178 shopt -s extglob 179 local prefix=${1%%$2*} ext=1 180 if [[ $prefix != "$1" ]]; then 181 mbeg=${#prefix} 182 new_tail=${1:mbeg} 183 new_tail=${new_tail##$2} 184 ((mend=${#1}-${#new_tail})) 185 ext=0 186 fi 187 [[ $extglob ]] || shopt -u extglob 188 return "$ext" 189 } 190 function ble/highlight/layer:{pattern}/pattern:glob/match1 { 191 [[ $1 == $2 ]] 192 } 193 194 # pattern type: sglob (shortest extended glob matching) 195 196 function ble/highlight/layer:{pattern}/pattern:sglob/create-gpat { 197 ble/highlight/layer:{pattern}/pattern:glob/create-gpat 198 } 199 function ble/highlight/layer:{pattern}/pattern:sglob/match { 200 local extglob= 201 shopt -q extglob && extglob=1 202 shopt -s extglob 203 local prefix=${1%%$2*} ext=1 204 if [[ $prefix != "$1" ]]; then 205 mbeg=${#prefix} 206 new_tail=${1:mbeg} 207 new_tail=${new_tail#$2} 208 ((mend=${#1}-${#new_tail})) 209 ext=0 210 fi 211 [[ $extglob ]] || shopt -u extglob 212 return "$ext" 213 } 214 function ble/highlight/layer:{pattern}/pattern:sglob/match1 { 215 ble/highlight/layer:{pattern}/pattern:glob/match1 "$@" 216 } 217 218 ##----------------------------------------------------------------------------- 219 220 function ble/highlight/layer:{pattern}/.match { 221 # If the text has the same content as the previous time, we skip the 222 # matching. 223 # 224 # Note: Initially, ((DMIN<0)) was used for the condition but turned out to be 225 # unusable for this purpose. DMIN only changes when the full content 226 # including the auto_complete insertion is changed. Even if the substantial 227 # part (excluding the auto_complete insertion) changes, DMIN can be negative 228 # when the full content does not change. 229 local rtext=_ble_highlight_layer_${1}_text 230 local text=$2 otext=${!rtext} 231 [[ $otext && $text == "$otext" ]] && return 0 232 ble/util/set "$rtext" "$text" 233 234 local ret 235 local dict=_ble_highlight_layer_${1}_dict 236 sel=() gflags=() 237 238 # Retrieve regular expressions 239 local keys type 240 ble/util/restore-vars "_ble_highlight_layer_${1}_" keys type 241 ((${#keys[@]})) || return 0 242 243 local gpat 244 ble/highlight/layer:{pattern}/pattern:"$type"/create-gpat 245 local g0= 246 if ((${#keys[@]}==1)); then 247 ble/gdict#get "$dict" "${keys[0]}" && g0=$ret 248 fi 249 250 local offset=0 tail=$text new_tail mbeg mend m 251 while [[ $tail ]] && ble/highlight/layer:{pattern}/pattern:"$type"/match "$tail" "$gpat"; do 252 ((mbeg+=offset,mend+=offset)) 253 if ((mbeg<mend)); then 254 # determine gflags of the selection 255 local g1=$g0 256 if [[ ! $g1 ]]; then 257 local pat1 m=${tail:mbeg-offset:mend-mbeg} 258 for pat1 in "${keys[@]}"; do 259 if ble/highlight/layer:{pattern}/pattern:"$type"/match1 "$m" "$pat1"; then 260 ble/gdict#get "$dict" "$pat1" && g1=$ret 261 break 262 fi 263 done 264 fi 265 266 # add selection with the gflags 267 if [[ $g1 ]]; then 268 if ((mbeg==offset&&${#sel[@]})) && [[ ${gflags[${#gflags[@]}-1]} == $g1 ]]; then 269 # extend the previous selection 270 sel[${#sel[@]}-1]=$mend 271 else 272 # add a new selection 273 ble/array#push sel "$mbeg" "$mend" 274 ble/array#push gflags "$g1" 275 fi 276 fi 277 fi 278 279 if ((mend==offset)); then 280 # step at least one character to avoid infinite matching 281 ((offset++)) 282 tail=${tail:1} 283 else 284 offset=$mend 285 tail=$new_tail 286 fi 287 done 288 } 289 290 function ble/highlight/layer:{pattern}/update { 291 local layer_name=$1 text=$2 292 local sel=-1 gflags=-1 293 294 local text1=$text 295 if [[ $_ble_edit_mark_active == auto_complete ]]; then 296 # When there is an insertion by auto-complete, we exclude that part from 297 # the matching target. 298 local a=$_ble_edit_ind b=$_ble_edit_mark 299 text1=${text::a}${text:b} 300 fi 301 ble/highlight/layer:{pattern}/.match "$layer_name" "$text1" 302 if [[ $_ble_edit_mark_active == auto_complete ]]; then 303 # The generated positions in the array "sel" is for "text1" where the 304 # auto-complete insertion is excluded. Here, we shift the positions in 305 # "sel" to consider the excluded part. 306 local i 307 for i in "${!sel[@]}"; do 308 ((sel[i]>a||sel[i]==a&&i%2==0)) && ((sel[i]+=b-a)) 309 done 310 fi 311 312 ble/highlight/layer:{selection}/update "$layer_name" "$text" 313 } 314 315 function ble/highlight/layer:{pattern}/getg { 316 ble/highlight/layer:{selection}/getg "$@" 317 }