├── .gitignore ├── LICENSE ├── README ├── cbuild.sh ├── conf.c ├── ex.c ├── kmap.h ├── lbuf.c ├── led.c ├── regex.c ├── ren.c ├── term.c ├── uc.c ├── vi.c └── vi.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.exe 3 | tags 4 | vi 5 | file 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | NEXTVI Editor 2 | 3 | Copyright (C) 2015-2019 Ali Gholami Rudi 4 | Copyright (C) 2020-2025 Kyryl Melekhin 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Nextvi(1) General Commands Manual Nextvi(1) 2 | 3 | NAME 4 | Nextvi - A small vi/ex terminal text editor 5 | 6 | SYNOPSIS 7 | vi [-emsv] [file ...] 8 | 9 | DESCRIPTION 10 | Nextvi is a modern clone of the command-line text editor vi(1), 11 | initially developed by Bill Joy in 1976 for Unix-based systems. 12 | Nextvi builds upon many standard features from vi(1) including 13 | unique modal interface that allows users to switch between normal, 14 | insert, and command modes, for efficient text manipulation. 15 | Additional enhancements include an unrestricted macro system, 16 | syntax highlighting, keymaps, bidirectional UTF-8 support, and 17 | numerous other features. Nextvi remains highly efficient, portable, 18 | and hackable, ensuring its continued relevance and high quality 19 | for years to come. 20 | 21 | OPTIONS 22 | -e Enter Ex mode on startup 23 | -m Disable initial file read message 24 | -s Enter raw Ex mode on startup 25 | -v Enter visual mode on startup (Default) 26 | 27 | MANPAGE NOTATION 28 | A closure where x represents character literal 29 | [x] A closure where x represents optional argument 30 | {x} A closure where x represents required argument 31 | "x" A closure where x represents a string 32 | <^X> Represents a ctrl key X 33 | # Represents a positive number in a closure 34 | * Represents any character(s) in a closure 35 | < > Separates alternatives in a closure 36 | x-y Range from x to y 37 | 38 | VI NORMAL 39 | [#]j Move # lines down 40 | [#]k Move # lines up 41 | [#]+ 42 | [#]<^M> 43 | [#] 44 | Move # lines down, cursor after indent 45 | [#]- Move # lines up, cursor after indent 46 | [#]h Move # cols left 47 | [#]l Move # cols right 48 | f{arg} Move to arg character found forward 49 | F{arg} Move to arg character found backward 50 | t{arg} Move until arg character found forward 51 | T{arg} Move until arg character found backward 52 | [#], Repeat last move backward # times 53 | [#]; Repeat last move forward # times 54 | [#]E Move to end of word # times, skip punctuation 55 | [#]e Move to end of word # times 56 | [#]B Move to start of word backward # times, skip punctuation 57 | [#]b Move to start of word backward # times 58 | [#]W Move to start of word forward # times, skip punctuation 59 | [#]w Move to start of word forward # times 60 | vw Toggle line mode for 61 | [#]{ Move to next <{> section down # times 62 | [#]} Move to next <{> section up # times 63 | [#][ Move to next section down # times 64 | [#]] Move to next section up # times 65 | ^ Move to start of line after indent 66 | 0 Move to start of line 67 | $ Move to end of line 68 | [#]| Goto # col 69 | [#] Move # characters forward 70 | [#]<^H> 71 | [#] 72 | Move # characters backward 73 | % Move to closest <] ) }><[ ( {> pair 74 | {#}% Move to # percent line number 75 | '{a-z ` ' [] *} 76 | Move to a line mark 77 | `{a-z ` ' [] *} 78 | Move to a line mark with cursor position 79 | gg Goto first line in buffer 80 | [#]G Move to last line in buffer or # line 81 | H Move to highest line on a screen 82 | L Move to lowest line on a screen 83 | M Move to middle line on a screen 84 | [#]z. Center screen at cursor. # is xtop 85 | [#]z<^M> 86 | [#]z 87 | Center screen at top row. # is xtop 88 | [#]z- Center screen at bottom row. # is xtop 89 | [#]<^E> Scroll down 1 or # lines, retain # and cursor position 90 | [#]<^Y> Scroll up 1 or # lines, retain # and cursor position 91 | [#]<^D> Scroll down half a screen size. If [#], set scroll to # 92 | lines 93 | [#]<^U> Scroll up half a screen size. If [#], set scroll to # lines 94 | <^B> Scroll up full screen size 95 | <^F> Scroll down full screen size 96 | # Show global and relative line numbers 97 | 2# Toggle show global line numbers permanently 98 | 4# Toggle show relative line numbers after indent permanently 99 | 8# Toggle show relative line numbers permanently 100 | V Toggle show hidden characters: 101 | <^C> Toggle show line motion numbers for 102 | {1-5}<^C> Switch to line motion number mode # 103 | <^V> Loop through line motion number modes 104 | [#]<^R> Redo # times 105 | [#]u Undo # times 106 | <^I> 107 | 108 | Open file path from cursor to end of line 109 | <^K> Write current buffer to file. Force write on 2nd attempt 110 | [#]<^W>{arg} Unindent arg region # times 111 | [#]<{arg} Indent left arg region # times 112 | [#]>{arg} Indent right arg region # times 113 | "{arg}{arg1} Operate on arg register according to arg1 motion 114 | R Print registers and their contents 115 | [#]&{arg} Execute arg register macro in non-blocking mode # times 116 | [#]@{arg} Execute arg register macro in blocking mode # times 117 | [#]@@ 118 | [#]&& 119 | Execute a last executed register macro # times 120 | [#]. Repeat last normal command # times 121 | [#]v. Repeat last normal command moving down across # lines 122 | : Enter ex prompt 123 | [#]!{arg} Enter pipe ex prompt based on # or arg region 124 | vv Enter ex prompt with the last line from history buffer b-1 125 | [#]vr Enter %s/ ex prompt. Insert # words from cursor 126 | [#]vt[#arg] Enter .,.+0s/ ex prompt. Insert # of lines from cursor. 127 | Insert #arg words from cursor 128 | [#]v/ Enter v/ xkwd ex prompt to set search keyword. Insert # 129 | words from cursor 130 | v; Enter ! ex prompt 131 | [#]vi Enter %s/ ex prompt. Contains regex for changing spaces to 132 | tabs. # modifies tab width 133 | [#]vI Enter %s/ ex prompt. Contains regex for changing tabs to 134 | spaces. # modifies tab width 135 | vo Remove trailing white spaces and <\r> line endings 136 | va Toggle ai ex option 137 | <^G> Print buffer status infos 138 | 1<^G> Enable permanent status bar row 139 | 2<^G> Disable permanent status bar row 140 | ga Print character info 141 | 1ga Enable permanent character info bar row 142 | 2ga Disable permanent character info bar row 143 | [#]gw Hard line wrap a line to # col limit. Default: 80 144 | [#]gq Hard line wrap a buffer to # col limit. Default: 80 145 | [#]g~{arg} Switch character case for arg region # times 146 | [#]gu{arg} Switch arg region to lowercase # times 147 | [#]gU{arg} Switch arg region to uppercase # times 148 | [#]~ Switch character case # times forward 149 | i Enter insert mode 150 | I Enter insert mode at start of line after indent 151 | A Enter insert mode at end of line 152 | a Enter insert mode 1 character forward 153 | [#]s Enter insert mode and delete # characters 154 | S Enter insert mode and delete all characters 155 | o Enter insert mode and create a new line down 156 | O Enter insert mode and create a new line up 157 | [#]c{arg} Enter insert mode and delete arg region # times 158 | C Enter insert mode and delete from cursor to end of line 159 | [#]d{arg} Delete arg region # times 160 | D Delete from cursor to end of line 161 | [#]x Delete # characters from cursor forward 162 | [#]X Delete # characters from cursor backward 163 | di{arg} Delete inside arg pairs <( ) "> 164 | ci{arg} Change inside arg pairs <( ) "> 165 | [#]r{arg} Replace # characters with arg from cursor forward 166 | K Split a line 167 | {#}K Split a line without creating blank 168 | [#]J Join # lines 169 | vj Toggle space padding when joining lines 170 | [#]y{arg} Yank arg region # times 171 | [#]Y Yank # lines 172 | [#]p Paste default register # times 173 | [#]P Paste default register below current line or behind cursor 174 | position # times 175 | m{a-z ` ' [] *} 176 | Set buffer local line mark 177 | <^T> Set global line mark 0. Global marks are always valid 178 | {0 2 4 6 8}<^T> 179 | Set a global line mark # 180 | {1 3 5 7 9}<^T> 181 | Switch to a global line mark # 182 | [#]<^7>{0-9} 183 | [#]<^_>{0-9} 184 | [#]<^/>{0-9} 185 | Show buffer list and switch based on # or 0-9 index when 186 | prompted 187 | <^^> 188 | <^6> 189 | Swap to previous buffer 190 | [#]<^N> Swap to next buffer, # changes direction [forward backward] 191 | \ Swap to /fm/ buffer b-2 192 | {#}\ Swap from /fm/ buffer b-2 and backfill directory listing 193 | vb Recurse into b-1 history buffer. Insert current line into 194 | ex prompt on exit 195 | z1 Set alternative keymap to Farsi keymap 196 | z2 Set alternative keymap to Russian keymap 197 | ze Switch to English keymap 198 | zf Switch to alternative keymap 199 | zL Set td ex option to 2 200 | zl Set td ex option to 1 201 | zr Set td ex option to -1 202 | zR Set td ex option to -2 203 | [#]/ Regex search, move down 1 or # matches 204 | [#]? Regex search, move up 1 or # matches 205 | [#]n 206 | [#]N 207 | Repeat regex search, move [down up] 1 or # matches 208 | <^A> Regex search 1 word from cursor, no center, wraparound move 209 | [up down] 210 | {#}<^A> Regex search, set keyword to # words from cursor 211 | <^]> Filesystem search forward based on directory listing in b-2 212 | {#}<^]> Filesystem search forward, set keyword to # words from 213 | cursor 214 | <^P> Filesystem search backward based on directory listing in 215 | b-2 216 | {#}<^P> Filesystem search backward, set keyword to # words from 217 | cursor 218 | <^Z> Suspend vi 219 | <^L> Force redraw whole screen and update terminal dimensions 220 | qq Exit and clean terminal, force quit in an & macro 221 | zz Exit and submit history command, force quit in an & macro 222 | ZZ Exit and soft write to a file 223 | 224 | VI REGIONS 225 | Regions are vi normal commands that define [h v]range for vi motions. 226 | Commands described with the word "move" define a region. 227 | 228 | <+ j ^M Newline - k h l f F t T , ; B E b e W w { } [ ] ^ 0 $ Space ^H 229 | Backspace % ' ` G H L M / ? n N ^A> 230 | 231 | All regions 232 | 233 | VI MOTIONS 234 | Motions are vi normal commands that run in a [h v]range. 235 | Commands described with the word "region" consume a region. 236 | Motions can be prefixed or suffixed by [#]. 237 | 238 | <^W < > ! c d y "> g~ gu gU 239 | All motions 240 | 241 | <"> Special motions that consume a motion 242 | 243 | dd yy cc g~~ guu gUU >> << <^W><^W> !! 244 | Special motions that can use [#] as number of lines 245 | 246 | Examples: 247 | 3d/int Delete text until the 3rd instance of "int" keyword 248 | 3dw Delete 3 words (prefix [#]) 249 | d3w Delete 3 words (suffix [#]) 250 | "ayl Yank a character into register 251 | "Ayw Append a word to register 252 | 253 | VI/EX INSERT 254 | <^H> 255 | 256 | Delete a character 257 | <^U> Delete util <^X> mark or everything 258 | <^W> Delete a word 259 | <^T> Increase indent 260 | <^D> Decrease indent 261 | <^]> Select paste register from 0-9 registers in a loop 262 | <^\>{arg} Select paste register arg. <^\> selects default register 263 | <^P> Paste a register 264 | <^X> Mark autocomplete and <^U> starting position. <^X> resets 265 | the mark 266 | <^G> Index current buffer for autocomplete 267 | <^Y> Reset all indexed autocomplete data 268 | <^R> Loop through autocomplete options backward 269 | <^N> Loop through autocomplete options forward 270 | <^B> Print autocomplete options when in vi insert 271 | <^B> Recurse into b-1 history buffer when in ex prompt. Insert 272 | current line into ex prompt on exit 273 | <^A> Loop through lines in a history buffer b-1 274 | <^Z> Suspend vi/ex 275 | <^L> Redraw screen in vi mode, clean terminal in ex 276 | <^O> Switch between vi and ex modes recursively 277 | <^E> Switch to english keymap 278 | <^F> Switch to alternative keymap 279 | <^V>{arg} Read a literal character arg 280 | <^K>{arg} Read a digraph sequence arg 281 | <^C> 282 | 283 | Exit insert mode 284 | 285 | EX 286 | Ex is a powerful line editor for Unix systems, initially developed 287 | by Bill Joy in 1976. This essential tool serves as the backbone 288 | of vi, enabling it to execute commands, macros and even transform 289 | into a purely command-line interface (CLI) when desired. 290 | 291 | EX PARSING 292 | Parsing follows the structure: 293 | [<:>][range][sep][cmd][][args][<:>] 294 | Ex commands are initiated and separated by <:> prefix. Fields 295 | can be separated by or . There can only be one separator 296 | in between [cmd] and [args]. To avoid ambiguity, it is recommended 297 | to always use a separator between [cmd] and [args] in scripts. 298 | 299 | Examples: 300 | :evi.c 301 | Evaluates to ":e vi.c" 302 | :eabc 303 | Evaluates to ":ea bc" not ":e abc" 304 | :e vi.c 305 | Edit " vi.c". [] is required 306 | 307 | EX ESCAPES 308 | Special characters in [args] will become regular when escaped 309 | with <\>. 310 | 311 | <( ^ [ ] \> 312 | Special characters in regex "[]" bracket exp 313 | <( ) { } + * ? ^ $ [ ] | \< \> . \> 314 | Special characters in regex 315 | <# % ! :> 316 | Special characters in ex 317 | 318 | EX EXPANSION 319 | Characters <# %> in [args] substitute the buffer pathname. 320 | <%> substitutes current buffer and <#> last swapped buffer. 321 | It is possible to expand any arbitrary buffer by using <# %> 322 | followed by the buffer number. 323 | 324 | Example: print the pathname for buffer 69. 325 | :!echo "%69" 326 | 327 | Every ex command is be able to receive data from external process 328 | through a special expansion character which pipes the data 329 | into the command itself. If the closing is not specified, 330 | the end of the line becomes a terminator. 331 | 332 | Example: substitute "int" with the value of $RANDOM 333 | :%s/int/!printf "%s" $RANDOM! 334 | Example: insert output of ls shell command 335 | :& i!ls 336 | 337 | EX RANGES 338 | Some ex commands can be prefixed with ranges. 339 | 340 | [. - +][1-9 %][sep][, ;][. - +][1-9 $] 341 | Numeric ranges 342 | {kwd}[] 343 | Search ranges 344 | <'>{mark} 345 | Mark ranges 346 | 347 | . Current position 348 | , Vertical range separator 349 | ; Horizontal range separator 350 | % Range from first to last line of the buffer 351 | $ Last line of the buffer or end of the line 352 | 353 | Examples: 354 | :1,5p Print lines 1,5 355 | :.-5,.+5p 356 | Print 5 lines around xrow 357 | :/int/p 358 | Print first occurrence of int 359 | :?int?p 360 | Print first occurrence of int in reverse 361 | :.,/int/p 362 | Print until int is found 363 | :?int?,.p 364 | Print until int is found in reverse 365 | :'d,'ap 366 | Print lines from mark to mark 367 | :%p Print all lines in the buffer 368 | :$p Print last line in the buffer 369 | :;50 Goto character offset 50 370 | :10;50 Goto line 10 character offset 50 371 | :10;.+5 372 | Goto line 10 +5 character offset 373 | :'a;'a Goto line mark offset mark 374 | :;$ Goto end of the line 375 | :5;/int/ 376 | Search for int on line 5 377 | :.;?int? 378 | Search for int in reverse on the current line 379 | 380 | EX COMMANDS 381 | [range]f{/?}[kwd] 382 | Ranged search (stands for find) 383 | 384 | Example: no range given, current line only 385 | :f/int 386 | Example: reverse 387 | :f?int 388 | Example: range given 389 | :10,100f/int 390 | Subsequent commands within the range will move to the next match 391 | just like vi n/N commands. 392 | 393 | b[#] Print currently active buffers state or switch to a buffer 394 | 395 | Example: switch to the 5th buffer 396 | :b5 397 | 398 | There are two temporary buffers which are separate from 399 | the main buffers. 400 | b-1 = /hist/ ex history buffer 401 | b-2 = /fm/ directory listing buffer 402 | Example: switch to the b-1 buffer 403 | :b-1 404 | Example: switch to the b-2 buffer 405 | :b-2 406 | 407 | bp[path] 408 | Set current buffer path 409 | bs[#] Set current buffer saved. If any arg given, reset undo/redo 410 | history 411 | 412 | [range]p 413 | Print line(s) from the buffer 414 | 415 | Example: utilize character offset ranges 416 | :1,10;5;5p 417 | Example: print current line from offset 5 to 10 418 | :.;5;10p 419 | 420 | ea[kwd] [#] 421 | Open file based on filename substring from dir listing in b-2 422 | 423 | Requires directory listing in b-2 backfilled prior. 424 | Example: backfill b-2 using :fd 425 | :fd 426 | Example: backfill b-2 using find 427 | :b-2:1,$!find . 428 | 429 | If the substring matches more than one filename, a prompt will 430 | be shown. Submit using numbers 0-9 or higher ascii values. 431 | <^C> to cancel, to select first match. Passing an 432 | extra arg to :ea in form of a number will bypass the prompt 433 | and open the corresponding file. 434 | Example: open filename containing "v" 435 | :ea v 436 | Example: open first match containing "v" 437 | :ea v 0 438 | 439 | ea![kwd] [#] 440 | Forced version of ea 441 | 442 | [#]a[str] 443 | [#]i[str] 444 | [#]c[str] 445 | Enter ex {append insert change} mode 446 | 447 | # determines insertion line number. 448 | str determines initial input into the insertion buffer. 449 | 450 | Example: insert "hello" in vi/ex 451 | :i hello<^M> 452 | Example: discard changes in vi/ex 453 | :i hello<^C> 454 | Example: immediately insert "hello" 455 | :i hello<^V><^M><^V> 456 | Example: insert "hello" in raw ex mode 457 | i hello<^M>.<^M> 458 | 459 | [range]d 460 | Delete line(s) 461 | e[path] 462 | Open a file at a path 463 | 464 | No argument opens "unnamed" buffer. 465 | e![path] 466 | Force open a file at a path 467 | 468 | No argument re-reads the current buffer from the filesystem. 469 | 470 | [range]g{*}[kwd]{*}{cmd} 471 | Global command 472 | 473 | Execute an ex command on a range of lines that matches an 474 | enclosed regex. 475 | 476 | Example: remove all empty lines 477 | :g/^$/d 478 | 479 | Multiple ex commands can be chained in one global command. 480 | To chain commands, the ex separator <:> must be escaped once. 481 | Example: yank matches appending to reg 'a' and print them out. 482 | :g/int/ya A\:p 483 | 484 | It is possible to nest global commands inside of global commands. 485 | A global command will not be executed on lines that were created 486 | or deleted by a nested global command. 487 | When range not given, a nested global command is executed on 488 | the current line. 489 | 490 | Example: nested global command 491 | Append "has a semicolon" to all lines that contain "int" and 492 | end with <;>. 493 | :g/int/g/;$/& A has a semicolon 494 | 495 | Example: extract/print data enclosed in "()" 496 | :g/\(.+\)/;0;/\(.+\)/\:.;.+1k a\:se grp=2\:;/\)*(\))/\: 497 | se nogrp\:k s\:.;'a;'sp 498 | 499 | [range]g!{*}[kwd]{*}{cmd} 500 | Inverted global command 501 | [range]= 502 | Print the current range line number 503 | 504 | [range]k[mark] 505 | Set a line mark 506 | 507 | The character offset is set to the current position. 508 | 509 | &{macro} 510 | Global non-blocking macro 511 | 512 | Execute raw vi/ex input sequence. 513 | A non-blocking macro shall not wait for input when the end of 514 | the sequence is reached. A non-blocking macro executing other 515 | macros will always reach a terminating point. 516 | Example: execute vi insert statement 517 | :& ihello 518 | Example: execute :hello 519 | :& \:hello<^V><^M> 520 | Example: execute vi ci(int macro 521 | :& ci(int 522 | Example: nest blocking macro inside non-blocking 523 | :& \:@ \\:blocking<^V><^M>i continue in non-blocking 524 | 525 | @{macro} 526 | Global blocking macro 527 | 528 | Execute raw vi/ex input sequence. 529 | A blocking macro shall wait for input when the end of the sequence 530 | is reached. A blocking macro executing other macros may result 531 | in congestion. 532 | Example: execute vi insert statement 533 | :@ ihello 534 | Example: insert "hello" into <:> vi prompt 535 | :@ \:hello 536 | Example: execute vi ci(int macro 537 | :@ ci(int 538 | Example: execute ci(int exiting insert mode 539 | :@ ci(int<^V><^C> 540 | Example: execute ci)INT as a follow-up 541 | :@ ci(int<^V><^C>ci)INT 542 | Example: execute vi dw command after user exits insert 543 | :@i:@dw 544 | 545 | pu[register] [cmd] 546 | Paste a register 547 | 548 | To pipe register data to an external process use :pu \![cmd] 549 | Example: copy default register to X11 clipboard 550 | :pu \!xclip -selection clipboard 551 | 552 | [range]r[path cmd] 553 | Read a file or a pipe 554 | 555 | To read data from a pipe use :[range]r \![cmd] 556 | Example: pipe in only the first line 557 | :r \!ls 558 | Example: pipe in only lines 3,5 559 | :3,5r \!ls 560 | Example: pipe in all data 561 | :%r \!ls 562 | 563 | [range]w[path cmd] 564 | Write to a file or a pipe 565 | 566 | To pipe buffer data to external process use :[range]w \![cmd] 567 | Example: pipe out all data into less 568 | :w \!less 569 | Example: pipe out only first 10 lines 570 | :1,10w \!less 571 | 572 | [range]w![path] 573 | Force write to a file 574 | 575 | q Exit 576 | q! Force quit 577 | 578 | wq[!] 579 | x[!] 580 | Soft write and exit or force quit 581 | 582 | u[# $] Undo # times or all with $ 583 | rd[# $] 584 | Redo # times or all with $ 585 | 586 | se{exp} 587 | Set ex option variable 588 | 589 | Example: set using implications 590 | :se hll 591 | :se nohll 592 | Example: set using numeric values 593 | :se hll=1 594 | :se hll=0 595 | Example: set using ascii values 596 | :se hll=a 597 | 598 | [range]s{*}[kwd]{*}{str}[*][opts] 599 | Substitute 600 | 601 | Find and replace text in a range of lines that matches an 602 | enclosed regex with an enclosed replacement string. 603 | 604 | Example: global replacement 605 | :%s/term1/term2/g 606 | 607 | Substitution backreference inserts the text of matched group 608 | specified by \x where x is group number. 609 | 610 | Example: substitution backreference 611 | this is an example text for subs and has int or void 612 | :%s/(int)|(void)/pre\0after 613 | this is an example text for subs and has preintafter or void 614 | :%s/(int)|(void)/pre\2after/g 615 | this is an example text for subs and has prepreafterafter or prevoidafter 616 | 617 | [range]ya[register][append] 618 | Yank a region 619 | 620 | To append to the register, pass in its uppercase version. 621 | To append to any of the non-alphabetical registers add any extra 622 | character to the command. 623 | Example: append to register <1> 624 | :ya 1x 625 | 626 | ya![register] 627 | Reset register value 628 | 629 | [range]![cmd] 630 | Run external program 631 | 632 | When ex range specified, pipes the buffer data to an external 633 | process and pipes the output back into current buffer replacing 634 | the affected range. 635 | Example: infamously sort the buffer 636 | :1,$!sort 637 | 638 | ft[filetype] 639 | Set a filetype 640 | 641 | No argument prints the current file type. 642 | Reloads the highlight ft, which makes it possible to reset dynamic 643 | highlights created by options like "hlw". 644 | 645 | cm[keymap] 646 | Set a keymap 647 | 648 | No argument prints the current keymap name. 649 | 650 | cm![keymap] 651 | Set an alternative keymap 652 | 653 | fd[path] 654 | Set a secondary directory (stands for file dir) 655 | 656 | Recalculates the directory listing in b-2 buffer. 657 | No argument implies current directory. 658 | 659 | fp[path] 660 | Set a directory path for :fd (stands for file path) 661 | 662 | cd[path] 663 | Set a working directory (stands for change dir) 664 | 665 | Currently open buffers' file paths will be automatically adjusted 666 | to reflect a newly set working directory. 667 | 668 | inc[regex] 669 | Include regex for :fd calculation 670 | 671 | Example: include only files in submodule directory that end with .c 672 | :inc submodule.*\.c$ 673 | Example: exclude the .git and submodule folders 674 | :inc (^(?\:(?\!^\.git|^submodule).)+[^/]+$) 675 | No argument disables the filter. 676 | 677 | reg[hscroll] 678 | Print registers and their contents 679 | 680 | Printing position is determined by xcols / 2 * [hscroll] 681 | 682 | bx[#] Set max number of buffers allowed 683 | 684 | Buffers will be deallocated if the number specified is lower 685 | than the number of buffers currently in use. 686 | No argument will reset to the default value of 10. 687 | 688 | ac[regex] 689 | Set autocomplete filter regex 690 | 691 | Example: autocomplete using whole lines from the buffer 692 | :ac .+ 693 | No argument resets to the default word filter regex as defined 694 | in led.c. 695 | 696 | uc Toggle multibyte utf-8 decoding 697 | 698 | This command is particularly useful when editing files with 699 | mixed encodings, binary files, or when the terminal does not 700 | support UTF-8 or lacks the necessary fonts to display UTF-8 701 | characters. It's often paired with :ph command to achieve 702 | hex editor-like functionality. 703 | 704 | uz Toggle zero width placeholders 705 | 706 | Use only if you need to hide zero width characters. 707 | 708 | ub Toggle combining multicodepoint placeholders 709 | 710 | Use only if your terminal can render multicodepoint utf-8 (emojis). 711 | 712 | ph[#clow] [#chigh] [#width] [#blen][*char] 713 | Redefine placeholders 714 | 715 | This command replaces placeholders defined in conf.c 716 | and subsequent :ph commands expand the list of placeholders. 717 | Example: render 8 bit ascii (Extended ASCII) as <~> 718 | :ph 128 255 1 1~ 719 | Example: flawless ISO/IEC 8859-1 (latin-1) support 720 | :uc:ph 128 160 1 1~ 721 | Example: render control byte 03 as "^C" 722 | :ph 3 3 2 1^C 723 | Example: reset to default as in conf.c 724 | :ph 725 | Example: disable default placeholders in conf.c 726 | :ph:ph0 727 | 728 | EX OPTIONS 729 | ai=1 Indent new lines 730 | ic=1 Ignore case in regular expressions 731 | 732 | ish=0 Interactive shell 733 | 734 | Run every command through an interactive shell. 735 | The shell will source the .rc file before command execution. 736 | This makes it possible to use predefined functions, aliases 737 | and ENV variables from the .rc file. 738 | Precondition 1: 739 | The .rc filename is shell specific, such as .bashrc in Bash 740 | and .zshrc in Zsh. 741 | Precondition 2: 742 | The environment variable $SHELL determines the default 743 | shell, otherwise it defaults to /bin/sh. 744 | Precondition 3: 745 | There must be no stdout output created by .rc file 746 | for commands to return expected results. 747 | 748 | grp=0 Regex search group 749 | 750 | Defines a target search group for any regex search operation. 751 | This becomes necessary when the result of regex search is to 752 | be based on some group rather than default match group. 753 | 754 | Example: ignore tabs at the start of the line 755 | :se grp=2:1,$f/^[ ]+(.+):se nogrp 756 | 757 | The value of grp is calculated using (group number * 2). 758 | 759 | hl=1 Highlight text based on rules defined in conf.c 760 | hlr=0 Highlight text in reverse direction 761 | hll=0 Highlight current line based on filetype hl 762 | hlp=0 Highlight "[]" "()" "{}" pairs based on filetype hl 763 | hlw=0 Highlight current word based on filetype hl 764 | led=1 Enable all terminal output 765 | 766 | vis=0 Control startup flags 767 | 768 | Example: disable :e message in ex mode 769 | :se vis=12 770 | Example: disable :e message in vi mode 771 | :se vis=8 772 | Example: enable raw ex mode 773 | :se vis=6 774 | Example: disable raw ex mode 775 | :se vis=4 776 | 777 | mpt=0 Control vi prompts 778 | 779 | When set to 0 after an ex command is called from vi, disables 780 | the "[any key to continue]" prompt. 781 | If mpt is negative, the prompt will remain disabled. 782 | 783 | order=1 784 | Reorder characters based on rules defined in conf.c 785 | shape=1 786 | Perform Arabic script letter shaping 787 | pac=0 Print autocomplete suggestions on the fly 788 | tbs=8 Number of spaces used to represent a tab 789 | td=1 Current text direction context 790 | 791 | This option accepts four meaningful values: 792 | +2 Exclusively left-to-right 793 | +1 Follow dircontexts[] (in conf.c), defaulting to left-to- 794 | right 795 | -1 Follow dircontexts[], defaulting to right-to-left 796 | -2 Exclusively right-to-left 797 | 798 | pr=0 Print register 799 | 800 | Set a special register using a character or a number. 801 | Once the register is set, all data passed into ex_print will 802 | be stored in the register. 803 | If the register is uppercase, characters are added 804 | to match the exact output that was printed. 805 | Example: paste current buffer list exactly like from :b command 806 | :se pr=A:ya! a:b:pu a 807 | Example: store a line printed with :p 808 | :se pr=A:ya! a:p 809 | 810 | sep=: Ex separator 811 | 812 | Set Ex command separator character. 813 | Changing default <:> separator will break some built-in commands/macros. 814 | Example: set separator to <|> 815 | :se sep=| 816 | Example: disable separator 817 | :se nosep 818 | 819 | EXINIT ENV VAR 820 | EXINIT defines a sequence of vi/ex commands to be performed 821 | at startup. Consequently, this is the primary way for scripting 822 | and customizing Nextvi outside of C. 823 | Many standard text processing utils such as grep, awk, sed 824 | can be replaced by Nextvi with EXINIT in mind. 825 | 826 | Examples: 827 | 828 | export EXINIT=$'e ./vi.c:& i\x7\x3:bx 1:bx' 829 | Index vi.c for autocomplete 830 | 831 | export EXINIT='b-1:%r ./vi.c:b-1' 832 | Load vi.c into a history buffer 833 | 834 | export EXINIT=$'e:& io{\n}\x16\x3kA\x3:& 1G:& 2\"ayy' 835 | Setup @ macro in register 836 | 837 | @a macro creates <{> and closing <}> below the cursor leaving 838 | cursor in insert mode in between the braces. 839 | 840 | REGEX 841 | Pikevm is a fast non backtracking NFA simulation regex engine 842 | developed for Nextvi. It ensures regular expressions are evaluated 843 | in constant space and O(n + k) time complexity where is 844 | the input string length and represents the regex's structural 845 | complexity (e.g., state transitions or alternations). While 846 | this does not guarantee strict O(n) linear runtime performance, 847 | it ensures computational and memory resources are distributed 848 | linearly and evenly across the input, with directly influencing 849 | the constant factor. This principle is similar to the concept 850 | utilized in radix sort algorithms. 851 | 852 | Pikevm's syntax is akin to regexp(7) from Plan 9. 853 | Disregard manpage notation for <{ } [ ]> in this section. 854 | 855 | . Match any single char 856 | ^ Assert start of the line 857 | $ Assert end of the line 858 | {N,M} Match N to M times 859 | () Grouping 860 | (?:) Non capture grouping 861 | [N-M] Match a set of alternate ranges N to M 862 | * Repeated zero or more times 863 | + Repeated one or more times 864 | | Union, alternative branch 865 | \< Assert start of the word 866 | \> Assert end of the word 867 | ? One or zero matches greedy 868 | ?? One or zero matches lazy 869 | (?=) Positive lookahead 870 | (?!) Negative lookahead 871 | (?<) Positive lookbehind 872 | (?>) Negative lookbehind 873 | 874 | Lookaround expressions enable the creation of regular expressions 875 | that would be impossible to construct without them. They can 876 | be used anywhere within a regex, though some considerations 877 | must be taken into account. 878 | Aspect 1: 879 | Lookarounds contain a full regex, but should use non-capturing 880 | groups to avoid performance issues. 881 | Aspect 2: 882 | Lookarounds can have nested lookarounds. 883 | Aspect 3: 884 | Static lookaheads like (?=^word) are optimized, bypassing 885 | compilation. 886 | Aspect 4: 887 | Using lookbehind assertions at the beginning of a regex can 888 | dramatically reduce performance. This is because the regex engine 889 | must scan the entire input to find valid starting positions 890 | for the assertion. Lookbehinds are best suited for validating 891 | context near the end of a complex pattern, where the engine 892 | has already narrowed down potential matches. 893 | 894 | SPECIAL MARKS 895 | * Position of the previous change 896 | [ First line of the previous change 897 | ] Last line of the previous change 898 | ' Position of the previous line region 899 | ` Position of the previous line region 900 | 901 | SPECIAL REGISTERS 902 | / Previous search keyword 903 | : Previous ex command 904 | 0 Previous value of default register (atomic) 905 | Atomic means the operation did not include a . 906 | 1-9 Previous value(s) of default register (nonatomic) 907 | 908 | CODE MAP 909 | +--------------+----------------------+ 910 | | 508 vi.h | definitions/aux | 911 | | 537 kmap.h | keymap translation | 912 | +--------------+----------------------+ 913 | | 295 conf.c | hl/ft/td config | 914 | | 342 term.c | low level IO | 915 | | 380 ren.c | positioning/syntax | 916 | | 521 lbuf.c | file/line buffer | 917 | | 650 uc.c | UTF-8 support | 918 | | 675 led.c | insert mode/output | 919 | | 715 regex.c | extended RE | 920 | | 1246 ex.c | ex options/commands | 921 | | 1938 vi.c | normal mode/general | 922 | | 6762 total | wc -l *.c|sort | 923 | +--------------+----------------------+ 924 | 925 | COMPILING 926 | export CC='g++ -x c' 927 | Set compiler, g++ example 928 | export CFLAGS='-s' 929 | Set CFLAGS, strip example 930 | ./cbuild.sh 931 | Build once 932 | ./cbuild.sh build 933 | Build 934 | ./cbuild.sh debug 935 | Build with -O0 -g 936 | ./cbuild.sh pgobuild 937 | PGO build can lead to a significant performance boost on some 938 | application specific tasks 939 | ./cbuild.sh install 940 | Install vi to $DESTDIR$PREFIX/bin 941 | ./cbuild.sh fetch 942 | Merge commits from upstream repository 943 | ./cbuild.sh bench 944 | Performance bench test 2000 word deletions on vi.c 945 | 946 | PHILOSOPHY 947 | In most text editors, flexibility is a minor or irrelevant design 948 | goal. Nextvi is designed to be flexible where the editor adapts 949 | to the user needs. This flexibility is achieved by heavily chaining 950 | basic commands and allowing them to create new ones with completely 951 | different functionality. Command reuse keeps the editor small 952 | without infringing on your freedom to quickly get a good grasp 953 | on the code. If you want to customize anything, you should be 954 | able to do it using only core commands or a mix with some specific 955 | C code for more difficult tasks. Simple and flexible design 956 | allows for straight forward solutions to any problem long term 957 | and filters bad inconsistent ideas. 958 | 959 | "All software sucks, but some do more than others." 960 | - Kyryl Melekhin 961 | 962 | SEE ALSO 963 | New functionality can be obtained through optional patches provided 964 | in the patches branch. If you have a meaningful contribution 965 | and would love to be made public the patch can be submitted 966 | via email or github pull request. 967 | https://github.com/kyx0r/nextvi/tree/patches 968 | 969 | Scripts used to generate this manual are located in the manual 970 | branch. 971 | https://github.com/kyx0r/nextvi/tree/manual 972 | 973 | Original Neatvi repository: 974 | https://github.com/aligrudi/neatvi 975 | 976 | Posix vi(1) 977 | https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html 978 | 979 | Posix ex(1) 980 | https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ex.html 981 | 982 | NFA regular expressions by Russ Cox: 983 | https://swtch.com/~rsc/regexp/regexp1.html 984 | 985 | Plan 9 regexp(7) 986 | https://man.cat-v.org/p9p/7/regexp 987 | 988 | AUTHORS 989 | Nextvi was written by Kyryl Melekhin . It is based 990 | on neatvi(1), which was written by Ali Gholami Rudi . This 991 | manual page was inspired by nepeta 992 | 993 | Linux 6.13.7 May 27, 2025 Linux 6.13.7 994 | -------------------------------------------------------------------------------- /cbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | POSIXLY_CORRECT=1 4 | cbuild_OPWD="$PWD" 5 | BASE="${0%/*}" && [ "$BASE" = "$0" ] && BASE="." # -> BASE="$(realpath "$0")" && BASE="${BASE%/*}" 6 | cd "$BASE" || log "$R" "Unable to change directory to ${BASE##*/}. Re-execute using a POSIX shell and check again." 7 | BASE="${PWD%/}" 8 | trap 'cd "$cbuild_OPWD"' EXIT 9 | 10 | # Color escape sequences 11 | G="\033[32m" # Green 12 | R="\033[31m" # Red 13 | B="\033[34m" # Blue 14 | NC="\033[m" # Unset 15 | 16 | log() { 17 | # shellcheck disable=SC2059 # Using %s with ANSII escape sequences is not possible 18 | printf "${1}->$NC " 19 | shift 20 | printf "%s\n" "$*" 21 | } 22 | 23 | require() { 24 | set -- $1 25 | command -v "$1" >/dev/null 2>&1 || { 26 | log "$R" "[$1] is not installed. Please ensure the command is available [$1] and try again." 27 | exit 1 28 | } 29 | } 30 | 31 | run() { 32 | log "$B" "$*" 33 | # shellcheck disable=SC2068 # We want to split elements, but avoid whitespace problems (`$*`), and also avoid `eval $*` 34 | $@ 35 | } 36 | 37 | CFLAGS="\ 38 | -pedantic -Wall -Wextra \ 39 | -Wno-implicit-fallthrough \ 40 | -Wno-missing-field-initializers \ 41 | -Wno-unused-parameter \ 42 | -Wno-unused-result \ 43 | -Wfatal-errors -std=c99 \ 44 | $CFLAGS" 45 | 46 | : "${CC:=cc}" 47 | : "${STRIP:=strip}" 48 | : "${PREFIX:=/usr/local}" 49 | : "${OS:=$(uname)}" 50 | case "$OS" in 51 | *_NT*) CFLAGS="$CFLAGS -D_POSIX_C_SOURCE=200809L" ;; 52 | *Darwin*) CFLAGS="$CFLAGS -D_POSIX_C_SOURCE=200809L -D_DARWIN_C_SOURCE" ;; 53 | *Linux*) CFLAGS="$CFLAGS -D_POSIX_C_SOURCE=200809L" ;; 54 | *) CFLAGS="$CFLAGS -D_DEFAULT_SOURCE" ;; 55 | esac 56 | 57 | : "${OPTFLAGS:=-O2}" 58 | build() { 59 | require "${CC}" 60 | log "$G" "Entering step: \"Build \"${BASE##*/}\" using \"$CC\"\"" 61 | run "$CC vi.c -o vi $OPTFLAGS $CFLAGS" || { 62 | log "$R" "Failed during step: \"Build \"${BASE##*/}\" using \"$CC\"" 63 | exit 1 64 | } 65 | } 66 | 67 | install() { 68 | run rm "$DESTDIR$PREFIX/bin/vi" 2> /dev/null 69 | command -v "$STRIP" >/dev/null 2>&1 && run "$STRIP" vi 70 | run mkdir -p "$DESTDIR$PREFIX/bin/" && 71 | run cp -f vi "$DESTDIR$PREFIX/bin/vi" && 72 | [ -x "$DESTDIR$PREFIX/bin/vi" ] && log "$G" "\"${BASE##*/}\" has been installed to $DESTDIR$PREFIX/bin/vi" || log "$R" "Couldn't finish installation" 73 | } 74 | 75 | print_usage() { 76 | echo "Usage: $0 {install|pgobuild|build|debug|fetch|clean|bench}" 77 | exit "$1" 78 | } 79 | 80 | # Argument processing 81 | while [ $# -gt 0 ] || [ "$1" = "" ]; do 82 | case "$1" in 83 | "install") 84 | shift 85 | [ -x ./vi ] && install && exit 0 || build && install && exit 0 86 | ;; 87 | "debug") 88 | shift 89 | log "$G" "Entering step: \"Override \"\$OPTFLAGS\" with debugging flags\"" 90 | OPTFLAGS="-O0 -g" 91 | set -- build "$@" 92 | ;; 93 | "" | "build") 94 | # If the user doesn't use "build" explicitly, do not run the build step again. 95 | if [ "$1" = "build" ]; then 96 | explicit="1" 97 | else 98 | [ -n "$1" ] && shift 99 | fi 100 | if [ "$explicit" != "1" ]; then 101 | if [ -f ./vi ] || [ -f ./nextvi ]; then 102 | log "$R" "Nothing to do; \"${BASE##*/}\" was already compiled" 103 | print_usage 0 104 | fi 105 | fi 106 | # Start build process 107 | build && exit 0 || exit 1 108 | ;; 109 | "pgobuild") 110 | shift 111 | pgobuild() { 112 | ccversion="$($CC --version)" 113 | case "$ccversion" in *clang*) clang=1 ;; esac 114 | if [ "$clang" = 1 ] && [ -z "$PROFDATA" ]; then 115 | if command -v llvm-profdata >/dev/null 2>&1; then 116 | PROFDATA=llvm-profdata 117 | elif xcrun -f llvm-profdata >/dev/null 2>&1; then 118 | PROFDATA="xcrun llvm-profdata" 119 | fi 120 | [ -z "$PROFDATA" ] && log "R" "pgobuild with clang requires llvm-profdata" && exit 1 121 | fi 122 | run "$CC vi.c -fprofile-generate=. -o vi -O2 $CFLAGS" 123 | echo "qq" | ./vi -v ./vi.c >/dev/null 124 | [ "$clang" = 1 ] && run "$PROFDATA" merge ./*.profraw -o default.profdata 125 | run "$CC vi.c -fprofile-use=. -o vi -O2 $CFLAGS" 126 | rm -f ./*.gcda ./*.profraw ./default.profdata 127 | } 128 | require "${CC}" 129 | log "$G" "Entering step: \"Build \"${BASE##*/}\" using \"$CC\" and PGO\"" 130 | pgobuild || { 131 | log "$R" "Failed during step: \"Build \"${BASE##*/}\" using \"$CC\" and PGO\"" 132 | exit 1 133 | } && exit 0 || exit 1 134 | ;; 135 | "clean") 136 | shift 137 | run rm -f ./vi ./nextvi 2>/dev/null 138 | exit 0 139 | ;; 140 | "retrieve") 141 | shift 142 | if [ -x ./vi ]; then 143 | [ ! -e ./nextvi ] && mv ./vi ./nextvi 144 | else 145 | log "$R" "\"${BASE##*/}\" was never compiled OR it was but its binaries weren't found anyways." ; exit 1 146 | fi 147 | readlink -f ./nextvi && exit 0 148 | ;; 149 | "fetch") 150 | shift 151 | ! git diff --quiet HEAD && { 152 | log "$R" "Please stash changes before fetching." 153 | exit 1 154 | } 155 | git switch -c upstream-temp 156 | git pull https://github.com/kyx0r/nextvi 157 | git switch master 158 | git rebase --rebase-merges upstream-temp 159 | git branch -D upstream-temp 160 | log "$G" "Successfully fetched from upstream." 161 | ;; 162 | "bench") 163 | shift 164 | export EXINIT="${EXINIT}:&dw1999.qq" 165 | valgrind --tool=callgrind ./vi vi.c 166 | valgrind --tool=cachegrind --cache-sim=yes --branch-sim=yes ./vi vi.c 167 | exit 0 168 | ;; 169 | *) 170 | print_usage 1 171 | ;; 172 | esac 173 | done 174 | -------------------------------------------------------------------------------- /conf.c: -------------------------------------------------------------------------------- 1 | /* nextvi configuration file */ 2 | #include "kmap.h" 3 | 4 | /* access mode of new files */ 5 | int conf_mode = 0600; 6 | 7 | struct filetype fts[] = { 8 | {"c", "\\.(c|h|cpp|hpp|cc|cs)$"}, /* C */ 9 | {"roff", "\\.(ms|tr|roff|tmac|txt|[1-9])$"}, /* troff */ 10 | {"tex", "\\.tex$"}, /* tex */ 11 | {"msg", "letter$|mbox$|mail$"}, /* email */ 12 | {"mk", "[Mm]akefile$|\\.mk$"}, /* makefile */ 13 | {"sh", "\\.(ba|z)?sh$|(ba|z|k)shrc$|profile$"}, /* shell script */ 14 | {"py", "\\.py$"}, /* python */ 15 | {"nm", "\\.nm$"}, /* neatmail */ 16 | {"js", "\\.js$"}, /* javascript */ 17 | {"html", "\\.(html?|css)$"}, /* html,css */ 18 | {"diff", "\\.(patch|diff)$"} /* diff */ 19 | }; 20 | int ftslen = LEN(fts); 21 | 22 | /* 23 | colors 0-15 24 | 0 = black | inverse 25 | 1 = red3 26 | 2 = green3 27 | 3 = yellow3 28 | 4 = blue2 29 | 5 = magenta3 30 | 6 = cyan3 31 | 7 = gray90 32 | bright colors 33 | 8 = gray50 34 | 9 = red 35 | 10 = green 36 | 11 = yellow 37 | 12 = blue 38 | 13 = magenta 39 | 14 = cyan 40 | 15 = white 41 | */ 42 | 43 | struct highlight hls[] = { 44 | /* lbuf lines are *always "\n\0" terminated, for $ to work one needs to account for '\n' too */ 45 | /* "/" is default hl, must have at least 1 entry for fallback */ 46 | {"/", NULL, {14 | SYN_BD}, {1}, 0, 2}, /* <-- optional, used by hll if set */ 47 | {"/", NULL, {9 | SYN_BGMK(10)}, {0}, 0, 3}, /* <-- optional, used by hlp if set */ 48 | {"/", NULL, {9}, {0}, 0, 1}, /* <-- optional, used by hlw if set */ 49 | 50 | {"c", NULL, {14 | SYN_BD}, {1}, 0, 2}, 51 | {"c", "^.+\\\\\n$", {14}, {1}}, 52 | {"c", "(/\\*(?:(?!^\\*/).)*)|((?:(?!^/\\*).)*\\*/(?>\".*\\*/.*\"))", 53 | {4 | SYN_IT}, {0}, 2}, 54 | {"c", NULL, {9 | SYN_BGMK(12)}, {0}, 0, 3}, 55 | {"c", NULL, {9}, {0}, 0, 1}, 56 | {"c", "\\<(?:signed|unsigned|char|short|u?int(?:64_t|32_t|16_t|8_t)?|\ 57 | long|f(?:loat|64|32)|double|void|enum|union|typedef|static|extern|register|struct|\ 58 | s(?:64|32|16|8)|u(?:64|32|16|8)|b32|bool|const|size_t|inline|restrict|\ 59 | (true|false|_?_?asm_?_?|mem(?:set|cpy|cmp)|malloc|free|realloc|NULL|std(?:in|\ 60 | out|err)|errno)|(return|for|while|if|else|do|sizeof|goto|switch|case|\ 61 | default|break|continue))\\>", {10, 12 | SYN_BD, 11}}, 62 | {"c", "(\\?).+?(:)", {0, 3, 3}, {1, 0, -1}}, 63 | {"c", "//.*", {4 | SYN_IT}}, 64 | {"c", "#[ \t]*([a-zA-Z0-9_]+([ \t]*<.*>)?)", {6, 6, 5}}, 65 | {"c", "([a-zA-Z0-9_]+)\\(", {0, SYN_BD}}, 66 | {"c", "\"\"|\"(?:.*?(?:\\\\\\\\|[^\\\\])\")?", {5}}, 67 | {"c", "'(?:[^\\\\]|\\\\.|\\\\x[0-9a-fA-F]{1,2}|\\\\[0-9]+?)'", {5}}, 68 | {"c", "[-+.]?\\<(?:0[xX][0-9a-fA-FUL]+|[0-9]+\\.?[0-9eEfFuULl]+|[0-9]+)\\>", {9}}, 69 | 70 | {"roff", NULL, {14 | SYN_BD}, {1}, 0, 2}, 71 | {"roff", NULL, {9}, {0}, 0, 1}, 72 | {"roff", "^[.'][ \t]*(([sS][hH].*)|(de) (.*)|([^ \t\\\\]{2,}))?.*", 73 | {4, 0, 5 | SYN_BD, 4 | SYN_BD, 5 | SYN_BD, 4 | SYN_BD}, {1}}, 74 | {"roff", "\\\\\".*", {2 | SYN_IT}}, 75 | {"roff", "\\\\{1,2}[*$fgkmns]([^[\\(]|\\(..|\\[[^\\]]*\\])", {3}}, 76 | {"roff", "\\\\([^[(*$fgkmns]|\\(..|\\[[^\\]]*\\])", {3}}, 77 | {"roff", "\\$[^$]+\\$", {3}}, 78 | 79 | {"tex", NULL, {14 | SYN_BD}, {1}, 0, 2}, 80 | {"tex", NULL, {9}, {0}, 0, 1}, 81 | {"tex", "\\\\[^[{ \t]+(\\[([^\\]]+)\\])?(\\{([^}]*)\\})?", 82 | {4 | SYN_BD, 0, 3, 0, 5}}, 83 | {"tex", "\\$[^$]+\\$", {3}}, 84 | {"tex", "%.*", {2 | SYN_IT}}, 85 | 86 | /* mail */ 87 | {"msg", NULL, {14 | SYN_BD}, {1}, 0, 2}, 88 | {"msg", NULL, {9}, {0}, 0, 1}, 89 | {"msg", "^From .*20..\n$", {6 | SYN_BD}}, 90 | {"msg", "^Subject: (.*)", {6 | SYN_BD, 4 | SYN_BD}}, 91 | {"msg", "^From: (.*)", {6 | SYN_BD, 2 | SYN_BD}}, 92 | {"msg", "^To: (.*)", {6 | SYN_BD, 5 | SYN_BD}}, 93 | {"msg", "^Cc: (.*)", {6 | SYN_BD, 5 | SYN_BD}}, 94 | {"msg", "^[-A-Za-z]+: .+", {6 | SYN_BD}}, 95 | {"msg", "^> .*", {2 | SYN_IT}}, 96 | 97 | /* makefile */ 98 | {"mk", NULL, {14 | SYN_BD}, {1}, 0, 2}, 99 | {"mk", NULL, {9}, {0}, 0, 1}, 100 | {"mk", "([A-Za-z0-9_]*)[ \t]*:?=", {0, 3}}, 101 | {"mk", "\\$[\\({][a-zA-Z0-9_]+[\\)}]|\\$\\$", {3}}, 102 | {"mk", "#.*", {2 | SYN_IT}}, 103 | {"mk", "([A-Za-z_%.\\-]+):", {0, SYN_BD}}, 104 | 105 | /* shell script */ 106 | {"sh", NULL, {14 | SYN_BD}, {1}, 0, 2}, 107 | {"sh", NULL, {9}, {0}, 0, 1}, 108 | {"sh", "\\<(?:break|case|continue|do|done|elif|else|esac|fi|for|if|in|then|until|while)\\>", 109 | {5 | SYN_BD}}, 110 | {"sh", "[ \t](#.*)|^(#.*)", {0, 2 | SYN_IT, 2 | SYN_IT}}, 111 | {"sh", "\"(?:[^\"\\\\]|\\\\.)*\"", {4}}, 112 | {"sh", "`(?:[^`\\\\]|\\\\.)*`", {4}}, 113 | {"sh", "'[^']*'", {4}}, 114 | {"sh", "\\$(?:\\{[^}]+\\}|[a-zA-Z_0-9]+|[!#$?*@-])", {1}}, 115 | {"sh", "^([a-zA-Z_0-9]* *\\(\\)) *\\{", {0, SYN_BD}}, 116 | {"sh", "^\\. .*", {SYN_BD}}, 117 | 118 | /* python */ 119 | {"py", NULL, {14 | SYN_BD}, {1}, 0, 2}, 120 | {"py", NULL, {9}, {0}, 0, 1}, 121 | {"py", "#.*", {2}}, 122 | {"py", "\\<(?:and|break|class|continue|def|del|elif|else|except|finally|\ 123 | for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|try|while)\\>", {5}}, 124 | {"py", "([a-zA-Z0-9_]+)\\(", {0, SYN_BD}}, 125 | {"py", "\"{3}.*?\"{3}", {6}}, 126 | {"py", "((?:(?:(?!^\"\"\").)*\"{3}\n$)|(?:\"{3}(?:(?!^\"\"\").)*)|\"{3})", 127 | {6}, {0}, -1}, 128 | {"py", "[\"](\\\\\"|[^\"])*?[\"]", {4}}, 129 | {"py", "['](\\\\'|[^'])*?[']", {4}}, 130 | 131 | /* neatmail */ 132 | {"nm", NULL, {14 | SYN_BD}, {1}, 0, 2}, 133 | {"nm", "^([ROU])([0-9]+)\t([^\t]*)\t([^\t]*)", 134 | {0 | SYN_BGMK(15), 6 | SYN_BD, 12 | SYN_BD, 5, 8 | SYN_BD}}, 135 | {"nm", "^[N].*", {0 | SYN_BD | SYN_BGMK(6)}}, 136 | {"nm", "^[A-Z][HT].*", {0 | SYN_BD | SYN_BGMK(13)}}, 137 | {"nm", "^[A-Z][MI].*", {0 | SYN_BD | SYN_BGMK(11)}}, 138 | {"nm", "^[A-Z][LJ].*", {7 | SYN_BGMK(15)}}, 139 | {"nm", "^[F].*", {0 | SYN_BD | SYN_BGMK(7)}}, 140 | {"nm", "^\t.*", {7 | SYN_IT}}, 141 | {"nm", "^:.*", {SYN_BD}}, 142 | 143 | /* javascript */ 144 | {"js", NULL, {14 | SYN_BD}, {1}, 0, 2}, 145 | {"js", NULL, {9}, {0}, 0, 1}, 146 | {"js", "(/\\*(?:(?!^\\*/).)*)|((?:(?!^/\\*).)*\\*/(?![\"'`]))", {10 | SYN_IT}, {0}, 2}, 147 | {"js", "\\<(?:abstract|arguments|await|boolean|\ 148 | break|byte|case|catch|char|class|const|continue|debugger|default|delete|do|\ 149 | double|else|enum|eval|export|extends|false|final|finally|float|for|function|\ 150 | goto|if|implements|import|in|instanceof|int|interface|let|long|native|new|\ 151 | null|package|private|protected|public|return|short|static|super|switch|synchronized|\ 152 | this|throw|throws|transient|true|try|typeof|var|void|volatile|while|with|yield|\ 153 | (Array|Date|hasOwnProperty|Infinity|isFinite|isNaN|isPrototypeOf|length|Math|NaN|\ 154 | name|Number|Object|prototype|String|toString|undefined|valueOf))\\>", {12, 6 | SYN_BD}}, 155 | {"js", "[-+]?\\<(?:0[xX][0-9a-fA-F]+|[0-9]+)\\>", {9}}, 156 | {"js", "//.*", {10 | SYN_IT}}, 157 | {"js", "'(?:[^'\\\\]|\\\\.)*'", {5}}, 158 | {"js", "\"(?:[^\"\\\\]|\\\\.)*\"", {5}}, 159 | {"js", "`(?:[^`\\\\]|\\\\.)*`", {5}}, 160 | 161 | /* html */ 162 | {"html", NULL, {14 | SYN_BD}, {1}, 0, 2}, 163 | {"html", "(\\{)[^}]*|(^[^{]*)?(\\})", {8, 5, 8, 5}, {1, 1, -1, -1}, 3}, 164 | {"html", NULL, {9}, {0}, 0, 1}, 165 | {"html", "(/\\*(?:(?!^\\*/).)*)|((?:(?!^/\\*).)*\\*/)", {5 | SYN_IT}, {0}, 2}, 166 | {"html", "().)*)|((?:(?!^)", {5 | SYN_IT}, {0}, 2}, 167 | {"html", "([^\t -,.-/:-@[-^{-~]+:).+;", {0, 3}, {1, 1}}, 168 | {"html", "\\<(?:accept|accesskey|align|allow|alt|async|\ 169 | auto(?:capitalize|complete|focus|play)|background|\ 170 | bgcolor|border|buffered|challenge|charset|checked|cite|\ 171 | class|code(?:base)|color|cols|colspan|content(?:editable)|\ 172 | contextmenu|controls|coords|crossorigin|import|url|\ 173 | csp|data|datetime|decoding|def(?:ault|er)|dir|dirname|\ 174 | disabled|download|draggable|dropzone|enctype|enterkeyhint|\ 175 | equiv|for|form|action|headers|height|hidden|high|href|http|\ 176 | icon|id|importance|inputmode|integrity|intrinsicsize|ismap|\ 177 | itemprop|keytype|kind|label|lang|language|list|loading|loop|\ 178 | max|maxlength|media|method|min|minlength|multiple|muted|\ 179 | name|novalidate|open|optimum|pattern|ping|placeholder|\ 180 | poster|preload|property|radiogroup|readonly|referrerpolicy|\ 181 | rel|required|reversed|rows|rowspan|sandbox|scope|scoped|\ 182 | selected|shape|size|sizes|slot|span|spellcheck|src|srcdoc|\ 183 | srclang|srcset|start|step|style|summary|tabindex|target|\ 184 | title|translate|type|usemap|value|width|wrap|low|manifest|\ 185 | (html|base|head|link|meta|body|address|article|\ 186 | aside|footer|header|hgroup|nav|section|blockquote|dd|\ 187 | div|dl|dt|figcaption|figure|hr|li|main|ol|p|pre|ul|a|abbr|\ 188 | b|bdi|bdo|br|dfn|em|i|kbd|mark|q|rb|rp|rt|rtc|\ 189 | ruby|s|samp|small|strong|sub|sup|time|tt|u|var|wbr|area|\ 190 | audio|img|map|track|video|embed|iframe|object|\ 191 | param|picture|source|canvas|noscript|script|del|ins|caption|\ 192 | col|colgroup|table|tbody|td|tfoot|th|thead|tr|button|datalist|\ 193 | fieldset|input|legend|meter|optgroup|option|output|\ 194 | progress|select|textarea|details|dialog|menu|\ 195 | shadow|template|acronym|applet|basefont|\ 196 | bgsound|big|blink|center|command|element|font|\ 197 | frame|frameset|image|isindex|keygen|listing|marquee|menuitem|\ 198 | multicol|nextid|nobr|noembed|noframes|plaintext|spacer|\ 199 | strike|tt|xmp|doctype|h1|h2|h3|h4|h5|h6|\ 200 | (fixed;|absolute;|relative;)))\\>", {2, 6, 13}}, 201 | {"html", "\"(?:[^\"\\\\]|\\\\.)*\"", {12}}, 202 | {"html", "'(?:[^'\\\\]|\\\\.)*'", {5}}, 203 | {"html", "#\\<[A-Fa-f0-9]+\\>", {9}}, 204 | {"html", "[-+]?\\<(?:0[xX][0-9a-fA-F]+|[0-9]+(?:px|vw|vh|%|s)?)\\>", {9}}, 205 | {"html", "<(/)?[^>]+>", {3, 13}, {1}}, 206 | /* do not use this regex on DFA engines, causes catastrophic backtracking */ 207 | {"html", "([#.][ \t]*[a-zA-Z0-9_\\-]+\ 208 | (?:(?:[, \t]*[#.][a-zA-Z0-9_\\-]+)?)+)(?:.?""?){1,20}\\{", {0, SYN_BD}, {1}}, 209 | {"html", "&[a-zA-Z0-9_]+;", {5}}, 210 | 211 | /* diff */ 212 | {"diff", NULL, {14 | SYN_BD}, {1}, 0, 2}, 213 | {"diff", "^-.*", {1}}, 214 | {"diff", "^\\+.*", {2}}, 215 | {"diff", "^@.*", {6}}, 216 | {"diff", "^diff .*", {SYN_BD}}, 217 | 218 | /* file manager */ 219 | {"/fm", "^\\.+(?:(?:(/)\\.\\.+)+)?", {4, 6}}, 220 | {"/fm", "[^/]*\\.sh\n$", {2}}, 221 | {"/fm", "[^/]*(?:\\.c|\\.h|\\.cpp|\\.cc)\n$", {5}}, 222 | {"/fm", "/.*/([^/]+\n$)?", {6, 8}, {1, 1}}, 223 | {"/fm", "(/).*[^/]+\n$", {8, 6}, {1, 1}}, 224 | 225 | /* numbers highlight for ^v */ 226 | {"/#", "[0lewEW]", {14 | SYN_BD}}, 227 | {"/#", "1([ \t]*[1-9][ \t]*)9", {9, 13 | SYN_BD}}, 228 | {"/#", "9[ \t]*([1-9][ \t]*)1", {9, 13 | SYN_BD}}, 229 | {"/#", "[1-9]", {9}}, 230 | 231 | /* numbers highlight for # */ 232 | {"/##", "[0-9]+", {9 | SYN_BD}}, 233 | 234 | /* autocomplete dropdown */ 235 | {"/ac", "[^ \t-/:-@[-^{-~]+(?:(\n$)|\n)|\n|([^\n]+(\n))", 236 | {0, SYN_BGMK(9), SYN_BGMK(8), SYN_BGMK(7)}}, 237 | {"/ac", "[^ \t-/:-@[-^{-~]+$|(.+$)", {0, SYN_BGMK(8)}}, 238 | 239 | /* status bar (is never '\n' terminated) */ 240 | {"/-", "^(\".*\").*(\\[[wrf]\\]).*$", {8 | SYN_BD, 4, 1}}, 241 | {"/-", "^<(.+)> [^ ]+ ([0-9]+L) ([0-9]+W) (S[0-9]+) (O[0-9]+) (C[0-9]+)$", 242 | {8 | SYN_BD, 9, 4, 3, 5, 14, 11}}, 243 | {"/-", "^(\".*\").* ([0-9]{1,3}%) (L[0-9]+) (C[0-9]+) (B-?[0-9]+)?.*$", 244 | {8 | SYN_BD, 4, 9, 4, 11, 2}}, 245 | {"/-", "^.*$", {8 | SYN_BD}}, 246 | }; 247 | int hlslen = LEN(hls); 248 | 249 | /* how to highlight text in the reverse direction */ 250 | int conf_hlrev = SYN_BGMK(8); 251 | 252 | /* right-to-left characters (used only in dctxs[] and dmarks[]) */ 253 | #define CR2L "ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىييپچژکگی‌‍؛،»«؟ًٌٍَُِّْٔ" 254 | /* neutral characters (used only in dctxs[] and dmarks[]) */ 255 | #define CNEUT "-!\"#$%&'\\()*+,./:;<=>?@\\^_`{|}~ " 256 | 257 | struct dircontext dctxs[] = { 258 | {-1, "^[" CR2L "]"}, 259 | {+1, "^[a-zA-Z_0-9]"}, 260 | }; 261 | int dctxlen = LEN(dctxs); 262 | 263 | struct dirmark dmarks[] = { 264 | {+1, {-1}, "[" CR2L "][" CNEUT CR2L "]*[" CR2L "]"}, 265 | {-1, {0, 1, -1, 1, -1}, "(^[ \t]*)([^" CR2L "]*)([" CR2L "]*)([^" CR2L "]*)"}, 266 | }; 267 | int dmarkslen = LEN(dmarks); 268 | 269 | struct placeholder _ph[2] = { 270 | {{0x0,0x1f}, "^", 1, 1}, 271 | {{0x200c,0x200d}, "-", 1, 3}, 272 | }; 273 | struct placeholder *ph = _ph; 274 | int phlen = LEN(_ph); 275 | 276 | char **conf_kmap(int id) 277 | { 278 | return kmaps[id]; 279 | } 280 | 281 | int conf_kmapfind(char *name) 282 | { 283 | for (int i = 0; i < LEN(kmaps); i++) 284 | if (name && kmaps[i][0] && !strcmp(name, kmaps[i][0])) 285 | return i; 286 | return 0; 287 | } 288 | 289 | char *conf_digraph(int c1, int c2) 290 | { 291 | for (int i = 0; i < LEN(digraphs); i++) 292 | if (digraphs[i][0][0] == c1 && digraphs[i][0][1] == c2) 293 | return digraphs[i][1]; 294 | return NULL; 295 | } 296 | -------------------------------------------------------------------------------- /ex.c: -------------------------------------------------------------------------------- 1 | int xrow, xoff, xtop; /* current row, column, and top row */ 2 | int xleft; /* the first visible column */ 3 | int xquit; /* exit if positive, force quit if negative */ 4 | int xvis; /* visual mode */ 5 | int xai = 1; /* autoindent option */ 6 | int xic = 1; /* ignorecase option */ 7 | int xhl = 1; /* syntax highlight option */ 8 | int xhll; /* highlight current line */ 9 | int xhlw; /* highlight current word */ 10 | int xhlp; /* highlight {}[]() pair */ 11 | int xhlr; /* highlight text in reverse direction */ 12 | int xled = 1; /* use the line editor */ 13 | int xtd = +1; /* current text direction */ 14 | int xshape = 1; /* perform letter shaping */ 15 | int xorder = 1; /* change the order of characters */ 16 | int xkmap; /* the current keymap */ 17 | int xkmap_alt = 1; /* the alternate keymap */ 18 | int xtabspc = 8; /* number of spaces for tab */ 19 | int xish; /* interactive shell */ 20 | int xgrp; /* regex search group */ 21 | int xpac; /* print autocomplete options */ 22 | int xkwdcnt; /* number of search kwd changes */ 23 | int xbufcur; /* number of active buffers */ 24 | int xgrec; /* global vi/ex recursion depth */ 25 | struct buf *bufs; /* main buffers */ 26 | struct buf tempbufs[2]; /* temporary buffers, for internal use */ 27 | struct buf *ex_buf; /* current buffer */ 28 | struct buf *ex_pbuf; /* prev buffer */ 29 | static struct buf *ex_tpbuf; /* temp prev buffer */ 30 | sbuf *xacreg; /* autocomplete db filter regex */ 31 | rset *xkwdrs; /* the last searched keyword rset */ 32 | int xkwddir; /* the last search direction */ 33 | int xmpt; /* whether to prompt after printing > 1 lines in vi */ 34 | int xpr; /* ex_cprint register */ 35 | int xsep = ':'; /* ex command separator */ 36 | char *xregs[256]; /* string registers */ 37 | static int xbufsmax; /* number of buffers */ 38 | static int xbufsalloc = 10; /* initial number of buffers */ 39 | static char xrep[EXLEN]; /* the last replacement */ 40 | static int xgdep; /* global command recursion depth */ 41 | 42 | static int rstrcmp(const char *s1, const char *s2, int l1, int l2) 43 | { 44 | if (l1 != l2 || !l1) 45 | return 1; 46 | for (int i = l1-1; i >= 0; i--) 47 | if (s1[i] != s2[i]) 48 | return 1; 49 | return 0; 50 | } 51 | 52 | static int bufs_find(const char *path, int len) 53 | { 54 | for (int i = 0; i < xbufcur; i++) 55 | if (!rstrcmp(bufs[i].path, path, bufs[i].plen, len)) 56 | return i; 57 | return -1; 58 | } 59 | 60 | static void bufs_free(int idx) 61 | { 62 | free(bufs[idx].path); 63 | lbuf_free(bufs[idx].lb); 64 | } 65 | 66 | static long mtime(char *path) 67 | { 68 | struct stat st; 69 | if (!stat(path, &st)) 70 | return st.st_mtime; 71 | return -1; 72 | } 73 | 74 | void bufs_switch(int idx) 75 | { 76 | if (ex_buf != &bufs[idx]) { 77 | exbuf_save(ex_buf) 78 | if (istempbuf(ex_buf)) 79 | ex_pbuf = &bufs[idx] == ex_pbuf ? ex_tpbuf : ex_pbuf; 80 | else 81 | ex_pbuf = ex_buf; 82 | ex_buf = &bufs[idx]; 83 | } 84 | exbuf_load(ex_buf) 85 | } 86 | 87 | static int bufs_open(const char *path, int len) 88 | { 89 | int i = xbufcur; 90 | if (i <= xbufsmax - 1) 91 | xbufcur++; 92 | else 93 | bufs_free(--i); 94 | bufs[i].path = uc_dup(path); 95 | bufs[i].lb = lbuf_make(); 96 | bufs[i].plen = len; 97 | bufs[i].row = 0; 98 | bufs[i].off = 0; 99 | bufs[i].top = 0; 100 | bufs[i].td = +1; 101 | bufs[i].mtime = -1; 102 | return i; 103 | } 104 | 105 | void temp_open(int i, char *name, char *ft) 106 | { 107 | if (tempbufs[i].lb) 108 | return; 109 | tempbufs[i].path = uc_dup(name); 110 | tempbufs[i].lb = lbuf_make(); 111 | tempbufs[i].row = 0; 112 | tempbufs[i].off = 0; 113 | tempbufs[i].top = 0; 114 | tempbufs[i].td = +1; 115 | tempbufs[i].mtime = -1; 116 | tempbufs[i].ft = ft; 117 | } 118 | 119 | void temp_pos(int i, int row, int off, int top) 120 | { 121 | if (row < 0) 122 | row = lbuf_len(tempbufs[i].lb)-1; 123 | tempbufs[i].row = row < 0 ? 0 : row; 124 | tempbufs[i].off = off; 125 | tempbufs[i].top = top; 126 | } 127 | 128 | void temp_switch(int i) 129 | { 130 | if (ex_buf == &tempbufs[i]) { 131 | exbuf_save(ex_buf) 132 | ex_buf = ex_pbuf; 133 | ex_pbuf = ex_tpbuf; 134 | } else { 135 | if (!istempbuf(ex_buf)) { 136 | ex_tpbuf = ex_pbuf; 137 | ex_pbuf = ex_buf; 138 | } 139 | exbuf_save(ex_buf) 140 | ex_buf = &tempbufs[i]; 141 | } 142 | exbuf_load(ex_buf) 143 | syn_setft(ex_ft); 144 | } 145 | 146 | void temp_write(int i, char *str) 147 | { 148 | if (!*str) 149 | return; 150 | struct lbuf *lb = tempbufs[i].lb; 151 | if (lbuf_get(lb, tempbufs[i].row)) 152 | tempbufs[i].row++; 153 | lbuf_edit(lb, str, tempbufs[i].row, tempbufs[i].row); 154 | } 155 | 156 | /* replace % and # with buffer names and !..! with command output */ 157 | static char *ex_pathexpand(char *src) 158 | { 159 | sbuf_smake(sb, 1024) 160 | while (*src) { 161 | if (*src == '#' || *src == '%') { 162 | int n = -1; 163 | struct buf *pbuf = *src == '%' ? ex_buf : ex_pbuf; 164 | if ((src[1] ^ '0') < 10) 165 | pbuf = &bufs[n = atoi(&src[1])]; 166 | if (pbuf >= &bufs[xbufcur] || !pbuf->path[0]) { 167 | ex_print("\"#\" or \"%\" is not set"); 168 | free(sb->s); 169 | return NULL; 170 | } 171 | sbuf_str(sb, pbuf->path) 172 | src += n >= 0 ? snprintf(0, 0, "%+d", n) : 1; 173 | } else if (*src == '!') { 174 | int n = sb->s_n; 175 | src++; 176 | while (*src && *src != '!') { 177 | if (*src == '\\' && src[1] == '!') 178 | src++; 179 | sbuf_chr(sb, *src++) 180 | } 181 | src += *src ? 1 : 0; 182 | sbuf_null(sb) 183 | char *str = cmd_pipe(sb->s + n, NULL, 1); 184 | sbuf_cut(sb, n) 185 | if (str) 186 | sbuf_str(sb, str) 187 | free(str); 188 | } else { 189 | if (*src == '\\' && 190 | (src[1] == '#' || src[1] == '%' || src[1] == '!')) 191 | src++; 192 | sbuf_chr(sb, *src++) 193 | } 194 | } 195 | sbufn_sret(sb) 196 | } 197 | 198 | /* set the current search keyword rset if the kwd or flags changed */ 199 | void ex_krsset(char *kwd, int dir) 200 | { 201 | char *reg = xregs['/']; 202 | if (kwd && *kwd && ((!reg || !xkwdrs || strcmp(kwd, reg)) 203 | || ((xkwdrs->regex->flg & REG_ICASE) != xic))) { 204 | rset_free(xkwdrs); 205 | xkwdrs = rset_smake(kwd, xic ? REG_ICASE : 0); 206 | xkwdcnt++; 207 | vi_regputraw('/', kwd, 0, 0); 208 | xkwddir = dir; 209 | } 210 | if (dir == -2 || dir == 2) 211 | xkwddir = dir / 2; 212 | } 213 | 214 | static int ec_search(char *loc, char *cmd, char *arg); 215 | 216 | static int ex_range(char **num, int n, int *row) 217 | { 218 | switch ((unsigned char) **num) { 219 | case '.': 220 | ++*num; 221 | break; 222 | case '$': 223 | n = row ? lbuf_eol(xb, *row) : lbuf_len(xb) - 1; 224 | ++*num; 225 | break; 226 | case '\'': 227 | if (lbuf_jump(xb, (unsigned char) *++(*num), 228 | &n, row ? &n : NULL)) 229 | return -1; 230 | ++*num; 231 | break; 232 | case '/': 233 | case '?': 234 | n = ec_search(NULL, (char*)row, (char*)num); 235 | break; 236 | default: 237 | if (isdigit((unsigned char) **num)) { 238 | n = atoi(*num) - !row; 239 | while (isdigit((unsigned char) **num)) 240 | ++*num; 241 | } 242 | } 243 | while (**num == '-' || **num == '+') { 244 | n += atoi((*num)++); 245 | while (isdigit((unsigned char) **num)) 246 | ++*num; 247 | } 248 | return n; 249 | } 250 | 251 | /* parse ex command addresses */ 252 | #define ex_region(loc, beg, end) ex_oregion(loc, beg, end, NULL, NULL) 253 | static int ex_oregion(char *loc, int *beg, int *end, int *o1, int *o2) 254 | { 255 | int naddr = 0; 256 | if (!strcmp("%", loc) || !lbuf_len(xb)) { 257 | *beg = 0; 258 | *end = MAX(0, lbuf_len(xb)); 259 | return 0; 260 | } 261 | if (!*loc) { 262 | *beg = xrow; 263 | *end = MIN(lbuf_len(xb), xrow + 1); 264 | return 0; 265 | } 266 | while (*loc) { 267 | int end0 = *end; 268 | if (*loc == ';' || *loc == ',') { 269 | loc++; 270 | if (loc[-1] == ',') 271 | goto skip; 272 | if (o1 && *o2 >= 0) 273 | *o1 = *o2; 274 | xoff = ex_range(&loc, xoff, naddr ? beg : &xrow); 275 | if (o2) 276 | *o2 = xoff; 277 | } else { 278 | skip: 279 | *end = ex_range(&loc, xrow, NULL) + 1; 280 | *beg = naddr++ ? end0 - 1 : *end - 1; 281 | } 282 | while (*loc && *loc != ';' && *loc != ',') 283 | loc++; 284 | } 285 | if (*beg < 0 && *end == 0) 286 | *beg = 0; 287 | if (*beg < 0 || *beg >= lbuf_len(xb)) 288 | return 1; 289 | if (*end < *beg || *end > lbuf_len(xb)) 290 | return 1; 291 | return 0; 292 | } 293 | 294 | static int ec_search(char *loc, char *cmd, char *arg) 295 | { 296 | int dir, off, obeg, beg = -1, end = lbuf_len(xb); 297 | char **re = !loc ? (char**)arg : &arg; 298 | dir = **re == '/' ? 2 : -2; 299 | char *e = re_read(re); 300 | if (!e) 301 | return -1; 302 | ex_krsset(e, dir); 303 | free(e); 304 | if (!xkwdrs) { 305 | ex_print("re syntax error"); 306 | return -1; 307 | } 308 | if (!loc) { 309 | beg = cmd ? *(int*)cmd : xrow + (xkwddir > 0); 310 | off = cmd ? xoff : 0; 311 | if (lbuf_search(xb, xkwdrs, xkwddir, &beg, 312 | &off, end, MIN(dir, 0))) 313 | return -1; 314 | } else if (!ex_region(loc, &beg, &end)) { 315 | off = xoff; 316 | obeg = beg; 317 | if (xrow < beg || xrow > end) { 318 | off = 0; 319 | beg = xkwddir > 0 ? beg : end++; 320 | } else 321 | beg = xrow; 322 | if (lbuf_search(xb, xkwdrs, xkwddir, &beg, 323 | &off, end, xkwddir)) 324 | return -1; 325 | if (beg < obeg) 326 | return -1; 327 | xrow = beg; 328 | xoff = off; 329 | } 330 | return cmd ? off : beg; 331 | } 332 | 333 | static int ec_buffer(char *loc, char *cmd, char *arg) 334 | { 335 | if (!arg[0]) { 336 | char ln[EXLEN]; 337 | for (int i = 0; i < xbufcur; i++) { 338 | char c = ex_buf == bufs+i ? '%' : ' '; 339 | c = ex_pbuf == bufs+i ? '#' : c; 340 | snprintf(ln, LEN(ln), "%d %c %s", i, 341 | c + (char)lbuf_modified(bufs[i].lb), bufs[i].path); 342 | ex_print(ln); 343 | } 344 | } else if (atoi(arg) < 0) { 345 | if (abs(atoi(arg)) <= LEN(tempbufs)) 346 | temp_switch(abs(atoi(arg))-1); 347 | else 348 | ex_print("no such buffer"); 349 | } else if (atoi(arg) < xbufcur) { 350 | bufs_switchwft(atoi(arg)) 351 | } else 352 | ex_print("no such buffer"); 353 | return 0; 354 | } 355 | 356 | static int ec_quit(char *loc, char *cmd, char *arg) 357 | { 358 | for (int i = 0; !strchr(cmd, '!') && i < xbufcur; i++) 359 | if ((xquit < 0 || xgrec < 2) && lbuf_modified(bufs[i].lb)) { 360 | ex_print("buffers modified"); 361 | return 1; 362 | } 363 | if (!xquit) 364 | xquit = !strchr(cmd, '!') ? 1 : -1; 365 | return 0; 366 | } 367 | 368 | void ex_bufpostfix(struct buf *p, int clear) 369 | { 370 | p->mtime = mtime(p->path); 371 | p->ft = syn_filetype(p->path); 372 | lbuf_saved(p->lb, clear); 373 | } 374 | 375 | #define readfile(errchk, init) \ 376 | fd = open(ex_path, O_RDONLY); \ 377 | if (fd >= 0) { \ 378 | errchk lbuf_rd(xb, fd, 0, lbuf_len(xb), init); \ 379 | close(fd); \ 380 | } \ 381 | 382 | int ex_edit(const char *path, int len) 383 | { 384 | int fd; 385 | if (path[0] == '.' && path[1] == '/') { 386 | path += 2; 387 | len -= 2; 388 | } 389 | if (path[0] && ((fd = bufs_find(path, len)) >= 0)) { 390 | bufs_switch(fd); 391 | return 1; 392 | } 393 | bufs_switch(bufs_open(path, len)); 394 | readfile(/**/, 1) 395 | return 0; 396 | } 397 | 398 | static int ec_edit(char *loc, char *cmd, char *arg) 399 | { 400 | char msg[128]; 401 | int fd, len, rd = 0, cd = 0; 402 | if (arg[0] == '.' && arg[1] == '/') 403 | cd = 2; 404 | len = strlen(arg+cd); 405 | if (len && ((fd = bufs_find(arg+cd, len)) >= 0)) { 406 | bufs_switchwft(fd) 407 | return 0; 408 | } else if (xbufcur == xbufsmax && !strchr(cmd, '!') && 409 | lbuf_modified(bufs[xbufsmax - 1].lb)) { 410 | ex_print("last buffer modified"); 411 | return 1; 412 | } else if (len || !xbufcur || !strchr(cmd, '!')) { 413 | bufs_switch(bufs_open(arg+cd, len)); 414 | cd = 3; /* XXX: quick hack to indicate new lbuf */ 415 | } 416 | readfile(rd =, cd == 3) 417 | if (cd == 3 || (!rd && fd >= 0)) { 418 | ex_bufpostfix(ex_buf, arg[0]); 419 | syn_setft(ex_ft); 420 | } 421 | snprintf(msg, sizeof(msg), "\"%s\" %dL [%c]", 422 | *ex_path ? ex_path : "unnamed", lbuf_len(xb), 423 | fd < 0 || rd ? 'f' : 'r'); 424 | if (!(xvis & 8)) 425 | ex_print(msg); 426 | return fd < 0 || rd; 427 | } 428 | 429 | static int ec_editapprox(char *loc, char *cmd, char *arg) 430 | { 431 | sbuf_smake(sb, 128) 432 | char ln[EXLEN]; 433 | char *path, *arg1 = arg+dstrlen(arg, ' '); 434 | struct lbuf *lb = tempbufs[1].lb; 435 | int c = 0, i, inst = *arg1 ? atoi(arg1) : -1; 436 | *arg1 = '\0'; 437 | for (int pos = 0; pos < lbuf_len(lb); pos++) { 438 | path = lb->ln[pos]; 439 | for (i = lbuf_s(path)->len; i > 0 && path[i] != '/'; i--); 440 | if (!i) 441 | continue; 442 | if (strstr(&path[i+1], arg)) { 443 | sbuf_mem(sb, &path, (int)sizeof(path)) 444 | snprintf(ln, LEN(ln), "%d %s", c++, path); 445 | ex_print(ln); 446 | } 447 | } 448 | if (inst < 0 && c > 1) { 449 | inst = term_read(); 450 | inst = inst == '\n' ? 0 : inst - '0'; 451 | } 452 | if ((inst >= 0 && inst < c) || c == 1) { 453 | path = *((char**)sb->s + (c == 1 ? 0 : inst)); 454 | path[lbuf_s(path)->len] = '\0'; 455 | ec_edit(loc, cmd, path); 456 | path[lbuf_s(path)->len] = '\n'; 457 | } 458 | xmpt = xmpt >= 0 ? 0 : xmpt; 459 | free(sb->s); 460 | return 0; 461 | } 462 | 463 | static int ec_setpath(char *loc, char *cmd, char *arg) 464 | { 465 | free(ex_path); 466 | ex_path = uc_dup(arg); 467 | ex_buf->plen = strlen(arg); 468 | return 0; 469 | } 470 | 471 | static int ec_read(char *loc, char *cmd, char *arg) 472 | { 473 | char msg[EXLEN+32]; 474 | int beg, end, fd = -1; 475 | char *path; 476 | char *obuf; 477 | int n = lbuf_len(xb); 478 | int pos = MIN(xrow + 1, lbuf_len(xb)); 479 | struct lbuf *lb = lbuf_make(), *pxb = xb; 480 | path = arg[0] ? arg : ex_path; 481 | if (arg[0] == '!') { 482 | obuf = cmd_pipe(arg + 1, NULL, 1); 483 | if (obuf) 484 | lbuf_edit(lb, obuf, 0, 0); 485 | free(obuf); 486 | } else { 487 | if ((fd = open(path, O_RDONLY)) < 0) { 488 | strcpy(msg, "open failed"); 489 | goto err; 490 | } 491 | if (lbuf_rd(lb, fd, 0, 0, 0)) { 492 | strcpy(msg, "read failed"); 493 | goto err; 494 | } 495 | } 496 | xb = lb; 497 | xrow = 0; 498 | if (ex_region(loc, &beg, &end)) { 499 | strcpy(msg, "bad region"); 500 | goto err; 501 | } 502 | obuf = lbuf_cp(lb, beg, end); 503 | if (*obuf) 504 | lbuf_edit(pxb, obuf, pos, pos); 505 | snprintf(msg, sizeof(msg), "\"%s\" %dL [r]", 506 | path, lbuf_len(pxb) - n); 507 | free(obuf); 508 | err: 509 | lbuf_free(lb); 510 | xrow = pos; 511 | xb = pxb; 512 | if (fd >= 0) 513 | close(fd); 514 | ex_print(msg); 515 | return 0; 516 | } 517 | 518 | static int ex_pipeout(char *cmd, char *buf) 519 | { 520 | if (!(xvis & 4)) { 521 | term_chr('\n'); 522 | xmpt = xmpt >= 0 ? 2 : xmpt; 523 | } 524 | return !!cmd_pipe(cmd, buf, 0); 525 | } 526 | 527 | static int ec_write(char *loc, char *cmd, char *arg) 528 | { 529 | char msg[EXLEN+32]; 530 | char *path; 531 | char *ibuf; 532 | int beg, end; 533 | path = arg[0] ? arg : ex_path; 534 | if (cmd[0] == 'x' && !lbuf_modified(xb)) 535 | return ec_quit("", cmd, ""); 536 | if (ex_region(loc, &beg, &end)) 537 | return 1; 538 | if (!loc[0]) { 539 | beg = 0; 540 | end = lbuf_len(xb); 541 | } 542 | if (arg[0] == '!') { 543 | ibuf = lbuf_cp(xb, beg, end); 544 | ex_pipeout(arg + 1, ibuf); 545 | free(ibuf); 546 | } else { 547 | int fd; 548 | if (!strchr(cmd, '!')) { 549 | if (!strcmp(ex_path, path) && mtime(path) > ex_buf->mtime) { 550 | ex_print("write failed: file changed"); 551 | return 1; 552 | } 553 | if (arg[0] && mtime(path) >= 0) { 554 | ex_print("write failed: file exists"); 555 | return 1; 556 | } 557 | } 558 | fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, conf_mode); 559 | if (fd < 0) { 560 | ex_print("write failed: cannot create file"); 561 | return 1; 562 | } 563 | if (lbuf_wr(xb, fd, beg, end)) { 564 | ex_print("write failed"); 565 | close(fd); 566 | return 1; 567 | } 568 | close(fd); 569 | snprintf(msg, sizeof(msg), "\"%s\" %dL [w]", 570 | path, end - beg); 571 | ex_print(msg); 572 | } 573 | if (strcmp(ex_path, path)) 574 | ec_setpath(NULL, NULL, path); 575 | lbuf_saved(xb, 0); 576 | ex_buf->mtime = mtime(path); 577 | if (cmd[0] == 'x' || (cmd[0] == 'w' && cmd[1] == 'q')) 578 | ec_quit("", cmd, ""); 579 | return 0; 580 | } 581 | 582 | static int ec_termexec(char *loc, char *cmd, char *arg) 583 | { 584 | if (*arg) 585 | term_exec(arg, strlen(arg), cmd[0]) 586 | return 0; 587 | } 588 | 589 | static int ec_insert(char *loc, char *cmd, char *arg) 590 | { 591 | sbuf *sb; 592 | char *s; 593 | int beg, end; 594 | int n; 595 | if (ex_region(loc, &beg, &end)) 596 | return 1; 597 | sbufn_make(sb, 64) 598 | if (*arg) 599 | term_push(arg, strlen(arg)); 600 | while ((s = ex_read(NULL))) { 601 | if (xvis & 2 && !strcmp(".", s)) 602 | break; 603 | sbuf_str(sb, s) 604 | sbufn_chr(sb, '\n') 605 | free(s); 606 | } 607 | free(s); 608 | if (cmd[0] == 'a' && (beg + 1 <= lbuf_len(xb))) 609 | beg++; 610 | if (cmd[0] != 'c') 611 | end = beg; 612 | if (vi_insmov != TK_CTL('c')) { 613 | n = lbuf_len(xb); 614 | lbuf_edit(xb, sb->s, beg, end); 615 | xrow = MIN(lbuf_len(xb) - 1, end + lbuf_len(xb) - n - 1); 616 | } else 617 | vi_insmov = 0; 618 | sbuf_free(sb) 619 | return 0; 620 | } 621 | 622 | static int ec_print(char *loc, char *cmd, char *arg) 623 | { 624 | int i, beg, end, o1 = -1, o2 = -1; 625 | char *o; 626 | if (!cmd[0] && !loc[0] && arg[0]) { 627 | ex_print("unknown command"); 628 | return 1; 629 | } 630 | if (ex_oregion(loc, &beg, &end, &o1, &o2)) 631 | return 1; 632 | if (!cmd[0] && loc[0]) { 633 | xrow = MAX(beg, end - 1); 634 | return 0; 635 | } 636 | for (i = beg; i < end; i++) { 637 | o = NULL; 638 | if (o1 >= 0 && o2 >= 0) { 639 | if (beg == end-1) 640 | o = uc_sub(lbuf_get(xb, i), o1, o2); 641 | else if (i == beg) 642 | o = uc_sub(lbuf_get(xb, i), o1, -1); 643 | else if (i == end-1) 644 | o = uc_sub(lbuf_get(xb, i), 0, o2); 645 | } 646 | ex_print(o ? o : lbuf_get(xb, i)); 647 | free(o); 648 | } 649 | xrow = MAX(beg, end - (cmd[0] || loc[0])); 650 | return 0; 651 | } 652 | 653 | static int ec_delete(char *loc, char *cmd, char *arg) 654 | { 655 | int beg, end; 656 | if (ex_region(loc, &beg, &end) || !lbuf_len(xb)) 657 | return 1; 658 | char *buf = lbuf_cp(xb, beg, end); 659 | vi_regput((unsigned char) arg[0], buf, 1); 660 | free(buf); 661 | lbuf_edit(xb, NULL, beg, end); 662 | xrow = beg; 663 | return 0; 664 | } 665 | 666 | static int ec_yank(char *loc, char *cmd, char *arg) 667 | { 668 | int beg, end; 669 | if (cmd[2] == '!') { 670 | vi_regputraw(arg[0], "", 0, 0); 671 | return 0; 672 | } 673 | if (ex_region(loc, &beg, &end) || !lbuf_len(xb)) 674 | return 1; 675 | char *buf = lbuf_cp(xb, beg, end); 676 | vi_regputraw(arg[0], buf, 1, isupper((unsigned char) arg[0]) || arg[1]); 677 | free(buf); 678 | return 0; 679 | } 680 | 681 | static int ec_put(char *loc, char *cmd, char *arg) 682 | { 683 | int beg, end, i = 0; 684 | char *buf; 685 | int n = lbuf_len(xb); 686 | if (arg[i] == '!' && arg[i+1] && arg[i+1] != ' ') 687 | buf = xregs[0]; 688 | else 689 | buf = xregs[(unsigned char) arg[i++]]; 690 | if (!buf || ex_region(loc, &beg, &end)) 691 | return 1; 692 | for (; arg[i] && arg[i] != '!'; i++){} 693 | if (arg[i] == '!' && arg[i+1]) 694 | return ex_pipeout(arg + i + 1, buf); 695 | lbuf_edit(xb, buf, end, end); 696 | xrow = MIN(lbuf_len(xb) - 1, end + lbuf_len(xb) - n - 1); 697 | return 0; 698 | } 699 | 700 | static int ec_lnum(char *loc, char *cmd, char *arg) 701 | { 702 | char msg[128]; 703 | int beg, end; 704 | if (ex_region(loc, &beg, &end)) 705 | return 1; 706 | sprintf(msg, "%d", end); 707 | ex_print(msg); 708 | return 0; 709 | } 710 | 711 | static int ec_undoredo(char *loc, char *cmd, char *arg) 712 | { 713 | int n = !arg[0] ? 1 : atoi(arg); 714 | if (arg[0] == '$') 715 | n = -1; 716 | for (int ret = 0; n && !ret; n--) 717 | ret = cmd[0] == 'u' ? lbuf_undo(xb) : lbuf_redo(xb); 718 | return 0; 719 | } 720 | 721 | static int ec_save(char *loc, char *cmd, char *arg) 722 | { 723 | lbuf_saved(xb, *arg); 724 | return 0; 725 | } 726 | 727 | static int ec_mark(char *loc, char *cmd, char *arg) 728 | { 729 | int beg, end; 730 | if (ex_region(loc, &beg, &end)) 731 | return 1; 732 | lbuf_mark(xb, (unsigned char) arg[0], end - 1, xoff); 733 | return 0; 734 | } 735 | 736 | static void replace(sbuf *dst, char *rep, char *ln, int *offs) 737 | { 738 | while (rep[0]) { 739 | if (rep[0] == '\\' && rep[1]) { 740 | if (rep[1] >= '0' && rep[1] <= '9') { 741 | int grp = (rep[1] - '0') * 2; 742 | int len = offs[grp + 1] - offs[grp]; 743 | sbuf_mem(dst, ln + offs[grp], len) 744 | } else 745 | sbuf_chr(dst, (unsigned char) rep[1]) 746 | rep++; 747 | } else 748 | sbuf_chr(dst, (unsigned char) rep[0]) 749 | rep++; 750 | } 751 | } 752 | 753 | static int ec_substitute(char *loc, char *cmd, char *arg) 754 | { 755 | int beg, end; 756 | char *pat = NULL, *rep = NULL; 757 | char *s = arg; 758 | int i, first = -1, last; 759 | if (ex_region(loc, &beg, &end)) 760 | return 1; 761 | pat = re_read(&s); 762 | ex_krsset(pat, +1); 763 | if (pat && *s) { 764 | s--; 765 | rep = re_read(&s); 766 | } 767 | if (pat || rep) 768 | snprintf(xrep, sizeof(xrep), "%s", rep ? rep : ""); 769 | free(pat); 770 | free(rep); 771 | if (!xkwdrs) { 772 | ex_print("re syntax error"); 773 | return 1; 774 | } 775 | int offs[xkwdrs->grpcnt * 2]; 776 | for (i = beg; i < end; i++) { 777 | char *ln = lbuf_get(xb, i); 778 | sbuf *r = NULL; 779 | while (rset_find(xkwdrs, ln, offs, REG_NEWLINE) >= 0) { 780 | if (offs[xgrp] < 0) { 781 | ln += offs[1] > 0 ? offs[1] : 1; 782 | continue; 783 | } else if (!r) 784 | sbuf_make(r, 256) 785 | sbuf_mem(r, ln, offs[xgrp]) 786 | replace(r, xrep, ln, offs); 787 | ln += offs[xgrp + 1]; 788 | if (!offs[xgrp + 1]) /* zero-length match */ 789 | sbuf_chr(r, (unsigned char)*ln++) 790 | if (*ln == '\n' || !*ln || !strchr(s, 'g')) 791 | break; 792 | } 793 | if (r) { 794 | if (first < 0) { 795 | first = i; 796 | lbuf_emark(xb, lbuf_opt(xb, NULL, xrow, 0, 0), 0, 0); 797 | } 798 | sbufn_str(r, ln) 799 | lbuf_edit(xb, r->s, i, i + 1); 800 | sbuf_free(r) 801 | last = i; 802 | } 803 | } 804 | if (first >= 0) 805 | lbuf_emark(xb, lbuf_opt(xb, NULL, xrow, 0, 0), first, last); 806 | return 0; 807 | } 808 | 809 | static int ec_exec(char *loc, char *cmd, char *arg) 810 | { 811 | int beg, end; 812 | char *text, *rep; 813 | if (!loc[0]) 814 | return ex_pipeout(arg, NULL); 815 | if (ex_region(loc, &beg, &end)) 816 | return 1; 817 | text = lbuf_cp(xb, beg, end); 818 | rep = cmd_pipe(arg, text, 1); 819 | if (rep) 820 | lbuf_edit(xb, rep, beg, end); 821 | free(text); 822 | free(rep); 823 | return 0; 824 | } 825 | 826 | static int ec_ft(char *loc, char *cmd, char *arg) 827 | { 828 | ex_ft = syn_setft(arg[0] ? arg : ex_ft); 829 | ex_print(ex_ft); 830 | if (led_attsb) { 831 | sbuf_free(led_attsb) 832 | led_attsb = NULL; 833 | } 834 | syn_reload = 1; 835 | return 0; 836 | } 837 | 838 | static int ec_cmap(char *loc, char *cmd, char *arg) 839 | { 840 | if (arg[0]) 841 | xkmap_alt = conf_kmapfind(arg); 842 | else 843 | ex_print(conf_kmap(xkmap)[0]); 844 | if (arg[0] && !strchr(cmd, '!')) 845 | xkmap = xkmap_alt; 846 | return 0; 847 | } 848 | 849 | static int ec_glob(char *loc, char *cmd, char *arg) 850 | { 851 | int beg, end, not; 852 | char *pat, *s = arg; 853 | int i; 854 | rset *rs; 855 | if (!loc[0] && !xgdep) 856 | loc = "%"; 857 | if (ex_region(loc, &beg, &end)) 858 | return 1; 859 | not = !!strchr(cmd, '!'); 860 | pat = re_read(&s); 861 | if (pat) 862 | rs = rset_smake(pat, xic ? REG_ICASE : 0); 863 | free(pat); 864 | if (!pat || !rs) 865 | return 1; 866 | xgdep = !xgdep ? 1 : xgdep * 2; 867 | for (i = beg; i < end; i++) 868 | lbuf_i(xb, i)->grec |= xgdep; 869 | for (i = beg; i < lbuf_len(xb);) { 870 | char *ln = lbuf_get(xb, i); 871 | lbuf_s(ln)->grec &= ~xgdep; 872 | if ((rset_find(rs, ln, NULL, REG_NEWLINE) < 0) == not) { 873 | xrow = i; 874 | if (ex_exec(s)) 875 | break; 876 | i = MIN(i, xrow); 877 | } 878 | while (i < lbuf_len(xb) && !(lbuf_i(xb, i)->grec & xgdep)) 879 | i++; 880 | } 881 | rset_free(rs); 882 | xgdep /= 2; 883 | return 0; 884 | } 885 | 886 | static struct option { 887 | char *name; 888 | int *var; 889 | } options[] = { 890 | {"ai", &xai}, 891 | {"ic", &xic}, 892 | {"td", &xtd}, 893 | {"shape", &xshape}, 894 | {"order", &xorder}, 895 | {"hl", &xhl}, 896 | {"hll", &xhll}, 897 | {"hlw", &xhlw}, 898 | {"hlp", &xhlp}, 899 | {"hlr", &xhlr}, 900 | {"tbs", &xtabspc}, 901 | {"ish", &xish}, 902 | {"grp", &xgrp}, 903 | {"pac", &xpac}, 904 | {"led", &xled}, 905 | {"vis", &xvis}, 906 | {"mpt", &xmpt}, 907 | {"pr", &xpr}, 908 | {"sep", &xsep}, 909 | }; 910 | 911 | static char *cutword(char *s, char *d) 912 | { 913 | while (isspace((unsigned char)*s)) 914 | s++; 915 | while (*s && !isspace((unsigned char)*s)) 916 | *d++ = *s++; 917 | while (isspace((unsigned char)*s)) 918 | s++; 919 | *d = '\0'; 920 | return s; 921 | } 922 | 923 | static int ec_set(char *loc, char *cmd, char *arg) 924 | { 925 | char tok[EXLEN]; 926 | char opt[EXLEN]; 927 | char *s = arg; 928 | int val = 0; 929 | int i; 930 | if (*s) { 931 | s = cutword(s, tok); 932 | /* if prefix "no" before option */ 933 | if (tok[0] == 'n' && tok[1] == 'o') { 934 | strcpy(opt, tok + 2); 935 | val = 0; 936 | } else { 937 | char *r = strchr(tok, '='); 938 | if (r) { 939 | *r = '\0'; 940 | if (!(val = atoi(r+1))) 941 | if (!isdigit((unsigned char)r[1])) 942 | val = (unsigned char)r[1]; 943 | } else 944 | val = 1; 945 | strcpy(opt, tok); 946 | } 947 | for (i = 0; i < LEN(options); i++) { 948 | struct option *o = &options[i]; 949 | if (!strcmp(o->name, opt)) { 950 | *o->var = val; 951 | return 0; 952 | } 953 | } 954 | ex_print("unknown option"); 955 | return 1; 956 | } 957 | return 0; 958 | } 959 | 960 | static int ec_setdir(char *loc, char *cmd, char *arg) 961 | { 962 | free(fs_exdir); 963 | fs_exdir = uc_dup(*arg ? arg : "."); 964 | if (cmd[1] == 'd') 965 | dir_calc(fs_exdir); 966 | return 0; 967 | } 968 | 969 | static int ec_chdir(char *loc, char *cmd, char *arg) 970 | { 971 | char oldpath[4096]; 972 | char newpath[4096]; 973 | char *opath; 974 | int i, c, plen; 975 | oldpath[sizeof(oldpath)-1] = '\0'; 976 | if (!getcwd(oldpath, sizeof(oldpath))) 977 | goto err; 978 | if (*arg && chdir(arg)) 979 | goto err; 980 | if (!getcwd(newpath, sizeof(newpath))) 981 | goto err; 982 | plen = strlen(oldpath); 983 | if (oldpath[plen-1] != '/') 984 | oldpath[plen++] = '/'; 985 | for (i = 0; i < xbufcur; i++) { 986 | if (!bufs[i].path[0]) 987 | continue; 988 | if (bufs[i].path[0] == '/') { 989 | opath = bufs[i].path; 990 | } else { 991 | opath = oldpath; 992 | strncpy(opath+plen, bufs[i].path, sizeof(oldpath)-plen-1); 993 | } 994 | for (c = 0; opath[c] && opath[c] == newpath[c]; c++); 995 | if (newpath[c] || !opath[c]) 996 | c = 0; 997 | else if (opath[c] == '/') 998 | c++; 999 | opath = uc_dup(opath+c); 1000 | free(bufs[i].path); 1001 | bufs[i].path = opath; 1002 | bufs[i].plen = strlen(opath); 1003 | } 1004 | return 0; 1005 | err: 1006 | ex_print("chdir error"); 1007 | return 1; 1008 | } 1009 | 1010 | static int ec_setincl(char *loc, char *cmd, char *arg) 1011 | { 1012 | rset_free(fsincl); 1013 | if (!*arg) 1014 | fsincl = NULL; 1015 | else if (!(fsincl = rset_smake(arg, xic ? REG_ICASE : 0))) 1016 | ex_print("syntax error"); 1017 | return 0; 1018 | } 1019 | 1020 | static int ec_setacreg(char *loc, char *cmd, char *arg) 1021 | { 1022 | if (xacreg) 1023 | sbuf_free(xacreg) 1024 | if (*arg) { 1025 | sbuf_make(xacreg, 128) 1026 | sbufn_str(xacreg, arg) 1027 | } else 1028 | xacreg = NULL; 1029 | return 0; 1030 | } 1031 | 1032 | static int ec_setbufsmax(char *loc, char *cmd, char *arg) 1033 | { 1034 | xbufsmax = *arg ? atoi(arg) : xbufsalloc; 1035 | if (xbufsmax <= 0) 1036 | return 1; 1037 | int bufidx = ex_buf - bufs; 1038 | int pbufidx = ex_pbuf - bufs; 1039 | int tpbufidx = ex_tpbuf - bufs; 1040 | int istemp = !ex_buf ? 0 : istempbuf(ex_buf); 1041 | for (; xbufcur > xbufsmax; xbufcur--) 1042 | bufs_free(xbufcur - 1); 1043 | bufs = erealloc(bufs, sizeof(struct buf) * xbufsmax); 1044 | if (!istemp) 1045 | ex_buf = bufidx >= &bufs[xbufsmax] - bufs ? bufs : bufs+bufidx; 1046 | ex_pbuf = pbufidx >= &bufs[xbufsmax] - bufs ? bufs : bufs+pbufidx; 1047 | ex_tpbuf = tpbufidx >= &bufs[xbufsmax] - bufs ? bufs : bufs+tpbufidx; 1048 | return 0; 1049 | } 1050 | 1051 | static int ec_regprint(char *loc, char *cmd, char *arg) 1052 | { 1053 | static char buf[5] = " "; 1054 | xleft = (xcols / 2) * (*arg ? atoi(arg) : 0); 1055 | preserve(int, xtd, 2) 1056 | for (int i = 1; i < LEN(xregs); i++) { 1057 | if (xregs[i] && i != tolower(xpr)) { 1058 | *buf = i; 1059 | ex_cprint(buf, -1, 0, 0); 1060 | ex_cprint(xregs[i], -1, xleft ? 0 : 2, 1); 1061 | } 1062 | } 1063 | restore(xtd) 1064 | return 0; 1065 | } 1066 | 1067 | static int ec_setenc(char *loc, char *cmd, char *arg) 1068 | { 1069 | if (cmd[0] == 'p') { 1070 | if (!*arg) { 1071 | if (ph != _ph) 1072 | free(ph); 1073 | phlen = LEN(_ph); 1074 | ph = _ph; 1075 | return 0; 1076 | } else if (ph == _ph) { 1077 | ph = NULL; 1078 | phlen = 0; 1079 | } 1080 | ph = erealloc(ph, sizeof(struct placeholder) * (phlen + 1)); 1081 | ph[phlen].cp[0] = strtol(arg, &arg, 0); 1082 | ph[phlen].cp[1] = strtol(arg, &arg, 0); 1083 | ph[phlen].wid = strtol(arg, &arg, 0); 1084 | ph[phlen].l = strtol(arg, &arg, 0); 1085 | if (strlen(arg) && strlen(arg) < LEN(ph[0].d)) 1086 | strcpy(ph[phlen++].d, arg); 1087 | return 0; 1088 | } 1089 | if (cmd[1] == 'z') 1090 | zwlen = !zwlen ? def_zwlen : 0; 1091 | else if (cmd[1] == 'b') 1092 | bclen = !bclen ? def_bclen : 0; 1093 | else if (utf8_length[0xc0] == 1) { 1094 | memset(utf8_length+0xc0, 2, 0xe0 - 0xc0); 1095 | memset(utf8_length+0xe0, 3, 0xf0 - 0xe0); 1096 | memset(utf8_length+0xf0, 4, 0xf8 - 0xf0); 1097 | } else 1098 | memset(utf8_length+1, 1, 255); 1099 | return 0; 1100 | } 1101 | 1102 | static struct excmd { 1103 | char *name; 1104 | int (*ec)(char *loc, char *cmd, char *arg); 1105 | } excmds[] = { /* commands must be sorted longest of its kind topmost */ 1106 | {"@", ec_termexec}, 1107 | {"&", ec_termexec}, 1108 | {"!", ec_exec}, 1109 | {"bp", ec_setpath}, 1110 | {"bs", ec_save}, 1111 | {"bx", ec_setbufsmax}, 1112 | {"b", ec_buffer}, 1113 | {"pu", ec_put}, 1114 | {"ph", ec_setenc}, 1115 | {"p", ec_print}, 1116 | {"ac", ec_setacreg}, 1117 | {"a", ec_insert}, 1118 | {"ea!", ec_editapprox}, 1119 | {"ea", ec_editapprox}, 1120 | {"e!", ec_edit}, 1121 | {"e", ec_edit}, 1122 | {"ft", ec_ft}, 1123 | {"fd", ec_setdir}, 1124 | {"fp", ec_setdir}, 1125 | {"f", ec_search}, 1126 | {"inc", ec_setincl}, 1127 | {"i", ec_insert}, 1128 | {"d", ec_delete}, 1129 | {"g!", ec_glob}, 1130 | {"g", ec_glob}, 1131 | {"k", ec_mark}, 1132 | {"q!", ec_quit}, 1133 | {"q", ec_quit}, 1134 | {"reg", ec_regprint}, 1135 | {"rd", ec_undoredo}, 1136 | {"r", ec_read}, 1137 | {"wq!", ec_write}, 1138 | {"wq", ec_write}, 1139 | {"w!", ec_write}, 1140 | {"w", ec_write}, 1141 | {"uc", ec_setenc}, 1142 | {"uz", ec_setenc}, 1143 | {"ub", ec_setenc}, 1144 | {"u", ec_undoredo}, 1145 | {"se", ec_set}, 1146 | {"s", ec_substitute}, 1147 | {"x!", ec_write}, 1148 | {"x", ec_write}, 1149 | {"ya!", ec_yank}, 1150 | {"ya", ec_yank}, 1151 | {"cm!", ec_cmap}, 1152 | {"cm", ec_cmap}, 1153 | {"cd", ec_chdir}, 1154 | {"c", ec_insert}, 1155 | {"=", ec_lnum}, 1156 | {"", ec_print}, /* do not remove */ 1157 | }; 1158 | 1159 | /* parse ex command until xsep or null. */ 1160 | static const char *ex_parse(const char *src, char *loc, int *idx, char *arg) 1161 | { 1162 | int i, j; 1163 | while (*src == xsep || *src == ' ' || *src == '\t') 1164 | src++; 1165 | while (*src && strchr(" \t0123456789+-.,/?$';%", *src)) { 1166 | if (*src == '\'' && src[1]) 1167 | *loc++ = *src++; 1168 | if (*src == '/' || *src == '?') { 1169 | j = *src; 1170 | do { 1171 | if (*src == '\\' && src[1]) 1172 | *loc++ = *src++; 1173 | *loc++ = *src++; 1174 | } while (*src && *src != j); 1175 | if (*src) 1176 | *loc++ = *src++; 1177 | } else 1178 | *loc++ = *src++; 1179 | } 1180 | for (i = 0; i < LEN(excmds); i++) { 1181 | for (j = 0; excmds[i].name[j]; j++) 1182 | if (!src[j] || src[j] != excmds[i].name[j]) 1183 | break; 1184 | if (!excmds[i].name[j]) { 1185 | *idx = i; 1186 | src += j; 1187 | break; 1188 | } 1189 | } 1190 | if (*src == ' ' || *src == '\t') 1191 | src++; 1192 | while (*src && *src != xsep) { 1193 | if (*src == '\\' && src[1] == xsep) 1194 | src++; 1195 | *arg++ = *src++; 1196 | } 1197 | *loc = '\0'; 1198 | *arg = '\0'; 1199 | return src; 1200 | } 1201 | 1202 | /* execute a single ex command */ 1203 | int ex_exec(const char *ln) 1204 | { 1205 | int ret = 0, idx = 0, len = strlen(ln) + 1; 1206 | char loc[len], arg[len]; 1207 | while (*ln) { 1208 | ln = ex_parse(ln, loc, &idx, arg); 1209 | char *ecmd = ex_pathexpand(arg); 1210 | if (ecmd) 1211 | ret = excmds[idx].ec(loc, excmds[idx].name, ecmd); 1212 | free(ecmd); 1213 | } 1214 | return ret; 1215 | } 1216 | 1217 | /* ex main loop */ 1218 | void ex(void) 1219 | { 1220 | vi_lncol = 0; 1221 | xgrec++; 1222 | while (!xquit) { 1223 | char *ln = ex_read(":"); 1224 | if (ln) { 1225 | ex_command(ln) 1226 | free(ln); 1227 | lbuf_modified(xb); 1228 | } 1229 | } 1230 | xgrec--; 1231 | } 1232 | 1233 | void ex_init(char **files, int n) 1234 | { 1235 | xbufsalloc = MAX(n, xbufsalloc); 1236 | ec_setbufsmax(NULL, NULL, ""); 1237 | char *s = files[0] ? files[0] : ""; 1238 | do { 1239 | ec_edit("", "e", s); 1240 | s = *(++files); 1241 | } while (--n > 0); 1242 | xmpt = 0; 1243 | xvis &= ~8; 1244 | if ((s = getenv("EXINIT"))) 1245 | ex_command(s) 1246 | } 1247 | -------------------------------------------------------------------------------- /kmap.h: -------------------------------------------------------------------------------- 1 | static char *kmap_en[256] = { 2 | [0] = "en", 3 | }; 4 | 5 | static char *kmap_fa[256] = { 6 | [0] = "fa", 7 | ['`'] = "‍", 8 | ['1'] = "۱", 9 | ['2'] = "۲", 10 | ['3'] = "۳", 11 | ['4'] = "۴", 12 | ['5'] = "۵", 13 | ['6'] = "۶", 14 | ['7'] = "۷", 15 | ['8'] = "۸", 16 | ['9'] = "۹", 17 | ['0'] = "۰", 18 | ['-'] = "-", 19 | ['='] = "=", 20 | ['q'] = "ض", 21 | ['w'] = "ص", 22 | ['e'] = "ث", 23 | ['r'] = "ق", 24 | ['t'] = "ف", 25 | ['y'] = "غ", 26 | ['u'] = "ع", 27 | ['i'] = "ه", 28 | ['o'] = "خ", 29 | ['p'] = "ح", 30 | ['['] = "ج", 31 | [']'] = "چ", 32 | ['a'] = "ش", 33 | ['s'] = "س", 34 | ['d'] = "ی", 35 | ['f'] = "ب", 36 | ['g'] = "ل", 37 | ['h'] = "ا", 38 | ['j'] = "ت", 39 | ['k'] = "ن", 40 | ['l'] = "م", 41 | [';'] = "ک", 42 | ['\''] = "گ", 43 | ['z'] = "ظ", 44 | ['x'] = "ط", 45 | ['c'] = "ز", 46 | ['v'] = "ر", 47 | ['b'] = "ذ", 48 | ['n'] = "د", 49 | ['m'] = "پ", 50 | [','] = "و", 51 | ['.'] = ".", 52 | ['/'] = "/", 53 | ['\\'] = "\\", 54 | ['~'] = "÷", 55 | ['!'] = "!", 56 | ['@'] = "٬", 57 | ['#'] = "٫", 58 | ['$'] = "﷼", 59 | ['%'] = "٪", 60 | ['^'] = "×", 61 | ['&'] = "،", 62 | ['*'] = "*", 63 | ['('] = "(", 64 | [')'] = ")", 65 | ['_'] = "ـ", 66 | ['+'] = "+", 67 | ['Q'] = "ْ", 68 | ['W'] = "ٌ", 69 | ['E'] = "ٍ", 70 | ['R'] = "ً", 71 | ['T'] = "ُ", 72 | ['Y'] = "ِ", 73 | ['U'] = "َ", 74 | ['I'] = "ّ", 75 | ['O'] = "[", 76 | ['P'] = "]", 77 | ['{'] = "{", 78 | ['}'] = "}", 79 | ['A'] = "ؤ", 80 | ['S'] = "ئ", 81 | ['D'] = "ي", 82 | ['F'] = "إ", 83 | ['G'] = "أ", 84 | ['H'] = "آ", 85 | ['J'] = "ة", 86 | ['K'] = "«", 87 | ['L'] = "»", 88 | [':'] = ":", 89 | ['"'] = "؛", 90 | ['Z'] = "ك", 91 | ['X'] = "ٓ", 92 | ['C'] = "ژ", 93 | ['V'] = "ٰ", 94 | ['B'] = "‌", 95 | ['N'] = "ٔ", 96 | ['M'] = "ء", 97 | ['<'] = "<", 98 | ['>'] = ">", 99 | ['?'] = "؟", 100 | ['|'] = "|", 101 | }; 102 | 103 | static char *kmap_ru[256] = { 104 | [0] = "ru", 105 | ['q'] = "й", 106 | ['w'] = "ц", 107 | ['e'] = "у", 108 | ['r'] = "к", 109 | ['t'] = "е", 110 | ['y'] = "н", 111 | ['u'] = "г", 112 | ['i'] = "ш", 113 | ['o'] = "щ", 114 | ['p'] = "з", 115 | ['['] = "х", 116 | [']'] = "ь", 117 | ['a'] = "ф", 118 | ['s'] = "ы", 119 | ['d'] = "в", 120 | ['f'] = "а", 121 | ['g'] = "п", 122 | ['h'] = "р", 123 | ['j'] = "о", 124 | ['k'] = "л", 125 | ['l'] = "д", 126 | [';'] = "ж", 127 | ['\''] = "э", 128 | ['z'] = "я", 129 | ['x'] = "ч", 130 | ['c'] = "с", 131 | ['v'] = "м", 132 | ['b'] = "и", 133 | ['n'] = "т", 134 | ['m'] = "ь", 135 | [','] = "б", 136 | ['.'] = "ю", 137 | ['/'] = ".", 138 | ['\\'] = "\\", 139 | ['~'] = "÷", 140 | ['!'] = "!", 141 | ['@'] = "\"", 142 | ['#'] = "#", 143 | ['$'] = ";", 144 | ['%'] = "%", 145 | ['^'] = ":", 146 | ['&'] = "?", 147 | ['*'] = "*", 148 | ['('] = "(", 149 | [')'] = ")", 150 | ['_'] = "_", 151 | ['+'] = "+", 152 | ['Q'] = "Й", 153 | ['W'] = "Ц", 154 | ['E'] = "У", 155 | ['R'] = "К", 156 | ['T'] = "Е", 157 | ['Y'] = "Н", 158 | ['U'] = "Г", 159 | ['I'] = "Ш", 160 | ['O'] = "Щ", 161 | ['P'] = "З", 162 | ['{'] = "Х", 163 | ['}'] = "Ъ", 164 | ['A'] = "Ф", 165 | ['S'] = "Ы", 166 | ['D'] = "В", 167 | ['F'] = "А", 168 | ['G'] = "П", 169 | ['H'] = "Р", 170 | ['J'] = "О", 171 | ['K'] = "Л", 172 | ['L'] = "Д", 173 | [':'] = "Ж", 174 | ['"'] = "Э", 175 | ['Z'] = "Я", 176 | ['X'] = "Ч", 177 | ['C'] = "С", 178 | ['V'] = "М", 179 | ['B'] = "И", 180 | ['N'] = "Т", 181 | ['M'] = "Ь", 182 | ['<'] = "Б", 183 | ['>'] = "Ю", 184 | ['?'] = ",", 185 | ['|'] = "|", 186 | }; 187 | 188 | static char **kmaps[] = {kmap_en, kmap_fa, kmap_ru}; 189 | 190 | static char *digraphs[][2] = { 191 | /* digraphs */ 192 | {"cq", "’"}, 193 | {"pl", "+"}, 194 | {"hy", "-"}, 195 | {"sl", "/"}, 196 | {"eq", "="}, 197 | {"dq", "\""}, 198 | {"rs", "\\"}, 199 | {"ru", "_"}, 200 | {"ul", "_"}, 201 | {"oq", "‘"}, 202 | {"or", "|"}, 203 | {"!!", "¡"}, 204 | {"r!", "¡"}, 205 | {"c|", "¢"}, 206 | {"ct", "¢"}, 207 | {"L-", "£"}, 208 | {"ps", "£"}, 209 | {"xo", "¤"}, 210 | {"cr", "¤"}, 211 | {"Y-", "¥"}, 212 | {"yn", "¥"}, 213 | {"||", "¦"}, 214 | {"so", "§"}, 215 | {"sc", "§"}, 216 | {"co", "©"}, 217 | {"a_", "ª"}, 218 | {"<<", "«"}, 219 | {"Fo", "«"}, 220 | {"-,", "¬"}, 221 | {"no", "¬"}, 222 | {"ro", "®"}, 223 | {"rg", "®"}, 224 | {"0^", "°"}, 225 | {"de", "°"}, 226 | {"+-", "±"}, 227 | {"2^", "²"}, 228 | {"3^", "³"}, 229 | {"P!", "¶"}, 230 | {"pg", "¶"}, 231 | {".^", "·"}, 232 | {"1^", "¹"}, 233 | {"o_", "º"}, 234 | {">>", "»"}, 235 | {"Fc", "»"}, 236 | {"14", "¼"}, 237 | {"12", "½"}, 238 | {"34", "¾"}, 239 | {"??", "¿"}, 240 | {"r?", "¿"}, 241 | {"A`", "À"}, 242 | {"A'", "Á"}, 243 | {"A^", "Â"}, 244 | {"A~", "Ã"}, 245 | {"A-", "Ā"}, 246 | {"A:", "Ä"}, 247 | {"A\"", "A:"}, 248 | {"A*", "Å"}, 249 | {"AE", "Æ"}, 250 | {"C,", "Ç"}, 251 | {"E`", "È"}, 252 | {"E'", "É"}, 253 | {"E^", "Ê"}, 254 | {"E-", "Ē"}, 255 | {"E:", "Ë"}, 256 | {"I`", "Ì"}, 257 | {"I'", "Í"}, 258 | {"I^", "Î"}, 259 | {"I-", "Ī"}, 260 | {"I:", "Ï"}, 261 | {"D-", "Ð"}, 262 | {"N~", "Ñ"}, 263 | {"O`", "Ò"}, 264 | {"O'", "Ó"}, 265 | {"O^", "Ô"}, 266 | {"O~", "Õ"}, 267 | {"O-", "Ō"}, 268 | {"O:", "Ö"}, 269 | {"xx", "×"}, 270 | {"mu", "×"}, 271 | {"O/", "Ø"}, 272 | {"U`", "Ù"}, 273 | {"U'", "Ú"}, 274 | {"U^", "Û"}, 275 | {"U-", "Ū"}, 276 | {"U:", "Ü"}, 277 | {"Y'", "Ý"}, 278 | {"TH", "Þ"}, 279 | {"ss", "ß"}, 280 | {"a`", "à"}, 281 | {"a'", "á"}, 282 | {"a^", "â"}, 283 | {"a-", "ā"}, 284 | {"a~", "ã"}, 285 | {"a:", "ä"}, 286 | {"a*", "å"}, 287 | {"ae", "æ"}, 288 | {"c,", "ç"}, 289 | {"e`", "è"}, 290 | {"e'", "é"}, 291 | {"e^", "ê"}, 292 | {"e:", "ë"}, 293 | {"e-", "ē"}, 294 | {"i`", "ì"}, 295 | {"i'", "í"}, 296 | {"i^", "î"}, 297 | {"i-", "ī"}, 298 | {"i:", "ï"}, 299 | {"d-", "ð"}, 300 | {"n~", "ñ"}, 301 | {"o`", "ò"}, 302 | {"o'", "ó"}, 303 | {"o^", "ô"}, 304 | {"o~", "õ"}, 305 | {"o-", "ō"}, 306 | {"o:", "ö"}, 307 | {"di", "÷"}, 308 | {"-:", "÷"}, 309 | {"o/", "ø"}, 310 | {"u`", "ù"}, 311 | {"u'", "ú"}, 312 | {"u^", "û"}, 313 | {"u-", "ū"}, 314 | {"u:", "ü"}, 315 | {"y'", "ý"}, 316 | {"y-", "ȳ"}, 317 | {"th", "þ"}, 318 | {"y:", "ÿ"}, 319 | {"C<", "Č"}, 320 | {"c<", "č"}, 321 | {"D<", "Ď"}, 322 | {"d<", "ď"}, 323 | {"e<", "ě"}, 324 | {"n<", "ň"}, 325 | {"O\"", "Ő"}, 326 | {"o\"", "ő"}, 327 | {"R<", "Ř"}, 328 | {"r<", "ř"}, 329 | {"S<", "Š"}, 330 | {"s<", "š"}, 331 | {"T<", "Ť"}, 332 | {"t<", "ť"}, 333 | {"U*", "Ů"}, 334 | {"u*", "ů"}, 335 | {"U\"", "Ű"}, 336 | {"u\"", "ű"}, 337 | {"Y:", "Ÿ"}, 338 | {"Y-", "Ȳ"}, 339 | {"Z<", "Ž"}, 340 | {"z<", "ž"}, 341 | {"fn", "ƒ"}, 342 | {",,", "¸"}, 343 | {",a", "¸"}, 344 | {"aa", "´"}, 345 | {"\\'", "´"}, 346 | {"-a", "¯"}, 347 | {"\"\"", "¨"}, 348 | {":a", "¨"}, 349 | {"^", "ˆ"}, 350 | {"^a", "ˆ"}, 351 | {"va", "ˇ"}, 352 | {"Ua", "˘"}, 353 | {".a", "˙"}, 354 | {"oa", "˚"}, 355 | {"Ca", "˛"}, 356 | {"\"a", "˝"}, 357 | {"~", "˜"}, 358 | {"*A", "Α"}, 359 | {"*B", "Β"}, 360 | {"*G", "Γ"}, 361 | {"*E", "Ε"}, 362 | {"*Z", "Ζ"}, 363 | {"*Y", "Η"}, 364 | {"*H", "Θ"}, 365 | {"*I", "Ι"}, 366 | {"*K", "Κ"}, 367 | {"*L", "Λ"}, 368 | {"*M", "Μ"}, 369 | {"*N", "Ν"}, 370 | {"*C", "Ξ"}, 371 | {"*O", "Ο"}, 372 | {"*P", "Π"}, 373 | {"*R", "Ρ"}, 374 | {"*S", "Σ"}, 375 | {"*T", "Τ"}, 376 | {"*U", "Υ"}, 377 | {"*F", "Φ"}, 378 | {"*X", "Χ"}, 379 | {"*Q", "Ψ"}, 380 | {"*W", "Ω"}, 381 | {"*a", "α"}, 382 | {"*b", "β"}, 383 | {"*g", "γ"}, 384 | {"*d", "δ"}, 385 | {"*e", "ε"}, 386 | {"*z", "ζ"}, 387 | {"*y", "η"}, 388 | {"*h", "θ"}, 389 | {"*i", "ι"}, 390 | {"*k", "κ"}, 391 | {"*l", "λ"}, 392 | {"*m", "μ"}, 393 | {"/u", "µ"}, 394 | {"*n", "ν"}, 395 | {"*c", "ξ"}, 396 | {"*o", "ο"}, 397 | {"*p", "π"}, 398 | {"*r", "ρ"}, 399 | {"ts", "ς"}, 400 | {"*s", "σ"}, 401 | {"*t", "τ"}, 402 | {"*u", "υ"}, 403 | {"*f", "φ"}, 404 | {"*x", "χ"}, 405 | {"*q", "ψ"}, 406 | {"*w", "ω"}, 407 | {"en", "–"}, 408 | {"\\-", "–"}, 409 | {"em", "—"}, 410 | {"--", "—"}, 411 | {"bq", "‚"}, 412 | {"``", "“"}, 413 | {"lq", "“"}, 414 | {"''", "”"}, 415 | {"rq", "”"}, 416 | {"dg", "†"}, 417 | {"dd", "‡"}, 418 | {"bu", "•"}, 419 | {"el", "…"}, 420 | {"%0", "‰"}, 421 | {"fm", "′"}, 422 | {"fo", "‹"}, 423 | {"fc", "›"}, 424 | {"fr", "⁄"}, 425 | {"If", "ℑ"}, 426 | {"ws", "ℛ"}, 427 | {"Rf", "ℜ"}, 428 | {"af", "ℵ"}, 429 | {"<-", "←"}, 430 | {"ua", "↑"}, 431 | {"->", "→"}, 432 | {"da", "↓"}, 433 | {"<>", "↔"}, 434 | {"ab", "↔"}, 435 | {"CR", "↵"}, 436 | {"fa", "∀"}, 437 | {"pd", "∂"}, 438 | {"te", "∃"}, 439 | {"es", "∅"}, 440 | {"*D", "Δ"}, 441 | {"gr", "∇"}, 442 | {"mo", "∈"}, 443 | {"!m", "∉"}, 444 | {"st", "∋"}, 445 | {"pr", "∏"}, 446 | {"su", "∑"}, 447 | {"mi", "−"}, 448 | {"-+", "∓"}, 449 | {"**", "∗"}, 450 | {"sr", "√"}, 451 | {"pt", "∝"}, 452 | {"if", "∞"}, 453 | {"an", "∠"}, 454 | {"l&", "∧"}, 455 | {"l|", "∨"}, 456 | {"ca", "∩"}, 457 | {"cu", "∪"}, 458 | {"is", "∫"}, 459 | {"tf", "∴"}, 460 | {"ap", "∼"}, 461 | {"cg", "≅"}, 462 | {"=~", "≅"}, 463 | {"~~", "≈"}, 464 | {"!=", "≠"}, 465 | {"==", "≡"}, 466 | {"<=", "≤"}, 467 | {">=", "≥"}, 468 | {"sb", "⊂"}, 469 | {"sp", "⊃"}, 470 | {"!b", "⊄"}, 471 | {"ib", "⊆"}, 472 | {"ip", "⊇"}, 473 | {"O+", "⊕"}, 474 | {"Ox", "⊗"}, 475 | {"pp", "⊥"}, 476 | {"c.", "⋅"}, 477 | {"b<", "〈"}, 478 | {"b>", "〉"}, 479 | {"lz", "◊"}, 480 | {"ci", "○"}, 481 | {"la", "⟨"}, 482 | {"ra", "⟩"}, 483 | {"co", ""}, 484 | {"rg", ""}, 485 | {"tm", ""}, 486 | {"rn", ""}, 487 | {"av", ""}, 488 | {"ah", ""}, 489 | {"RG", ""}, 490 | {"CO", ""}, 491 | {"TM", ""}, 492 | {"LT", ""}, 493 | {"br", ""}, 494 | {"LX", ""}, 495 | {"LB", ""}, 496 | {"LT", "⎛"}, 497 | {"LX", "⎜"}, 498 | {"LB", "⎝"}, 499 | {"lc", ""}, 500 | {"lx", ""}, 501 | {"lf", ""}, 502 | {"lc", "⎡"}, 503 | {"lx", "⎢"}, 504 | {"lf", "⎣"}, 505 | {"lt", ""}, 506 | {"lk", ""}, 507 | {"lb", ""}, 508 | {"lt", "⎧"}, 509 | {"lk", "⎨"}, 510 | {"lb", "⎩"}, 511 | {"bv", "⎪"}, 512 | {"RT", ""}, 513 | {"RX", ""}, 514 | {"RB", ""}, 515 | {"RT", "⎞"}, 516 | {"RX", "⎟"}, 517 | {"RB", "⎠"}, 518 | {"rc", ""}, 519 | {"rx", ""}, 520 | {"rf", ""}, 521 | {"rc", "⎤"}, 522 | {"rx", "⎥"}, 523 | {"rf", "⎦"}, 524 | {"rt", ""}, 525 | {"rk", ""}, 526 | {"rb", ""}, 527 | {"rt", "⎫"}, 528 | {"rk", "⎬"}, 529 | {"rb", "⎭"}, 530 | {"ff", "ff"}, 531 | {"fi", "fi"}, 532 | {"fl", "fl"}, 533 | {"ffi", "ffi"}, 534 | {"Fi", "ffi"}, 535 | {"ffl", "ffl"}, 536 | {"Fl", "ffl"}, 537 | }; 538 | -------------------------------------------------------------------------------- /lbuf.c: -------------------------------------------------------------------------------- 1 | struct lbuf *lbuf_make(void) 2 | { 3 | struct lbuf *lb = emalloc(sizeof(*lb)); 4 | int i; 5 | memset(lb, 0, sizeof(*lb)); 6 | for (i = 0; i < LEN(lb->mark); i++) 7 | lb->mark[i] = -1; 8 | lb->useq = 1; 9 | return lb; 10 | } 11 | 12 | static void lopt_done(struct lopt *lo) 13 | { 14 | free(lo->ins); 15 | free(lo->del); 16 | free(lo->mark); 17 | free(lo->mark_off); 18 | } 19 | 20 | static void lbuf_savemark(struct lbuf *lb, struct lopt *lo, int m1, int m2) 21 | { 22 | lo->mark[m1] = lb->mark[m2]; 23 | lo->mark_off[m1] = lb->mark_off[m2]; 24 | } 25 | 26 | static void lbuf_loadmark(struct lbuf *lb, struct lopt *lo, int m1, int m2) 27 | { 28 | if (lo->mark[m2] >= 0) { 29 | lb->mark[m1] = lo->mark[m2]; 30 | lb->mark_off[m1] = lo->mark_off[m2]; 31 | } 32 | } 33 | 34 | static int markidx(int mark) 35 | { 36 | if (mark == '\'' || mark == '`') 37 | return 'z' - 'a' + 1; 38 | if (mark == '*') 39 | return 'z' - 'a' + 2; 40 | if (mark == '[') 41 | return 'z' - 'a' + 3; 42 | if (mark == ']') 43 | return 'z' - 'a' + 4; 44 | if (islower(mark)) 45 | return mark - 'a'; 46 | return -1; 47 | } 48 | 49 | static void lbuf_loadpos(struct lbuf *lb, struct lopt *lo) 50 | { 51 | lb->mark[markidx('*')] = lo->pos; 52 | lb->mark_off[markidx('*')] = lo->pos_off; 53 | } 54 | 55 | void lbuf_free(struct lbuf *lb) 56 | { 57 | int i; 58 | for (i = 0; i < lb->ln_n; i++) 59 | free(lbuf_i(lb, i)); 60 | for (i = 0; i < lb->hist_n; i++) 61 | lopt_done(&lb->hist[i]); 62 | free(lb->hist); 63 | free(lb->ln); 64 | free(lb); 65 | } 66 | 67 | static int linelength(char *s) 68 | { 69 | int len = dstrlen(s, '\n'); 70 | return s[len] == '\n' ? len + 1 : len; 71 | } 72 | 73 | /* low-level line replacement */ 74 | static int lbuf_replace(struct lbuf *lb, char *s, struct lopt *lo, int n_del) 75 | { 76 | int i, n_ins = 0, pos = lo->pos; 77 | sbuf_smake(sb, 0) 78 | if (s) { 79 | for (; *s; n_ins++) { 80 | int l = linelength(s); 81 | int l_nonl = l - (s[l - !!l] == '\n'); 82 | struct linfo *n = emalloc(l_nonl + 7 + sizeof(struct linfo)); 83 | n->len = l_nonl; 84 | n->grec = 0; 85 | char *ln = (char*)(n + 1); 86 | memcpy(ln, s, l_nonl); 87 | memset(&ln[l_nonl + 1], 0, 5); /* fault tolerance pad */ 88 | ln[l_nonl] = '\n'; 89 | sbuf_mem(sb, &ln, (int)sizeof(s)) 90 | s += l; 91 | } 92 | } 93 | for (i = 0; i < n_del; i++) 94 | free(lbuf_i(lb, pos + i)); 95 | rstate->s = NULL; /* there is no guarantee malloc not giving same ptr back */ 96 | if (lb->ln_n + n_ins - n_del >= lb->ln_sz) { 97 | int nsz = lb->ln_n + n_ins - n_del + 512; 98 | char **nln = emalloc(nsz * sizeof(lb->ln[0])); 99 | memcpy(nln, lb->ln, lb->ln_n * sizeof(lb->ln[0])); 100 | free(lb->ln); 101 | lb->ln = nln; 102 | lb->ln_sz = nsz; 103 | } 104 | if (n_ins != n_del) { 105 | memmove(lb->ln + pos + n_ins, lb->ln + pos + n_del, 106 | (lb->ln_n - pos - n_del) * sizeof(lb->ln[0])); 107 | } 108 | lb->ln_n += n_ins - n_del; 109 | for (i = 0; i < n_ins; i++) 110 | lb->ln[pos + i] = *((char**)sb->s + i); 111 | for (i = 0; i < NMARKS_BASE; i++) { /* updating marks */ 112 | if (!s && lb->mark[i] >= pos && lb->mark[i] < pos + n_del) { 113 | lbuf_savemark(lb, lo, i, i); 114 | lb->mark[i] = -1; 115 | continue; 116 | } else if (lb->mark[i] >= pos + n_del) 117 | lb->mark[i] += n_ins - n_del; 118 | else if (n_ins && lb->mark[i] >= pos + n_ins) 119 | lb->mark[i] = pos + n_ins - 1; 120 | else 121 | lbuf_loadmark(lb, lo, i, i); 122 | } 123 | free(sb->s); 124 | return n_ins; 125 | } 126 | 127 | void lbuf_emark(struct lbuf *lb, struct lopt *lo, int beg, int end) 128 | { 129 | lbuf_savemark(lb, lo, markidx(']'), markidx(']')); 130 | lbuf_mark(lb, ']', end, lo->pos_off); 131 | if (beg >= 0) { 132 | lbuf_savemark(lb, lo, markidx('['), markidx('[')); 133 | lbuf_mark(lb, '[', beg, lo->pos_off); 134 | } 135 | } 136 | 137 | /* append undo/redo history */ 138 | struct lopt *lbuf_opt(struct lbuf *lb, char *buf, int pos, int n_del, int init) 139 | { 140 | struct lopt *lo; 141 | int i; 142 | for (i = lb->hist_u; i < lb->hist_n; i++) 143 | lopt_done(&lb->hist[i]); 144 | lb->hist_n = lb->hist_u; 145 | if (lb->hist_n == lb->hist_sz) { 146 | int sz = lb->hist_sz + (lb->hist_sz ? lb->hist_sz : 128); 147 | struct lopt *hist = emalloc(sz * sizeof(hist[0])); 148 | memcpy(hist, lb->hist, lb->hist_n * sizeof(hist[0])); 149 | free(lb->hist); 150 | lb->hist = hist; 151 | lb->hist_sz = sz; 152 | } 153 | lo = &lb->hist[lb->hist_n++]; 154 | lb->hist_u = lb->hist_n; 155 | lo->ins = !init && buf ? uc_dup(buf) : NULL; 156 | lo->del = n_del ? lbuf_cp(lb, pos, pos + n_del) : NULL; 157 | lo->pos = pos; 158 | lo->n_ins = 0; 159 | lo->n_del = n_del; 160 | lo->pos_off = lb->mark[markidx('*')] >= 0 ? lb->mark_off[markidx('*')] : 0; 161 | lo->seq = lb->useq; 162 | lo->mark = emalloc(sizeof(lb->mark)); 163 | lo->mark_off = emalloc(sizeof(lb->mark_off)); 164 | memset(lo->mark, 0xff, sizeof(lb->mark)); 165 | return lo; 166 | } 167 | 168 | int lbuf_rd(struct lbuf *lbuf, int fd, int beg, int end, int init) 169 | { 170 | long nr; 171 | sbuf_smake(sb, 1048575) /* caps at 2147481600 on 32 bit */ 172 | while ((nr = read(fd, sb->s + sb->s_n, sb->s_sz - sb->s_n)) > 0) { 173 | sb->s_n += nr; 174 | if (sb->s_n >= sb->s_sz) { 175 | if (sb->s_n > INT_MAX / 2) 176 | break; 177 | sbuf_extend(sb, sb->s_n * 2) 178 | } 179 | } 180 | sbuf_null(sb) 181 | lbuf_iedit(lbuf, sb->s, beg, end, init); 182 | free(sb->s); 183 | return nr != 0; 184 | } 185 | 186 | int lbuf_wr(struct lbuf *lbuf, int fd, int beg, int end) 187 | { 188 | for (int i = beg; i < end; i++) { 189 | char *ln = lbuf->ln[i]; 190 | long nw = 0; 191 | long nl = lbuf_s(ln)->len + 1; 192 | while (nw < nl) { 193 | long nc = write(fd, ln + nw, nl - nw); 194 | if (nc < 0) 195 | return 1; 196 | nw += nc; 197 | } 198 | } 199 | return 0; 200 | } 201 | 202 | /* replace lines beg through end with buf */ 203 | void lbuf_iedit(struct lbuf *lb, char *buf, int beg, int end, int init) 204 | { 205 | if (beg > lb->ln_n) 206 | beg = lb->ln_n; 207 | if (end > lb->ln_n) 208 | end = lb->ln_n; 209 | if (beg == end && !buf) 210 | return; 211 | struct lopt *lo = lbuf_opt(lb, buf, beg, end - beg, init); 212 | lo->n_ins = lbuf_replace(lb, buf, lo, lo->n_del); 213 | lbuf_emark(lb, lo, lb->hist_u < 2 || 214 | lb->hist[lb->hist_u - 2].seq != lb->useq ? beg : -1, 215 | beg + (lo->n_ins ? lo->n_ins - 1 : 0)); 216 | } 217 | 218 | char *lbuf_cp(struct lbuf *lb, int beg, int end) 219 | { 220 | sbuf_smake(sb, 64) 221 | for (int i = beg; i < end; i++) 222 | if (i < lb->ln_n) 223 | sbuf_str(sb, lb->ln[i]) 224 | sbufn_sret(sb) 225 | } 226 | 227 | char *lbuf_get(struct lbuf *lb, int pos) 228 | { 229 | return pos >= 0 && pos < lb->ln_n ? lb->ln[pos] : NULL; 230 | } 231 | 232 | void lbuf_mark(struct lbuf *lbuf, int mark, int pos, int off) 233 | { 234 | if (markidx(mark) >= 0) { 235 | lbuf->mark[markidx(mark)] = pos; 236 | lbuf->mark_off[markidx(mark)] = off; 237 | } 238 | } 239 | 240 | int lbuf_jump(struct lbuf *lbuf, int mark, int *pos, int *off) 241 | { 242 | int mk = markidx(mark); 243 | if (mk < 0 || lbuf->mark[mk] < 0) 244 | return 1; 245 | *pos = lbuf->mark[mk]; 246 | if (off) 247 | *off = lbuf->mark_off[mk]; 248 | return 0; 249 | } 250 | 251 | int lbuf_undo(struct lbuf *lb) 252 | { 253 | if (!lb->hist_u) 254 | return 1; 255 | struct lopt *lo = &lb->hist[lb->hist_u - 1]; 256 | int useq = lo->seq; 257 | lbuf_savemark(lb, lo, markidx('*'), markidx('[')); 258 | while (lb->hist_u && lb->hist[lb->hist_u - 1].seq == useq) { 259 | lo = &lb->hist[--(lb->hist_u)]; 260 | lbuf_replace(lb, lo->del, lo, lo->n_ins); 261 | } 262 | lbuf_loadpos(lb, lo); 263 | lbuf_savemark(lb, lo, markidx('`'), markidx(']')); 264 | lbuf_loadmark(lb, lo, markidx('['), markidx('[')); 265 | lbuf_loadmark(lb, lo, markidx(']'), markidx(']')); 266 | return 0; 267 | } 268 | 269 | int lbuf_redo(struct lbuf *lb) 270 | { 271 | if (lb->hist_u == lb->hist_n) 272 | return 1; 273 | struct lopt *lo = &lb->hist[lb->hist_u]; 274 | int useq = lo->seq; 275 | lbuf_loadmark(lb, lo, markidx(']'), markidx('`')); 276 | while (lb->hist_u < lb->hist_n && lb->hist[lb->hist_u].seq == useq) { 277 | lo = &lb->hist[lb->hist_u++]; 278 | lbuf_replace(lb, lo->ins, lo, lo->n_del); 279 | } 280 | lbuf_loadpos(lb, lo); 281 | lbuf_loadmark(lb, lo, markidx('['), markidx('*')); 282 | return 0; 283 | } 284 | 285 | static int lbuf_seq(struct lbuf *lb) 286 | { 287 | return lb->hist_u ? lb->hist[lb->hist_u - 1].seq : lb->useq_last; 288 | } 289 | 290 | /* mark buffer as saved and, if clear, clear the undo history */ 291 | void lbuf_saved(struct lbuf *lb, int clear) 292 | { 293 | int i; 294 | if (clear) { 295 | for (i = 0; i < lb->hist_n; i++) 296 | lopt_done(&lb->hist[i]); 297 | lb->hist_n = 0; 298 | lb->hist_u = 0; 299 | lb->useq_last = lb->useq; 300 | } 301 | lb->useq_zero = lbuf_seq(lb); 302 | lbuf_modified(xb); 303 | } 304 | 305 | /* was the file modified since the last reset */ 306 | int lbuf_modified(struct lbuf *lb) 307 | { 308 | lb->useq++; 309 | return lbuf_seq(lb) != lb->useq_zero; 310 | } 311 | 312 | int lbuf_indents(struct lbuf *lb, int r) 313 | { 314 | char *ln = lbuf_get(lb, r); 315 | int o; 316 | if (!ln) 317 | return 0; 318 | for (o = 0; uc_isspace(ln); o++) 319 | ln += uc_len(ln); 320 | return *ln ? o : o - 2; 321 | } 322 | 323 | static int uc_nextdir(char **s, char *beg, int dir) 324 | { 325 | if (dir < 0) { 326 | if (*s == beg) 327 | return 1; 328 | *s = uc_beg(beg, (*s) - 1); 329 | } else { 330 | *s += uc_len(s[0]); 331 | if (!(*s)[0]) 332 | return 1; 333 | } 334 | return 0; 335 | } 336 | 337 | int lbuf_findchar(struct lbuf *lb, char *cs, int cmd, int n, int *row, int *off) 338 | { 339 | char *ln = lbuf_get(lb, *row); 340 | char *s; 341 | int c1, c2, l, dir = (cmd == 'f' || cmd == 't') ? +1 : -1; 342 | if (!ln) 343 | return 1; 344 | if (n < 0) 345 | dir = -dir; 346 | if (n < 0) 347 | n = -n; 348 | s = uc_chr(ln, *off); 349 | while (n > 0 && !uc_nextdir(&s, ln, dir)) { 350 | uc_code(c1, s, l) 351 | uc_code(c2, cs, l) 352 | if (c1 == c2) 353 | n--; 354 | } 355 | if (!n && (cmd == 't' || cmd == 'T')) 356 | uc_nextdir(&s, ln, -dir); 357 | if (!n) 358 | *off = uc_off(ln, s - ln); 359 | return n != 0; 360 | } 361 | 362 | int lbuf_search(struct lbuf *lb, rset *re, int dir, int *r, 363 | int *o, int ln_n, int skip) 364 | { 365 | int r0 = *r, o0 = *o; 366 | int offs[re->grpcnt * 2], i = r0; 367 | char *s = lbuf_get(lb, i); 368 | int off = skip >= 0 && *uc_chr(s, o0 + skip) ? uc_chr(s, o0 + skip) - s : 0; 369 | for (; i >= 0 && i < ln_n; i += dir) { 370 | s = lb->ln[i]; 371 | while (rset_find(re, s + off, offs, 372 | off ? REG_NOTBOL | REG_NEWLINE : REG_NEWLINE) >= 0) { 373 | int g1 = offs[xgrp], g2 = offs[xgrp + 1]; 374 | if (g1 < 0) { 375 | off += offs[1] > 0 ? offs[1] : 1; 376 | continue; 377 | } 378 | if (dir < 0 && r0 == i && uc_off(s, off+g1) >= o0) 379 | break; 380 | *o = uc_off(s, off + g1); 381 | *r = i; 382 | off += g2 > 0 ? g2 : 1; 383 | if (dir > 0) 384 | return 0; 385 | ln_n = -1; /* break outer loop efficiently */ 386 | } 387 | off = 0; 388 | } 389 | return ln_n < 0 ? 0 : 1; 390 | } 391 | 392 | int lbuf_sectionbeg(struct lbuf *lb, int dir, int *row, int *off, int ch) 393 | { 394 | if (ch == '\n') 395 | while (*row >= 0 && *row < lbuf_len(lb) && *lbuf_get(lb, *row) == ch) 396 | *row += dir; 397 | else 398 | *row += dir; 399 | while (*row >= 0 && *row < lbuf_len(lb) && *lbuf_get(lb, *row) != ch) 400 | *row += dir; 401 | *row = MAX(0, MIN(*row, lbuf_len(lb) - 1)); 402 | *off = 0; 403 | return 0; 404 | } 405 | 406 | int lbuf_eol(struct lbuf *lb, int row) 407 | { 408 | int len = 0; 409 | if (lbuf_get(lb, row)) 410 | len = ren_position(lbuf_get(lb, row))->n; 411 | return len ? len - 1 : len; 412 | } 413 | 414 | static int lbuf_next(struct lbuf *lb, int dir, int *r, int *o) 415 | { 416 | int odir = dir > 0 ? 1 : -1; 417 | int len, off = *o + odir; 418 | if (lbuf_get(lb, *r)) 419 | len = ren_position(lbuf_get(lb, *r))->n; 420 | else 421 | return -1; 422 | if (off < 0 || off >= len) { 423 | if (dir % 2 == 0 || !lbuf_get(lb, *r + odir)) 424 | return -1; 425 | *r += odir; 426 | if (odir > 0) { 427 | ren_position(lbuf_get(lb, *r)); 428 | *o = 0; 429 | } else 430 | *o = lbuf_eol(lb, *r); 431 | } else 432 | *o = off; 433 | return 0; 434 | } 435 | 436 | /* move to the last character of the word */ 437 | static int lbuf_wordlast(struct lbuf *lb, int kind, int dir, int *row, int *off) 438 | { 439 | if (!kind || !(uc_kind(rstate->chrs[*off]) & kind)) 440 | return 0; 441 | while (uc_kind(rstate->chrs[*off]) & kind) 442 | if (lbuf_next(lb, dir, row, off)) 443 | return 1; 444 | if (!(uc_kind(rstate->chrs[*off]) & kind)) 445 | lbuf_next(lb, -dir, row, off); 446 | return 0; 447 | } 448 | 449 | int lbuf_wordbeg(struct lbuf *lb, int big, int dir, int *row, int *off) 450 | { 451 | int nl; 452 | if (!lbuf_get(lb, *row)) 453 | return 1; 454 | ren_state *r = ren_position(lbuf_get(lb, *row)); 455 | lbuf_wordlast(lb, big ? 3 : uc_kind(r->chrs[*off]), dir, row, off); 456 | nl = *rstate->chrs[*off] == '\n'; 457 | if (lbuf_next(lb, dir, row, off)) 458 | return 1; 459 | while (uc_isspace(rstate->chrs[*off])) { 460 | nl += *rstate->chrs[*off] == '\n'; 461 | if (nl == 2) 462 | return 0; 463 | if (lbuf_next(lb, dir, row, off)) 464 | return 1; 465 | } 466 | return 0; 467 | } 468 | 469 | int lbuf_wordend(struct lbuf *lb, int big, int dir, int *row, int *off) 470 | { 471 | int nl = 0; 472 | if (!lbuf_get(lb, *row)) 473 | return 1; 474 | ren_state *r = ren_position(lbuf_get(lb, *row)); 475 | if (!uc_isspace(r->chrs[*off])) { 476 | if (lbuf_next(lb, dir, row, off)) 477 | return 1; 478 | nl = dir < 0 && *rstate->chrs[*off] == '\n'; 479 | } 480 | nl += dir > 0 && *rstate->chrs[*off] == '\n'; 481 | while (uc_isspace(rstate->chrs[*off])) { 482 | if (lbuf_next(lb, dir, row, off)) 483 | return 1; 484 | nl += *rstate->chrs[*off] == '\n'; 485 | if (nl == 2) { 486 | if (dir < 0) 487 | lbuf_next(lb, -dir, row, off); 488 | return 0; 489 | } 490 | } 491 | lbuf_wordlast(lb, big ? 3 : uc_kind(rstate->chrs[*off]), dir, row, off); 492 | return 0; 493 | } 494 | 495 | /* move to the matching character */ 496 | int lbuf_pair(struct lbuf *lb, int *row, int *off) 497 | { 498 | int r = *row, o = *off; 499 | char *pairs = "()[]{}"; 500 | int p, c, dep = 1; 501 | if (!lbuf_get(lb, r)) 502 | return 1; 503 | ren_state *rs = ren_position(lbuf_get(lb, r)); 504 | for (; o < rs->n-1 && !memchr(pairs, *rs->chrs[o], 6); o++); 505 | if (!memchr(pairs, *rs->chrs[o], 6)) 506 | return 1; 507 | p = (char*)memchr(pairs, *rs->chrs[o], 6) - pairs; 508 | while (!lbuf_next(lb, (p & 1) ? -1 : +1, &r, &o)) { 509 | c = *rstate->chrs[o]; 510 | if (c == pairs[p ^ 1]) 511 | dep--; 512 | if (c == pairs[p]) 513 | dep++; 514 | if (!dep) { 515 | *row = r; 516 | *off = o; 517 | return 0; 518 | } 519 | } 520 | return 1; 521 | } 522 | -------------------------------------------------------------------------------- /led.c: -------------------------------------------------------------------------------- 1 | /* line editing and drawing */ 2 | 3 | static sbuf *suggestsb; 4 | static sbuf *acsb; 5 | sbuf *led_attsb; 6 | 7 | int dstrlen(const char *s, char delim) 8 | { 9 | register const char *i; 10 | for (i=s; *i && *i != delim; ++i); 11 | return i-s; 12 | } 13 | 14 | static int search(const char *pattern, int l) 15 | { 16 | if (!*pattern) 17 | return 0; 18 | sbuf_cut(suggestsb, 0) 19 | sbuf_smake(sylsb, 1024) 20 | char *part = strstr(acsb->s, pattern); 21 | while (part) { 22 | char *part1 = part; 23 | while (*part != '\n') 24 | part--; 25 | int len = dstrlen(++part, '\n'); 26 | if (len++ != l) { 27 | if (part == part1) 28 | sbuf_mem(suggestsb, part, len) 29 | else 30 | sbuf_mem(sylsb, part, len) 31 | } 32 | part = strstr(part+len, pattern); 33 | } 34 | sbuf_mem(suggestsb, sylsb->s, sylsb->s_n) 35 | free(sylsb->s); 36 | sbuf_set(suggestsb, '\0', 4) 37 | suggestsb->s_n -= 4; 38 | return suggestsb->s_n; 39 | } 40 | 41 | static void file_index(struct lbuf *buf) 42 | { 43 | char reg[] = "[^\t ;:,`.<>[\\]\\^%$#@*!?+\\-|/=\\\\{}&\\()'\"]+"; 44 | int len, sidx, grp = xgrp; 45 | char **ss = buf->ln; 46 | int ln_n = lbuf_len(buf), n; 47 | rset *rs = rset_smake(xacreg ? xacreg->s : reg, xic ? REG_ICASE : 0); 48 | if (!rs) 49 | return; 50 | int subs[rs->grpcnt * 2]; 51 | sbuf_smake(ibuf, 1024) 52 | for (n = 1; n <= acsb->s_n; n++) 53 | if (acsb->s[n - 1] == '\n') 54 | sbuf_mem(ibuf, &n, (int)sizeof(n)) 55 | for (int i = 0; i < ln_n; i++) { 56 | sidx = 0; 57 | while (rset_find(rs, ss[i]+sidx, subs, 58 | sidx ? REG_NOTBOL | REG_NEWLINE : REG_NEWLINE) >= 0) { 59 | /* if target group not found, continue with group 1 60 | which will always be valid, otherwise there be no match */ 61 | if (subs[grp] < 0) { 62 | sidx += subs[1] > 0 ? subs[1] : 1; 63 | continue; 64 | } 65 | len = subs[grp + 1] - subs[grp]; 66 | if (len > 1) { 67 | char *part = ss[i]+sidx+subs[grp]; 68 | int *ip = (int*)(ibuf->s+sizeof(n)); 69 | for (n = len+1; ip < (int*)&ibuf->s[ibuf->s_n]; ip++) 70 | if (*ip - ip[-1] == n && 71 | !memcmp(acsb->s + ip[-1], part, len)) 72 | goto skip; 73 | sbuf_mem(acsb, part, len) 74 | sbuf_chr(acsb, '\n') 75 | sbuf_mem(ibuf, &acsb->s_n, (int)sizeof(n)) 76 | } 77 | skip: 78 | sidx += subs[grp + 1] > 0 ? subs[grp + 1] : 1; 79 | } 80 | } 81 | sbuf_null(acsb) 82 | free(ibuf->s); 83 | rset_free(rs); 84 | } 85 | 86 | static char *kmap_map(int kmap, int c) 87 | { 88 | static char cs[4]; 89 | char **keymap = conf_kmap(kmap); 90 | cs[0] = c; 91 | return keymap[c] ? keymap[c] : cs; 92 | } 93 | 94 | /* map cursor horizontal position to terminal column number */ 95 | int led_pos(char *s, int pos) 96 | { 97 | if (dir_context(s) < 0) 98 | return xleft + xcols - pos - 1; 99 | return pos - xleft; 100 | } 101 | 102 | #define print_ch1(out) sbuf_mem(out, chrs[o], l) 103 | #define print_ch2(out) sbuf_mem(out, *chrs[o] == ' ' ? "_" : chrs[o], l) 104 | 105 | #define hid_ch1(out) sbuf_set(out, ' ', i - l) 106 | #define hid_ch2(out) \ 107 | sbuf_set(out, *chrs[o] == '\n' ? '\\' : '-', i - l) \ 108 | if (ctx > 0 && *chrs[o] == '\t') \ 109 | out->s[out->s_n-1] = '>'; \ 110 | else if (*chrs[o] == '\t') \ 111 | out->s[out->s_n - (i - l)] = '<'; \ 112 | 113 | #define led_out(out, n) \ 114 | { \ 115 | for (i = 0; i < cterm;) { \ 116 | int att_new = 0; \ 117 | o = off[i]; \ 118 | if (o >= 0) { \ 119 | for (l = i; off[i] == o; i++); \ 120 | att_new = att[bound ? ctt[atti++] : o]; \ 121 | if (att_new != att_old) \ 122 | sbuf_str(out, term_att(att_new)) \ 123 | char *s = ren_translate(chrs[o], s0); \ 124 | if (s) \ 125 | sbuf_str(out, s) \ 126 | else if (uc_isprint(chrs[o])) { \ 127 | l = uc_len(chrs[o]); \ 128 | print_ch##n(out) \ 129 | } else { \ 130 | hid_ch##n(out) \ 131 | } \ 132 | } else { \ 133 | if (cbeg || ctx < 0) { \ 134 | if (att_new != att_old) \ 135 | sbuf_str(out, term_att(0)) \ 136 | sbuf_chr(out, ' ') \ 137 | i++; \ 138 | } else \ 139 | break; \ 140 | } \ 141 | att_old = att_new; \ 142 | } } \ 143 | 144 | /* render and highlight a line */ 145 | void led_render(char *s0, int cbeg, int cend) 146 | { 147 | if (!xled) 148 | return; 149 | ren_state *r = ren_position(s0); 150 | int j, c, l, i, o, n = r->n; 151 | int att_old = 0, atti = 0, cterm = cend - cbeg; 152 | char *bound = NULL; 153 | char **chrs = r->chrs; /* chrs[i]: the i-th character in s0 */ 154 | int off[cterm+1]; /* off[i]: the character at screen position i */ 155 | int att[cterm+1]; /* att[i]: the attributes of i-th character */ 156 | int stt[cterm+1]; /* stt[i]: remap off indexes */ 157 | int ctt[cterm+1]; /* ctt[i]: cterm bound attrs */ 158 | int ctx = r->ctx; 159 | off[cterm] = -1; 160 | if (ctx < 0) { 161 | o = cbeg; 162 | for (c = cterm-1; c >= 0; c--, o++) 163 | off[c] = o <= r->cmax ? r->col[o] : -1; 164 | } else { 165 | for (c = cbeg; c < cend; c++) 166 | off[c - cbeg] = c <= r->cmax ? r->col[c] : -1; 167 | } 168 | if (r->cmax > cterm || cbeg) { 169 | i = ctx < 0 ? cterm-1 : 0; 170 | o = off[i]; 171 | if (o >= 0 && cbeg && r->pos[o] < cbeg) 172 | while (off[i] == o) 173 | off[ctx < 0 ? i-- : i++] = -1; 174 | i = ctx < 0 ? 0 : cterm-1; 175 | o = off[i]; 176 | if (o >= 0 && r->cmax > cterm && r->pos[o] + r->wid[o] > cend) 177 | while (off[i] == o) 178 | off[ctx < 0 ? i++ : i--] = -1; 179 | for (i = 0, c = 0; i < cterm;) { 180 | if ((o = off[i++]) >= 0) { 181 | att[c++] = o; 182 | for (; off[i] == o; i++); 183 | } 184 | } 185 | stt[0] = 0; 186 | for (i = 1; i < c; i++) { 187 | int key0 = att[i]; 188 | j = i - 1; 189 | while (j >= 0 && att[j] > key0) { 190 | att[j + 1] = att[j]; 191 | stt[j + 1] = stt[j]; 192 | j = j - 1; 193 | } 194 | att[j + 1] = key0; 195 | stt[j + 1] = i; 196 | } 197 | sbuf_smake(bsb, cterm*4); 198 | for (i = 0; i < c; i++) { 199 | ctt[stt[i]] = i; 200 | stt[i] = att[i]; 201 | sbuf_mem(bsb, chrs[att[i]], uc_len(chrs[att[i]])) 202 | } 203 | sbuf_set(bsb, '\0', 4) 204 | bound = bsb->s; 205 | } 206 | memset(att, 0, MIN(n, cterm+1) * sizeof(att[0])); 207 | if (xhl) 208 | syn_highlight(att, bound ? bound : s0, MIN(n, cterm)); 209 | free(bound); 210 | if (led_attsb && xhl) { 211 | led_att *p = (led_att*)led_attsb->s; 212 | for (; (char*)p < &led_attsb->s[led_attsb->s_n]; p++) { 213 | if (p->s != s0) 214 | continue; 215 | if (!bound) 216 | att[p->off] = syn_merge(p->att, att[p->off]); 217 | else if (c && stt[0] <= p->off && stt[c-1] >= p->off) { 218 | i = p->off - stt[0]; 219 | if (i < c && stt[i] == p->off) { 220 | att[i] = syn_merge(p->att, att[i]); 221 | continue; /* text not reordered */ 222 | } 223 | for (l = 0, j = c - 1; l <= j;) { 224 | i = l + (j - l) / 2; 225 | if (stt[i] == p->off) { 226 | att[i] = syn_merge(p->att, att[i]); 227 | break; 228 | } else if (stt[i] < p->off) 229 | l = i + 1; 230 | else 231 | j = i - 1; 232 | } 233 | } 234 | } 235 | } 236 | if (xhlr && xhl) { 237 | for (l = 0, i = 0; i < cterm;) { 238 | o = off[i++]; 239 | if (o < 0) 240 | continue; 241 | for (l++; off[i] == o; i++); 242 | if (o+1 >= n || r->pos[o] + r->wid[o] == r->pos[o + 1]) 243 | continue; 244 | if (r->pos[o + 1] + r->wid[o + 1] != r->pos[o]) 245 | continue; 246 | j = bound ? ctt[l-1] : o; 247 | att[j] = syn_merge(conf_hlrev, att[j]); 248 | att[j+1] = syn_merge(conf_hlrev, att[j+1]); 249 | } 250 | } 251 | /* generate term output */ 252 | if (vi_hidch) 253 | led_out(term_sbuf, 2) 254 | else 255 | led_out(term_sbuf, 1) 256 | sbufn_str(term_sbuf, term_att(0)) 257 | } 258 | 259 | static int led_lastchar(char *s) 260 | { 261 | char *r = *s ? strchr(s, '\0') : s; 262 | if (r != s) 263 | r = uc_beg(s, r - 1); 264 | return r - s; 265 | } 266 | 267 | static int led_lastword(char *s) 268 | { 269 | char *r = *s ? uc_beg(s, strchr(s, '\0') - 1) : s; 270 | int kind; 271 | while (r > s && uc_isspace(r)) 272 | r = uc_beg(s, r - 1); 273 | kind = r > s ? uc_kind(r) : 0; 274 | while (r > s && uc_kind(uc_beg(s, r - 1)) == kind) 275 | r = uc_beg(s, r - 1); 276 | return r - s; 277 | } 278 | 279 | static void led_printparts(sbuf *sb, int pre, int ps, char *post, int ai_max) 280 | { 281 | if (!xled) { 282 | sbuf_null(sb) 283 | return; 284 | } 285 | int dir, off, pos, psn = sb->s_n; 286 | sbuf_str(sb, post) 287 | sbuf_set(sb, '\0', 4) 288 | rstate->s = NULL; 289 | ren_state *r = ren_position(sb->s + ps); 290 | off = r->n - uc_slen(post); 291 | if (ai_max >= 0) 292 | xoff = off; 293 | pos = ren_cursor(r->s, r->pos[MAX(0, off-1)]); 294 | if (off > 0) { 295 | int two = off > 1 && psn != pre; 296 | dir = r->pos[off-two] - r->pos[off-(two+1)]; 297 | if (abs(dir) > r->wid[off-(two+1)]) 298 | pos = ren_cursor(r->s, r->pos[off-two]); 299 | pos += dir < 0 ? -1 : 1; 300 | } 301 | if (pos >= xleft + xcols) 302 | xleft = pos - xcols / 2; 303 | if (pos < xleft) 304 | xleft = pos < xcols ? 0 : pos - xcols / 2; 305 | syn_blockhl = 0; 306 | led_crender(r->s, -1, vi_lncol, xleft, xleft + xcols - vi_lncol); 307 | term_pos(-1, led_pos(r->s, pos) + vi_lncol); 308 | sbufn_cut(sb, psn) 309 | } 310 | 311 | /* read a character from the terminal */ 312 | char *led_read(int *kmap, int c) 313 | { 314 | static char buf[32]; 315 | int c1, c2, i, n; 316 | while (!TK_INT(c)) { 317 | switch (c) { 318 | case TK_CTL('f'): 319 | *kmap = xkmap_alt; 320 | break; 321 | case TK_CTL('e'): 322 | *kmap = 0; 323 | break; 324 | case TK_CTL('v'): /* literal character */ 325 | buf[0] = term_read(); 326 | buf[1] = '\0'; 327 | return buf; 328 | case TK_CTL('k'): /* digraph */ 329 | c1 = term_read(); 330 | if (TK_INT(c1)) 331 | return NULL; 332 | c2 = term_read(); 333 | if (TK_INT(c2)) 334 | return NULL; 335 | return conf_digraph(c1, c2); 336 | default: 337 | if ((c & 0xc0) == 0xc0) { /* utf-8 character */ 338 | buf[0] = c; 339 | n = uc_len(buf); 340 | for (i = 1; i < n; i++) 341 | buf[i] = term_read(); 342 | buf[n] = '\0'; 343 | return buf; 344 | } 345 | return kmap_map(*kmap, c); 346 | } 347 | c = term_read(); 348 | } 349 | return NULL; 350 | } 351 | 352 | static void led_info(char *str, int ai_max) 353 | { 354 | led_recrender(str, xtop+xrows, 0, 0, xcols) 355 | if (ai_max >= 0) 356 | term_pos(xrow - xtop, 0); 357 | } 358 | 359 | static void led_redraw(char *cs, int r, int orow, int lsh) 360 | { 361 | for (int nl = 0; r < xrows; r++) { 362 | if (vi_lncol) { 363 | term_pos(r, 0); 364 | term_kill(); 365 | } 366 | if (r >= orow-xtop && r < xrow-xtop) { 367 | sbuf_smake(cb, 128) 368 | nl = dstrlen(cs, '\n'); 369 | sbuf_mem(cb, cs, nl+!!cs[nl]) 370 | sbuf_set(cb, '\0', 4) 371 | led_recrender(cb->s, r, vi_lncol, xleft, xleft + xcols - vi_lncol) 372 | free(cb->s); 373 | cs += nl+!!cs[nl]; 374 | continue; 375 | } 376 | nl = r < xrow-xtop ? r+xtop : (r-(xrow-orow+lsh))+xtop; 377 | led_crender(lbuf_get(xb, nl) ? lbuf_get(xb, nl) : "~", r, 378 | vi_lncol, xleft, xleft + xcols - vi_lncol); 379 | } 380 | term_pos(xrow - xtop, 0); 381 | } 382 | 383 | /* read a line from the terminal */ 384 | static void led_line(sbuf *sb, int ps, int pre, char *post, int ai_max, 385 | int *key, int *kmap, int orow, int lsh) 386 | { 387 | int len, t_row = -2, p_reg = 0; 388 | int c, i, lsug = 0, sug_pt = -1; 389 | char *cs, *sug = NULL, *_sug = NULL; 390 | while (1) { 391 | led_printparts(sb, pre, ps, post, ai_max); 392 | len = sb->s_n; 393 | c = term_read(); 394 | switch (c) { 395 | case TK_CTL('h'): 396 | case 127: 397 | if (len - pre > 0) 398 | sbuf_cut(sb, led_lastchar(sb->s + pre) + pre) 399 | else 400 | goto leave; 401 | break; 402 | case TK_CTL('u'): 403 | sbuf_cut(sb, sug_pt > pre && len > sug_pt ? sug_pt : pre) 404 | break; 405 | case TK_CTL('w'): 406 | if (len - pre > 0) 407 | sbuf_cut(sb, led_lastword(sb->s + pre) + pre) 408 | else if (ai_max >= 0) 409 | term_push("bdwi", 5); 410 | break; 411 | case TK_CTL('t'): 412 | cs = uc_dup(sb->s + ps); 413 | sbuf_cut(sb, ps) 414 | sbuf_chr(sb, '\t') 415 | sbuf_str(sb, cs) 416 | free(cs); 417 | pre++; 418 | break; 419 | case TK_CTL('d'): 420 | if (sb->s[ps] == ' ' || sb->s[ps] == '\t') { 421 | sbuf_cut(sb, ps) 422 | sbuf_str(sb, sb->s+ps+1) 423 | pre--; 424 | } 425 | break; 426 | case TK_CTL(']'): 427 | case TK_CTL('\\'): 428 | i = 0; 429 | retry: 430 | if (c == TK_CTL(']')) { 431 | if (!p_reg || p_reg == '9') 432 | p_reg = '/'; 433 | while (p_reg < '9' && !xregs[++p_reg]); 434 | } else { 435 | c = term_read(); 436 | p_reg = c == TK_CTL('\\') ? 0 : c; 437 | } 438 | if ((cs = xregs[p_reg])) { 439 | sbuf_chr(sb, p_reg ? p_reg : '~') 440 | sbuf_chr(sb, ' ') 441 | sbuf_str(sb, cs) 442 | sbuf_set(sb, '\0', 4) 443 | led_info(sb->s + len, ai_max); 444 | sbuf_cut(sb, len) 445 | } else if (!i++) 446 | goto retry; 447 | continue; 448 | case TK_CTL('p'): 449 | if (xregs[p_reg]) 450 | sbuf_str(sb, xregs[p_reg]) 451 | break; 452 | case TK_CTL('g'): 453 | if (!suggestsb) { 454 | sbuf_make(suggestsb, 1) 455 | sbuf_make(acsb, 1024) 456 | sbufn_chr(acsb, '\n') 457 | } 458 | file_index(xb); 459 | break; 460 | case TK_CTL('y'): 461 | led_done(); 462 | suggestsb = NULL; 463 | break; 464 | case TK_CTL('r'): 465 | if (!suggestsb || !suggestsb->s_n) 466 | continue; 467 | if (!sug) 468 | sug = suggestsb->s; 469 | if (suggestsb->s_n == sug - suggestsb->s) 470 | sug--; 471 | for (c = 0; sug != suggestsb->s; sug--) { 472 | if (!*sug) { 473 | c++; 474 | if (c == 3) { 475 | sug++; 476 | goto redo_suggest; 477 | } else 478 | *sug = '\n'; 479 | } 480 | } 481 | goto redo_suggest; 482 | case TK_CTL('z'): 483 | term_suspend(); 484 | if (ai_max >= 0) 485 | led_redraw(sb->s, 0, orow, lsh); 486 | continue; 487 | case TK_CTL('x'): 488 | sug_pt = sug_pt == len ? -1 : len; 489 | char buf[100]; 490 | itoa(sug_pt, buf); 491 | led_info(buf, ai_max); 492 | continue; 493 | case TK_CTL('n'): 494 | if (!suggestsb) 495 | continue; 496 | lsug = sug_pt >= 0 ? sug_pt : led_lastword(sb->s + pre) + pre; 497 | if (_sug) { 498 | if (suggestsb->s_n == sug - suggestsb->s) 499 | continue; 500 | redo_suggest: 501 | if (!(_sug = strchr(sug, '\n'))) { 502 | sug = suggestsb->s; 503 | goto lookup; 504 | } 505 | suggest: 506 | *_sug = '\0'; 507 | sbuf_cut(sb, lsug) 508 | sbuf_str(sb, sug) 509 | sug = _sug+1; 510 | continue; 511 | } 512 | lookup: 513 | if (search(sb->s + lsug, len - lsug)) { 514 | sug = suggestsb->s; 515 | if (!(_sug = strchr(sug, '\n'))) 516 | continue; 517 | goto suggest; 518 | } 519 | continue; 520 | case TK_CTL('b'): 521 | if (ai_max >= 0) { 522 | pac:; 523 | sbuf_null(sb) 524 | int r = xrow-xtop+1; 525 | if (sug) 526 | goto pac_; 527 | c = sug_pt >= 0 ? sug_pt : led_lastword(sb->s + pre) + pre; 528 | if (suggestsb && search(sb->s + c, sb->s_n - c)) { 529 | sug = suggestsb->s; 530 | pac_: 531 | syn_setft("/ac"); 532 | preserve(int, xtd, 2) 533 | for (int left = 0; r < xrows; r++) { 534 | led_crender(sug, r, 0, left, left+xcols) 535 | left += xcols; 536 | if (left >= rstate->pos[rstate->n]) 537 | break; 538 | } 539 | restore(xtd) 540 | syn_setft(ex_ft); 541 | r++; 542 | } 543 | led_redraw(sb->s, r, orow, lsh); 544 | continue; 545 | } 546 | lbuf_dedup(tempbufs[0].lb, sb->s + pre, sb->s_n - pre) 547 | temp_pos(0, -1, 0, 0); 548 | temp_write(0, sb->s + pre); 549 | preserve(struct buf*, ex_pbuf, ex_pbuf) 550 | preserve(struct buf*, ex_buf, ex_buf) 551 | preserve(int, texec, texec == '@' ? 0 : texec) 552 | preserve(int, xquit, 0) 553 | temp_switch(0); 554 | vi(1); 555 | temp_switch(0); 556 | restore(ex_pbuf) 557 | restore(ex_buf) 558 | restore(texec) 559 | exbuf_load(ex_buf) 560 | syn_setft(ex_ft); 561 | vi(1); /* redraw past screen */ 562 | syn_setft("/-"); 563 | term_pos(xrows, 0); 564 | if (xquit >= 0) 565 | restore(xquit) 566 | t_row = tempbufs[0].row; 567 | case TK_CTL('a'): 568 | t_row = t_row < -1 ? tempbufs[0].row : t_row; 569 | t_row += lbuf_len(tempbufs[0].lb); 570 | t_row = t_row % MAX(1, lbuf_len(tempbufs[0].lb)); 571 | if ((cs = lbuf_get(tempbufs[0].lb, t_row--))) { 572 | sbuf_cut(sb, pre) 573 | sbuf_str(sb, cs) 574 | sb->s_n--; 575 | } 576 | break; 577 | case TK_CTL('l'): 578 | if (ai_max < 0) 579 | term_clean(); 580 | else 581 | led_redraw(sb->s, 0, orow, lsh); 582 | continue; 583 | case TK_CTL('o'):; 584 | preserve(int, xvis, xvis & 4 ? xvis & ~4 : xvis | 4) 585 | syn_setft(ex_ft); 586 | if (xvis & 4) 587 | ex(); 588 | else 589 | vi(1); 590 | xquit = xquit > 0 ? 0 : xquit; 591 | restore(xvis) 592 | continue; 593 | default: 594 | if (c == '\n' || TK_INT(c)) 595 | goto leave; 596 | if ((cs = led_read(kmap, c))) 597 | sbuf_str(sb, cs) 598 | } 599 | sug = NULL; _sug = NULL; 600 | if (ai_max >= 0 && xpac) 601 | goto pac; 602 | } 603 | leave: 604 | vi_insmov = c; 605 | *key = c; 606 | } 607 | 608 | /* read an ex command */ 609 | char *led_prompt(char *pref, char *post, char *insert, int *kmap) 610 | { 611 | int key, n; 612 | sbuf_smake(sb, xcols) 613 | if (pref) 614 | sbuf_str(sb, pref) 615 | n = sb->s_n; 616 | if (insert) 617 | sbuf_str(sb, insert) 618 | preserve(int, xtd, +2) 619 | led_line(sb, 0, n, post, -1, &key, kmap, 0, 0); 620 | restore(xtd) 621 | if (key == '\n') { 622 | if (pref) { 623 | lbuf_dedup(tempbufs[0].lb, sb->s + n, sb->s_n - n) 624 | temp_pos(0, -1, 0, 0); 625 | temp_write(0, sb->s + n); 626 | } 627 | sbuf_str(sb, post) 628 | sbufn_sret(sb) 629 | } 630 | free(sb->s); 631 | return NULL; 632 | } 633 | 634 | /* read visual command input */ 635 | sbuf *led_input(char *pref, char **post, int row, int lsh) 636 | { 637 | sbuf *sb; sbuf_make(sb, xcols) 638 | int ai_max = 128 * xai; 639 | int n, key, ps = 0; 640 | sbuf_str(sb, pref) 641 | while (1) { 642 | led_line(sb, ps, sb->s_n, *post, ai_max, &key, &xkmap, row, lsh); 643 | if (key != '\n') { 644 | sbuf_set(sb, '\0', 4) 645 | sb->s_n -= 4; 646 | if (!xled) 647 | xoff = uc_slen(sb->s+ps); 648 | return sb; 649 | } 650 | sbufn_chr(sb, key) 651 | led_printparts(sb, -1, ps, "", 0); 652 | term_chr('\n'); 653 | term_room(1); 654 | xrow++; 655 | n = ps; 656 | ps = sb->s_n; 657 | if (ai_max) { /* updating autoindent */ 658 | while (**post == ' ' || **post == '\t') 659 | ++*post; 660 | int ai_new = n; 661 | while (sb->s[ai_new] == ' ' || sb->s[ai_new] == '\t') 662 | ai_new++; 663 | ai_new = ai_max > ai_new - n ? ai_new - n : ai_max; 664 | sbufn_mem(sb, sb->s+n, ai_new) 665 | } 666 | } 667 | } 668 | 669 | void led_done(void) 670 | { 671 | if (suggestsb) { 672 | sbuf_free(suggestsb) 673 | sbuf_free(acsb) 674 | } 675 | } 676 | -------------------------------------------------------------------------------- /regex.c: -------------------------------------------------------------------------------- 1 | static int isword(const char *s) 2 | { 3 | int c = (unsigned char) s[0]; 4 | return isalnum(c) || c == '_' || c > 127; 5 | } 6 | 7 | enum 8 | { 9 | /* Instructions which consume input bytes */ 10 | CHAR = 1, 11 | CLASS, 12 | MATCH, 13 | ANY, 14 | /* Assert position */ 15 | WBEG, 16 | WEND, 17 | BOL, 18 | EOL, 19 | LOOKAROUND, 20 | _PAD, 21 | /* Other (special) instructions */ 22 | SAVE, 23 | /* Instructions which take relative offset as arg */ 24 | JMP, 25 | SPLIT, 26 | RSPLIT, 27 | }; 28 | 29 | typedef struct rsub rsub; 30 | struct rsub 31 | { 32 | int ref; 33 | rsub *freesub; 34 | const char *sub[]; 35 | }; 36 | 37 | typedef struct rthread rthread; 38 | struct rthread 39 | { 40 | int *pc; 41 | rsub *sub; 42 | }; 43 | 44 | #define INSERT_CODE(at, num, pc) \ 45 | if (code) \ 46 | memmove(code + at + num, code + at, (pc - at)*sizeof(int)); \ 47 | pc += num; 48 | #define REL(at, to) (to - at - 2) 49 | #define EMIT(at, byte) (code ? (code[at] = byte) : at) 50 | #define PC (prog->unilen) 51 | 52 | static int re_sizecode(char *re, int *laidx); 53 | static int reg_comp(rcode *prog, char *re, int nsubs, int laidx, int flags); 54 | 55 | static void reg_free(rcode *p) 56 | { 57 | for (int i = 0; i < p->laidx; i++) 58 | reg_free(p->la[i]); 59 | free(p->la); 60 | free(p); 61 | } 62 | 63 | static int compilecode(char *re_loc, rcode *prog, int sizecode, int flg) 64 | { 65 | char *re = re_loc, *s, *p; 66 | int *code = sizecode ? NULL : prog->insts; 67 | int start = PC, term = PC; 68 | int alt_label = 0, c, l, cnt; 69 | int alt_stack[4096], altc = 0; 70 | int cap_stack[4096 * 5], capc = 0; 71 | 72 | while (*re) { 73 | switch (*re) { 74 | case '\\': 75 | re++; 76 | if (!*re) 77 | return -1; /* Trailing backslash */ 78 | if (*re == '<' || *re == '>') { 79 | if (re - re_loc > 2 && re[-2] == '\\') 80 | break; 81 | EMIT(PC++, *re == '<' ? WBEG : WEND); 82 | term = PC; 83 | break; 84 | } 85 | default: 86 | term = PC; 87 | EMIT(PC++, CHAR); 88 | uc_code(c, re, l) 89 | if (flg & REG_ICASE && (unsigned int)c < 128) 90 | c = tolower(c); 91 | EMIT(PC++, c); 92 | break; 93 | case '.': 94 | term = PC; 95 | EMIT(PC++, ANY); 96 | break; 97 | case '[':; 98 | term = PC; 99 | re++; 100 | EMIT(PC++, CLASS); 101 | if (*re == '^') { 102 | EMIT(PC++, 0); 103 | re++; 104 | } else 105 | EMIT(PC++, 1); 106 | PC++; 107 | for (cnt = 0; *re != ']'; cnt++) { 108 | if (*re == '\\') 109 | re++; 110 | if (!*re) 111 | return -1; 112 | uc_code(c, re, l) 113 | if (flg & REG_ICASE && (unsigned int)c < 128) 114 | c = tolower(c); 115 | EMIT(PC++, c); 116 | if (re[l] == '-' && re[l+1] != ']') 117 | re += l+1; 118 | uc_code(c, re, l) 119 | re += l; 120 | if (flg & REG_ICASE && (unsigned int)c < 128) 121 | c = tolower(c); 122 | EMIT(PC++, c); 123 | } 124 | EMIT(term + 2, cnt); 125 | break; 126 | case '(':; 127 | term = PC; 128 | int sub, sz, laidx, bal, la_static; 129 | int capture = 1; 130 | if (*(re+1) == '?') { 131 | re += 2; 132 | if (*re == ':') 133 | capture = 0; 134 | else if (*re == '=' || *re == '!' || *re == '<' || *re == '>') { 135 | EMIT(PC++, LOOKAROUND); 136 | EMIT(PC++, *re); 137 | EMIT(PC++, prog->laidx); 138 | bal = 1; 139 | s = ++re; 140 | la_static = *s == '^'; 141 | while (1) { 142 | if (!*s) 143 | return -1; 144 | else if (*s == '\\') { 145 | s++; 146 | if (code && (*s == '<' || *s == '>')) 147 | la_static = 0; 148 | } else if (*s == '(') { 149 | bal++; 150 | la_static = 0; 151 | } else if (*s == ')') { 152 | bal--; 153 | if (!bal) 154 | break; 155 | } else if (code && la_static && strchr("|.*+?[]{}$", *s)) 156 | la_static = 0; 157 | s += uc_len(s); 158 | } 159 | EMIT(PC++, la_static); 160 | if (code) { 161 | *s = '\0'; 162 | if (la_static) { 163 | p = emalloc(sizeof(rcode) + s - re); 164 | prog->la[prog->laidx] = (rcode*)p; 165 | prog->la[prog->laidx]->laidx = 0; 166 | prog->la[prog->laidx]->la = NULL; 167 | for (p += sizeof(rcode), re++; re != s; re++) 168 | if (*re != '\\') 169 | *p++ = *re; 170 | EMIT(PC-1, p - (char*)(prog->la[prog->laidx]+1)); 171 | } else { 172 | sz = re_sizecode(re, &laidx) * sizeof(int); 173 | if (sz < 0) 174 | return -1; 175 | prog->la[prog->laidx] = emalloc(sizeof(rcode)+sz); 176 | if (reg_comp(prog->la[prog->laidx], re, 0, laidx, prog->flg)) { 177 | reg_free(prog->la[prog->laidx]); 178 | return -1; 179 | } 180 | } 181 | *s = ')'; 182 | } 183 | prog->laidx++; 184 | re = s; 185 | break; 186 | } else 187 | return -1; 188 | } 189 | if (capture) { 190 | sub = ++prog->sub; 191 | EMIT(PC++, SAVE); 192 | EMIT(PC++, sub); 193 | } 194 | cap_stack[capc++] = capture; 195 | cap_stack[capc++] = term; 196 | cap_stack[capc++] = alt_label; 197 | cap_stack[capc++] = start; 198 | cap_stack[capc++] = altc; 199 | alt_label = 0; 200 | start = PC; 201 | break; 202 | case ')': 203 | if (--capc-4 < 0) 204 | return -1; 205 | if (code && alt_label) { 206 | EMIT(alt_label, REL(alt_label, PC) + 1); 207 | int _altc = cap_stack[capc]; 208 | for (int alts = altc; altc > _altc; altc--) { 209 | int at = alt_stack[_altc+alts-altc]+(altc-_altc)*2; 210 | EMIT(at, REL(at, PC) + 1); 211 | } 212 | } 213 | start = cap_stack[--capc]; 214 | alt_label = cap_stack[--capc]; 215 | term = cap_stack[--capc]; 216 | if (cap_stack[--capc]) { 217 | EMIT(PC++, SAVE); 218 | EMIT(PC++, code[term+1] + prog->presub + 1); 219 | } 220 | break; 221 | case '{':; 222 | int maxcnt = 0, mincnt = 0, i = 0, size = PC - term; 223 | re++; 224 | while (isdigit((unsigned char) *re)) 225 | mincnt = mincnt * 10 + *re++ - '0'; 226 | if (*re == ',') { 227 | re++; 228 | if (*re == '}') { 229 | EMIT(PC, RSPLIT); 230 | EMIT(PC+1, REL(PC, PC - size)); 231 | PC += 2; 232 | maxcnt = mincnt; 233 | } 234 | while (isdigit((unsigned char) *re)) 235 | maxcnt = maxcnt * 10 + *re++ - '0'; 236 | } else 237 | maxcnt = mincnt; 238 | for (; i < mincnt-1; i++) { 239 | if (code) 240 | memcpy(&code[PC], &code[term], size*sizeof(int)); 241 | PC += size; 242 | } 243 | for (i = maxcnt-mincnt; i > 0; i--) { 244 | EMIT(PC++, SPLIT); 245 | EMIT(PC++, REL(PC, PC+((size+2)*i))); 246 | if (code) 247 | memcpy(&code[PC], &code[term], size*sizeof(int)); 248 | PC += size; 249 | } 250 | break; 251 | case '?': 252 | if (PC == term) 253 | return -1; 254 | INSERT_CODE(term, 2, PC); 255 | if (re[1] == '?') { 256 | EMIT(term, RSPLIT); 257 | re++; 258 | } else 259 | EMIT(term, SPLIT); 260 | EMIT(term + 1, REL(term, PC)); 261 | term = PC; 262 | break; 263 | case '*': 264 | if (PC == term) 265 | return -1; 266 | INSERT_CODE(term, 2, PC); 267 | EMIT(PC, JMP); 268 | EMIT(PC + 1, REL(PC, term)); 269 | PC += 2; 270 | if (re[1] == '?') { 271 | EMIT(term, RSPLIT); 272 | re++; 273 | } else 274 | EMIT(term, SPLIT); 275 | EMIT(term + 1, REL(term, PC)); 276 | term = PC; 277 | break; 278 | case '+': 279 | if (PC == term) 280 | return -1; 281 | if (re[1] == '?') { 282 | EMIT(PC, SPLIT); 283 | re++; 284 | } else 285 | EMIT(PC, RSPLIT); 286 | EMIT(PC + 1, REL(PC, term)); 287 | PC += 2; 288 | term = PC; 289 | break; 290 | case '|': 291 | if (alt_label) 292 | alt_stack[altc++] = alt_label; 293 | INSERT_CODE(start, 2, PC); 294 | EMIT(PC++, JMP); 295 | alt_label = PC++; 296 | EMIT(start, SPLIT); 297 | EMIT(start + 1, REL(start, PC)); 298 | term = PC; 299 | break; 300 | case '^': 301 | EMIT(PC++, BOL); 302 | term = PC; 303 | break; 304 | case '$': 305 | EMIT(PC++, EOL); 306 | term = PC; 307 | break; 308 | } 309 | re += uc_len(re); 310 | } 311 | if (code && alt_label) { 312 | EMIT(alt_label, REL(alt_label, PC) + 1); 313 | for (int alts = altc; altc; altc--) { 314 | int at = alt_stack[alts-altc]+altc*2; 315 | EMIT(at, REL(at, PC) + 1); 316 | } 317 | } 318 | return capc ? -1 : 0; 319 | } 320 | 321 | static int re_sizecode(char *re, int *laidx) 322 | { 323 | rcode dummyprog; 324 | dummyprog.unilen = 4; 325 | dummyprog.laidx = 0; 326 | int res = compilecode(re, &dummyprog, 1, 0); 327 | *laidx = dummyprog.laidx; 328 | return res < 0 ? res : dummyprog.unilen; 329 | } 330 | 331 | static int reg_comp(rcode *prog, char *re, int nsubs, int laidx, int flags) 332 | { 333 | prog->len = 0; 334 | prog->unilen = 0; 335 | prog->sub = 0; 336 | prog->presub = nsubs; 337 | prog->splits = 0; 338 | prog->laidx = 0; 339 | prog->flg = flags; 340 | prog->la = laidx ? emalloc(laidx * sizeof(rcode*)) : NULL; 341 | if (compilecode(re, prog, 0, flags) < 0) 342 | return -1; 343 | int icnt = 0, scnt = SPLIT; 344 | for (int i = 0; i < prog->unilen; i++) 345 | switch (prog->insts[i]) { 346 | case LOOKAROUND: 347 | i += 3; 348 | icnt++; 349 | break; 350 | case CLASS: 351 | i += prog->insts[i+2] * 2 + 2; 352 | icnt++; 353 | break; 354 | case SPLIT: 355 | prog->insts[i++] = scnt; 356 | scnt += 2; 357 | icnt++; 358 | break; 359 | case RSPLIT: 360 | prog->insts[i] = -scnt; 361 | scnt += 2; 362 | case JMP: 363 | case SAVE: 364 | case CHAR: 365 | i++; 366 | case ANY: 367 | icnt++; 368 | } 369 | prog->insts[prog->unilen++] = SAVE; 370 | prog->insts[prog->unilen++] = prog->sub + 1; 371 | prog->insts[prog->unilen++] = MATCH; 372 | prog->splits = (scnt - SPLIT) / 2; 373 | prog->len = icnt + 2; 374 | prog->presub = sizeof(rsub) + (sizeof(char*) * (nsubs + 1) * 2); 375 | prog->sub = prog->presub * (prog->len - prog->splits + 3); 376 | prog->sparsesz = scnt; 377 | return 0; 378 | } 379 | 380 | #define _return(state) { if (eol_ch) utf8_length[eol_ch] = 1; return state; } \ 381 | 382 | #define newsub(init, copy) \ 383 | if (freesub) \ 384 | { s1 = freesub; freesub = s1->freesub; copy } \ 385 | else \ 386 | { if (suboff == prog->sub) suboff = 0; \ 387 | s1 = (rsub*)&nsubs[suboff]; suboff += rsubsize; init } \ 388 | 389 | #define onlist(nn) \ 390 | if (sdense[spc] < sparsesz) \ 391 | if (sdense[sdense[spc] * 2] == (unsigned int)spc) \ 392 | deccheck(nn) \ 393 | sdense[spc] = sparsesz; \ 394 | sdense[sparsesz++ * 2] = spc; \ 395 | 396 | #define decref(csub) \ 397 | if (--csub->ref == 0) { \ 398 | csub->freesub = freesub; \ 399 | freesub = csub; \ 400 | } \ 401 | 402 | #define rec_check(nn) \ 403 | if (si) { \ 404 | npc = pcs[--si]; \ 405 | nsub = subs[si]; \ 406 | goto rec##nn; \ 407 | } \ 408 | 409 | #define deccheck(nn) { decref(nsub) rec_check(nn) continue; } \ 410 | 411 | #define fastrec(nn, list, listidx) \ 412 | nsub->ref++; \ 413 | spc = *npc; \ 414 | if ((unsigned int)spc < WBEG) { \ 415 | list[listidx].sub = nsub; \ 416 | list[listidx++].pc = npc; \ 417 | npc = pcs[si]; \ 418 | goto rec##nn; \ 419 | } \ 420 | subs[si++] = nsub; \ 421 | goto next##nn; \ 422 | 423 | #define saveclist() \ 424 | if (npc[1] > nsubp / 2 && nsub->ref > 1) { \ 425 | nsub->ref--; \ 426 | newsub(memcpy(s1->sub, nsub->sub, osubp);, \ 427 | memcpy(s1->sub, nsub->sub, osubp / 2);) \ 428 | nsub = s1; \ 429 | nsub->ref = 1; \ 430 | } \ 431 | 432 | #define savenlist() \ 433 | if (nsub->ref > 1) { \ 434 | nsub->ref--; \ 435 | newsub(/*nop*/, /*nop*/) \ 436 | memcpy(s1->sub, nsub->sub, osubp); \ 437 | nsub = s1; \ 438 | nsub->ref = 1; \ 439 | } \ 440 | 441 | #define clistmatch(n) 442 | #define nlistmatch(n) \ 443 | if (spc == MATCH) \ 444 | for (i++; i < clistidx; i++) { \ 445 | npc = clist[i].pc; \ 446 | nsub = clist[i].sub; \ 447 | if (*npc == MATCH) \ 448 | goto matched##n; \ 449 | decref(nsub) \ 450 | } \ 451 | 452 | #define addthread(n, nn, list, listidx) \ 453 | rec##nn: \ 454 | spc = *npc; \ 455 | if ((unsigned int)spc < WBEG) { \ 456 | list[listidx].sub = nsub; \ 457 | list[listidx++].pc = npc; \ 458 | rec_check(nn) \ 459 | list##match(n) \ 460 | continue; \ 461 | } \ 462 | next##nn: \ 463 | if (spc > JMP) { \ 464 | onlist(nn) \ 465 | npc += 2; \ 466 | pcs[si] = npc + npc[-1]; \ 467 | fastrec(nn, list, listidx) \ 468 | } else if (spc == SAVE) { \ 469 | save##list() \ 470 | nsub->sub[npc[1]] = _sp; \ 471 | npc += 2; goto rec##nn; \ 472 | } else if (spc == WBEG) { \ 473 | if (((sp != s || sp != _sp) && isword(sp)) \ 474 | || !isword(_sp)) \ 475 | deccheck(nn) \ 476 | npc++; goto rec##nn; \ 477 | } else if (spc < 0) { \ 478 | spc = -spc; \ 479 | onlist(nn) \ 480 | npc += 2; \ 481 | pcs[si] = npc; \ 482 | npc += npc[-1]; \ 483 | fastrec(nn, list, listidx) \ 484 | } else if (spc == WEND) { \ 485 | if (isword(_sp)) \ 486 | deccheck(nn) \ 487 | npc++; goto rec##nn; \ 488 | } else if (spc == EOL) { \ 489 | if (flg & REG_NOTEOL || *_sp != eol_ch) \ 490 | deccheck(nn) \ 491 | npc++; goto rec##nn; \ 492 | } else if (spc == JMP) { \ 493 | npc += 2 + npc[1]; \ 494 | goto rec##nn; \ 495 | } else if (spc == LOOKAROUND) { \ 496 | int test; \ 497 | const char *str = npc[1] == '<' || npc[1] == '>' ? s : _sp; \ 498 | if (npc[3]) { \ 499 | test = !strncmp(str, (char*)(prog->la[npc[2]]+1), npc[3]); \ 500 | } else \ 501 | test = re_pikevm(prog->la[npc[2]], str, NULL, 0, 0); \ 502 | if ((test && (npc[1] == '!' || npc[1] == '>')) \ 503 | || (!test && (npc[1] == '=' || npc[1] == '<'))) \ 504 | deccheck(nn) \ 505 | npc += 4; goto rec##nn; \ 506 | } else { \ 507 | if (flg & REG_NOTBOL || _sp != s) { \ 508 | if (!si && !clistidx) \ 509 | _return(0) \ 510 | deccheck(nn) \ 511 | } \ 512 | npc++; goto rec##nn; \ 513 | } \ 514 | 515 | #define swaplist() \ 516 | tmp = clist; \ 517 | clist = nlist; \ 518 | nlist = tmp; \ 519 | clistidx = nlistidx; \ 520 | 521 | #define deccont() { decref(nsub) continue; } 522 | 523 | #define match(n, cpn) \ 524 | for (;; sp = _sp) { \ 525 | uc_code(c, sp, i) cpn \ 526 | _sp = sp+i; \ 527 | nlistidx = 0, sparsesz = 0; \ 528 | for (i = 0; i < clistidx; i++) { \ 529 | npc = clist[i].pc; \ 530 | nsub = clist[i].sub; \ 531 | spc = *npc; \ 532 | if (spc == CHAR) { \ 533 | if (c != npc[1]) \ 534 | deccont() \ 535 | npc += 2; \ 536 | } else if (spc == CLASS) { \ 537 | int *pc = npc+1; \ 538 | int cnt = pc[1]; \ 539 | for (; cnt > 0; cnt--) { \ 540 | pc += 2; \ 541 | if (c >= *pc && c <= pc[1]) \ 542 | cnt = -1; \ 543 | } \ 544 | if ((!cnt && npc[1]) || (cnt < 0 && !npc[1])) \ 545 | deccont() \ 546 | npc += npc[2] * 2 + 3; \ 547 | } else if (spc == MATCH) { \ 548 | matched##n: \ 549 | nlist[nlistidx++].pc = &mcont; \ 550 | if (npc != &mcont) { \ 551 | if (matched) \ 552 | decref(matched) \ 553 | matched = nsub; \ 554 | } \ 555 | if (sp == _sp || nlistidx == 1) { \ 556 | for (i = 0, j = i; i < nsubp; i+=2, j++) { \ 557 | subp[i] = matched->sub[j]; \ 558 | subp[i+1] = matched->sub[nsubp / 2 + j]; \ 559 | } \ 560 | _return(1) \ 561 | } \ 562 | swaplist() \ 563 | goto _continue##n; \ 564 | } else \ 565 | npc++; \ 566 | addthread(n, 2##n, nlist, nlistidx) \ 567 | } \ 568 | if (sp == _sp) \ 569 | break; \ 570 | swaplist() \ 571 | jmp_start##n: \ 572 | newsub(memset(s1->sub, 0, osubp);, /*nop*/) \ 573 | s1->ref = 1; \ 574 | s1->sub[0] = _sp; \ 575 | npc = insts; nsub = s1; \ 576 | addthread(n, 1##n, clist, clistidx) \ 577 | _continue##n:; \ 578 | } \ 579 | _return(0) \ 580 | 581 | int re_pikevm(rcode *prog, const char *s, const char **subp, int nsubp, int flg) 582 | { 583 | if (!*s) 584 | return 0; 585 | const char *sp = s, *_sp = s; 586 | int rsubsize = prog->presub, suboff = 0; 587 | int spc, i, j, c, *npc, osubp = nsubp * sizeof(char*); 588 | int si = 0, clistidx = 0, nlistidx, mcont = MATCH; 589 | int *insts = prog->insts, eol_ch = flg & REG_NEWLINE ? '\n' : 0; 590 | int *pcs[prog->splits]; 591 | rsub *subs[prog->splits]; 592 | unsigned int sdense[prog->sparsesz], sparsesz = 0; 593 | rsub *nsub, *s1, *matched = NULL, *freesub = NULL; 594 | rthread _clist[prog->len], _nlist[prog->len]; 595 | rthread *clist = _clist, *nlist = _nlist, *tmp; 596 | char nsubs[prog->sub]; 597 | flg = prog->flg | flg; 598 | if (eol_ch) 599 | utf8_length[eol_ch] = 0; 600 | if (flg & REG_ICASE) 601 | goto jmp_start1; 602 | goto jmp_start2; 603 | match(1, if ((unsigned int)c < 128) c = tolower(c);) 604 | match(2, /*nop*/) 605 | } 606 | 607 | static int re_groupcount(char *s) 608 | { 609 | int n = *s == '(' && s[1] != '?' ? 1 : 0; 610 | while (*s++) 611 | if (s[0] == '(' && s[-1] != '\\' && s[1] != '?') 612 | n++; 613 | return n; 614 | } 615 | 616 | void rset_free(rset *rs) 617 | { 618 | if (!rs) 619 | return; 620 | reg_free(rs->regex); 621 | free(rs->setgrpcnt); 622 | free(rs->grp); 623 | free(rs); 624 | } 625 | 626 | rset *rset_make(int n, char **re, int flg) 627 | { 628 | int i, laidx, sz, c = 0; 629 | rset *rs = emalloc(sizeof(*rs)); 630 | rs->grp = emalloc((n + 1) * sizeof(rs->grp[0])); 631 | rs->setgrpcnt = emalloc((n + 1) * sizeof(rs->setgrpcnt[0])); 632 | sbuf_smake(sb, 1024) 633 | rs->n = n; 634 | for (i = 0; i < n; i++) 635 | if (!re[i]) 636 | c++; 637 | rs->grpcnt = (n - c) > 1; 638 | for (i = 0; i < n; i++) { 639 | if (!re[i]) { 640 | rs->grp[i] = -1; 641 | continue; 642 | } 643 | if (sb->s_n > 0) 644 | sbuf_chr(sb, '|') 645 | if ((n - c) > 1) 646 | sbuf_chr(sb, '(') 647 | sbuf_str(sb, re[i]) 648 | if ((n - c) > 1) 649 | sbuf_chr(sb, ')') 650 | rs->grp[i] = rs->grpcnt; 651 | rs->setgrpcnt[i] = re_groupcount(re[i]) + 1; 652 | rs->grpcnt += rs->setgrpcnt[i]; 653 | } 654 | sbuf_mem(sb, "\0\0\0\0", 4) 655 | sz = re_sizecode(sb->s, &laidx) * sizeof(int); 656 | if (sz > 0) { 657 | rs->regex = emalloc(sizeof(rcode)+sz); 658 | if (!reg_comp(rs->regex, sb->s, 659 | MAX(rs->grpcnt-1, 0), laidx, flg)) 660 | goto success; 661 | reg_free(rs->regex); 662 | } 663 | free(rs->setgrpcnt); 664 | free(rs->grp); 665 | free(rs); 666 | rs = NULL; 667 | success: 668 | free(sb->s); 669 | return rs; 670 | } 671 | 672 | /* return the index of the matching regular expression or -1 if none matches */ 673 | int rset_find(rset *rs, char *s, int *grps, int flg) 674 | { 675 | regmatch_t subs[rs->grpcnt+1]; 676 | regmatch_t *sub = subs+1; 677 | if (re_pikevm(rs->regex, s, (const char**)sub, rs->grpcnt * 2, flg)) { 678 | subs[0].rm_eo = NULL; /* make sure sub[-1] never matches */ 679 | for (int i = rs->n-1; i >= 0; i--) { 680 | if (sub[rs->grp[i]].rm_eo) { 681 | int grp, n = grps ? rs->setgrpcnt[i] : 0; 682 | for (int gi = 0; gi < n; gi++) { 683 | grp = rs->grp[i] + gi; 684 | if (sub[grp].rm_eo && sub[grp].rm_so) { 685 | grps[gi * 2] = sub[grp].rm_so - s; 686 | grps[gi * 2 + 1] = sub[grp].rm_eo - s; 687 | } else { 688 | grps[gi * 2] = -1; 689 | grps[gi * 2 + 1] = -1; 690 | } 691 | } 692 | return i; 693 | } 694 | } 695 | } 696 | return -1; 697 | } 698 | 699 | /* read a regular expression enclosed in a delimiter */ 700 | char *re_read(char **src) 701 | { 702 | char *s = *src; 703 | int delim = (unsigned char) *s++; 704 | if (!delim) 705 | return NULL; 706 | sbuf_smake(sb, 256) 707 | while (*s && *s != delim) { 708 | if (s[0] == '\\' && s[1]) 709 | if (*(++s) != delim) 710 | sbuf_chr(sb, '\\') 711 | sbuf_chr(sb, (unsigned char) *s++) 712 | } 713 | *src = *s ? s + 1 : s; 714 | sbufn_sret(sb) 715 | } 716 | -------------------------------------------------------------------------------- /ren.c: -------------------------------------------------------------------------------- 1 | /* rendering strings */ 2 | 3 | static rset *dir_rslr; /* pattern of marks for left-to-right strings */ 4 | static rset *dir_rsrl; /* pattern of marks for right-to-left strings */ 5 | static rset *dir_rsctx; /* direction context patterns */ 6 | 7 | static void dir_reverse(int *ord, int beg, int end) 8 | { 9 | end--; 10 | while (beg < end) { 11 | int tmp = ord[beg]; 12 | ord[beg] = ord[end]; 13 | ord[end] = tmp; 14 | beg++; 15 | end--; 16 | } 17 | } 18 | 19 | /* reorder the characters based on direction marks and characters */ 20 | static int dir_reorder(char **chrs, int *ord, int end, int dir) 21 | { 22 | rset *rs = dir < 0 ? dir_rsrl : dir_rslr; 23 | int beg = 0, end1 = end, c_beg, c_end; 24 | int subs[LEN(dmarks[0].dir) * 2], gdir, found, i; 25 | while (beg < end) { 26 | char *s = chrs[beg]; 27 | found = rset_find(rs, s, subs, 28 | *chrs[end-1] == '\n' ? REG_NEWLINE : 0); 29 | if (found >= 0) { 30 | for (i = 0; i < end1; i++) 31 | ord[i] = i; 32 | c_end = 0; 33 | end1 = -1; 34 | for (i = 0; i < rs->setgrpcnt[found]; i++) { 35 | gdir = dmarks[found].dir[i]; 36 | if (subs[i * 2] < 0 || gdir >= 0) 37 | continue; 38 | c_beg = uc_off(s, subs[i * 2]); 39 | c_end = uc_off(s, subs[i * 2 + 1]); 40 | dir_reverse(ord, beg+c_beg, beg+c_end); 41 | } 42 | beg += c_end ? c_end : 1; 43 | } else 44 | break; 45 | } 46 | return end1 < 0; 47 | } 48 | 49 | /* return the direction context of the given line */ 50 | int dir_context(char *s) 51 | { 52 | int found; 53 | if (xtd > +1) 54 | return +1; 55 | if (xtd < -1) 56 | return -1; 57 | if (dir_rsctx && s) 58 | if ((found = rset_find(dir_rsctx, s, NULL, 0)) >= 0) 59 | return dctxs[found].dir; 60 | return xtd < 0 ? -1 : +1; 61 | } 62 | 63 | void dir_init(void) 64 | { 65 | char *relr[128]; 66 | char *rerl[128]; 67 | char *ctx[128]; 68 | int i; 69 | for (i = 0; i < dmarkslen; i++) { 70 | relr[i] = dmarks[i].ctx >= 0 ? dmarks[i].pat : NULL; 71 | rerl[i] = dmarks[i].ctx <= 0 ? dmarks[i].pat : NULL; 72 | } 73 | dir_rslr = rset_make(i, relr, 0); 74 | dir_rsrl = rset_make(i, rerl, 0); 75 | for (i = 0; i < dctxlen; i++) 76 | ctx[i] = dctxs[i].pat; 77 | dir_rsctx = rset_make(i, ctx, 0); 78 | } 79 | 80 | static int ren_cwid(char *s, int pos) 81 | { 82 | if (s[0] == '\t') 83 | return xtabspc - (pos & (xtabspc-1)); 84 | if (s[0] == '\n') 85 | return 1; 86 | int c, l; uc_code(c, s, l) 87 | for (int i = 0; i < phlen; i++) 88 | if (c >= ph[i].cp[0] && c <= ph[i].cp[1] && l == ph[i].l) 89 | return ph[i].wid; 90 | return uc_wid(c); 91 | } 92 | 93 | static ren_state rstates[2]; 94 | ren_state *rstate = &rstates[0]; 95 | 96 | /* specify the screen position of the characters in s */ 97 | ren_state *ren_position(char *s) 98 | { 99 | if (rstate->s == s) 100 | return rstate; 101 | else if (rstate->col) { 102 | free(rstate->col - 2); 103 | free(rstate->pos); 104 | free(rstate->chrs); 105 | } 106 | unsigned int n, i, c = 2; 107 | int cpos = 0, wid, *off, *pos, *col; 108 | char **chrs = uc_chop(s, &n); 109 | pos = emalloc(((n + 1) * sizeof(pos[0])) * 2); 110 | off = &pos[n+1]; 111 | rstate->ctx = dir_context(s); 112 | if (xorder && dir_reorder(chrs, off, n, rstate->ctx)) { 113 | int *wids = emalloc(n * sizeof(wids[0])); 114 | for (i = 0; i < n; i++) { 115 | pos[off[i]] = cpos; 116 | cpos += ren_cwid(chrs[off[i]], cpos); 117 | } 118 | col = emalloc((cpos + 2) * sizeof(col[0])); 119 | pos[n] = cpos; 120 | for (i = 0; i < n; i++) { 121 | wid = ren_cwid(chrs[off[i]], pos[off[i]]); 122 | wids[off[i]] = wid; 123 | while (wid--) 124 | col[c++] = off[i]; 125 | } 126 | memcpy(off, wids, n * sizeof(wids[0])); 127 | free(wids); 128 | } else { 129 | for (i = 0; i < n; i++) { 130 | pos[i] = cpos; 131 | cpos += ren_cwid(chrs[i], cpos); 132 | } 133 | col = emalloc((cpos + 2) * sizeof(col[0])); 134 | pos[n] = cpos; 135 | for (i = 0; i < n; i++) { 136 | wid = pos[i+1] - pos[i]; 137 | off[i] = wid; 138 | while (wid--) 139 | col[c++] = i; 140 | } 141 | } 142 | off[n] = 0; 143 | col[0] = n; 144 | col[1] = n; 145 | rstate->wid = off; 146 | rstate->cmax = cpos - 1; 147 | rstate->col = col + 2; 148 | rstate->s = s; 149 | rstate->pos = pos; 150 | rstate->chrs = chrs; 151 | rstate->n = n; 152 | return rstate; 153 | } 154 | 155 | /* convert character offset to visual position */ 156 | int ren_pos(char *s, int off) 157 | { 158 | ren_state *r = ren_position(s); 159 | return off < r->n ? r->pos[off] : 0; 160 | } 161 | 162 | /* convert visual position to character offset */ 163 | int ren_off(char *s, int p) 164 | { 165 | ren_state *r = ren_position(s); 166 | return r->col[p < r->cmax ? p : r->cmax]; 167 | } 168 | 169 | /* adjust cursor position */ 170 | int ren_cursor(char *s, int p) 171 | { 172 | if (!s) 173 | return 0; 174 | ren_state *r = ren_position(s); 175 | if (p >= r->cmax) 176 | p = r->cmax - (*r->chrs[r->col[r->cmax]] == '\n'); 177 | int i = r->col[p]; 178 | return r->pos[i] + r->wid[i] - 1; 179 | } 180 | 181 | /* return an offset before EOL */ 182 | int ren_noeol(char *s, int o) 183 | { 184 | if (!s) 185 | return 0; 186 | ren_state *r = ren_position(s); 187 | o = o >= r->n ? r->n - 1 : MAX(0, o); 188 | return o - (o > 0 && *r->chrs[o] == '\n'); 189 | } 190 | 191 | /* the visual position of the next character */ 192 | int ren_next(char *s, int p, int dir) 193 | { 194 | ren_state *r = ren_position(s); 195 | if (p+dir < 0 || p > r->cmax) 196 | return r->pos[r->col[r->cmax]]; 197 | int i = r->col[p]; 198 | if (r->wid[i] > 1 && dir > 0) 199 | return r->pos[i] + r->wid[i]; 200 | return r->pos[i] + dir; 201 | } 202 | 203 | char *ren_translate(char *s, char *ln) 204 | { 205 | if (s[0] == '\t' || s[0] == '\n') 206 | return NULL; 207 | int c, l; uc_code(c, s, l) 208 | for (int i = 0; i < phlen; i++) 209 | if (c >= ph[i].cp[0] && c <= ph[i].cp[1] && l == ph[i].l) 210 | return ph[i].d; 211 | if (l == 1) 212 | return NULL; 213 | if (uc_acomb(c)) { 214 | static char buf[16] = "ـ"; 215 | *((char*)memcpy(buf+2, s, l)+l) = '\0'; 216 | return buf; 217 | } 218 | if (uc_isbell(c)) 219 | return "�"; 220 | return xshape ? uc_shape(ln, s, c) : NULL; 221 | } 222 | 223 | #define NFTS 30 224 | /* mapping filetypes to regular expression sets */ 225 | static struct ftmap { 226 | int setbidx; 227 | int seteidx; 228 | char *ft; 229 | rset *rs; 230 | } ftmap[NFTS]; 231 | static int ftmidx; 232 | static int ftidx; 233 | 234 | static rset *syn_ftrs; 235 | static int last_scdir; 236 | static int *blockatt; 237 | static int blockcont; 238 | int syn_reload; 239 | int syn_blockhl; 240 | 241 | static void syn_initft(int fti, int n, char *name) 242 | { 243 | int i = n; 244 | char *pats[hlslen]; 245 | for (; i < hlslen && !strcmp(hls[i].ft, name); i++) 246 | pats[i - n] = hls[i].pat; 247 | ftmap[fti].setbidx = n; 248 | ftmap[fti].ft = name; 249 | ftmap[fti].rs = rset_make(i - n, pats, 0); 250 | ftmap[fti].seteidx = i; 251 | } 252 | 253 | char *syn_setft(char *ft) 254 | { 255 | for (int i = 1; i < 4; i++) 256 | syn_addhl(NULL, i, 0); 257 | for (int i = 0; i < ftmidx; i++) 258 | if (!strcmp(ft, ftmap[i].ft)) { 259 | ftidx = i; 260 | return ftmap[ftidx].ft; 261 | } 262 | for (int i = 0; i < hlslen; i++) 263 | if (!strcmp(ft, hls[i].ft)) { 264 | ftidx = ftmidx; 265 | syn_initft(ftmidx++, i, hls[i].ft); 266 | break; 267 | } 268 | return ftmap[ftidx].ft; 269 | } 270 | 271 | void syn_scdir(int scdir) 272 | { 273 | if (last_scdir != scdir) { 274 | last_scdir = scdir; 275 | syn_blockhl = 0; 276 | } 277 | } 278 | 279 | int syn_merge(int old, int new) 280 | { 281 | int fg = SYN_FGSET(new) ? SYN_FG(new) : SYN_FG(old); 282 | int bg = SYN_BGSET(new) ? SYN_BG(new) : SYN_BG(old); 283 | return ((old | new) & SYN_FLG) | (bg << 8) | fg; 284 | } 285 | 286 | void syn_highlight(int *att, char *s, int n) 287 | { 288 | rset *rs = ftmap[ftidx].rs; 289 | int subs[rs->grpcnt * 2], sl; 290 | int blk = 0, blkm = 0, sidx = 0, flg = 0, hl, j, i; 291 | int bend = 0, cend = 0; 292 | while ((sl = rset_find(rs, s + sidx, subs, flg)) >= 0) { 293 | hl = sl + ftmap[ftidx].setbidx; 294 | int *catt = hls[hl].att; 295 | int blkend = hls[hl].blkend; 296 | if (blkend && sidx >= bend) { 297 | for (i = 0; i <= abs(blkend); i++) 298 | if (subs[i * 2] >= 0) 299 | blk = i; 300 | blkm += blkm > abs(blkend) ? -1 : 1; 301 | if (blkm == 1 && last_scdir > 0) 302 | blkend = blkend < 0 ? -1 : 1; 303 | if (syn_blockhl == hl && blk == abs(blkend)) 304 | syn_blockhl = 0; 305 | else if (!syn_blockhl && blk != blkend) { 306 | syn_blockhl = hl; 307 | blockatt = catt; 308 | blockcont = hls[hl].end[blk]; 309 | } else 310 | blk = 0; 311 | } 312 | for (i = 0; i < rs->setgrpcnt[sl]; i++) { 313 | if (subs[i * 2] >= 0) { 314 | int beg = uc_off(s, sidx + subs[i * 2 + 0]); 315 | int end = uc_off(s, sidx + subs[i * 2 + 1]); 316 | for (j = beg; j < end; j++) 317 | att[j] = syn_merge(att[j], catt[i]); 318 | if (!hls[hl].end[i]) 319 | cend = MAX(cend, subs[i * 2 + 1]); 320 | else { 321 | if (blkend) 322 | bend = MAX(cend, subs[i * 2 + 1]) + sidx; 323 | if (hls[hl].end[i] > 0) 324 | cend = MAX(cend, subs[i * 2]); 325 | } 326 | } 327 | } 328 | sidx += cend; 329 | cend = 1; 330 | flg = REG_NOTBOL; 331 | } 332 | if (syn_blockhl && !blk) 333 | for (j = 0; j < n; j++) 334 | att[j] = blockcont && att[j] ? att[j] : *blockatt; 335 | } 336 | 337 | char *syn_filetype(char *path) 338 | { 339 | int hl = rset_find(syn_ftrs, path, NULL, 0); 340 | return hl >= 0 && hl < ftslen ? fts[hl].ft : hls[0].ft; 341 | } 342 | 343 | void syn_reloadft(void) 344 | { 345 | if (syn_reload) { 346 | rset *rs = ftmap[ftidx].rs; 347 | syn_initft(ftidx, ftmap[ftidx].setbidx, ftmap[ftidx].ft); 348 | if (!ftmap[ftidx].rs) { 349 | ftmap[ftidx].rs = rs; 350 | } else 351 | rset_free(rs); 352 | syn_reload = 0; 353 | } 354 | } 355 | 356 | int syn_findhl(int id) 357 | { 358 | for (int i = ftmap[ftidx].setbidx; i < ftmap[ftidx].seteidx; i++) 359 | if (hls[i].id == id) 360 | return i; 361 | return -1; 362 | } 363 | 364 | void syn_addhl(char *reg, int id, int reload) 365 | { 366 | int ret = syn_findhl(id); 367 | if (ret >= 0) { 368 | hls[ret].pat = reg; 369 | syn_reload = reload; 370 | } 371 | } 372 | 373 | void syn_init(void) 374 | { 375 | char *pats[ftslen]; 376 | int i = 0; 377 | for (; i < ftslen; i++) 378 | pats[i] = fts[i].pat; 379 | syn_ftrs = rset_make(i, pats, 0); 380 | } 381 | -------------------------------------------------------------------------------- /term.c: -------------------------------------------------------------------------------- 1 | static struct termios termios; 2 | sbuf *term_sbuf; 3 | int term_record; 4 | int xrows, xcols; 5 | unsigned int ibuf_pos, ibuf_cnt, ibuf_sz = 128, icmd_pos; 6 | unsigned char *ibuf, icmd[4096]; 7 | unsigned int texec, tn; 8 | 9 | void term_init(void) 10 | { 11 | if (xvis & 2) 12 | return; 13 | struct winsize win; 14 | struct termios newtermios; 15 | sbuf_make(term_sbuf, 2048) 16 | tcgetattr(0, &termios); 17 | newtermios = termios; 18 | newtermios.c_lflag &= ~(ICANON | ISIG | ECHO); 19 | tcsetattr(0, TCSAFLUSH, &newtermios); 20 | if (getenv("LINES")) 21 | xrows = atoi(getenv("LINES")); 22 | if (getenv("COLUMNS")) 23 | xcols = atoi(getenv("COLUMNS")); 24 | if (!ioctl(0, TIOCGWINSZ, &win)) { 25 | xcols = win.ws_col; 26 | xrows = win.ws_row; 27 | } 28 | xcols = xcols ? xcols : 80; 29 | xrows = xrows ? xrows : 25; 30 | } 31 | 32 | void term_done(void) 33 | { 34 | if (xvis & 2) 35 | return; 36 | term_commit(); 37 | sbuf_free(term_sbuf) 38 | term_sbuf = NULL; 39 | tcsetattr(0, 0, &termios); 40 | } 41 | 42 | void term_clean(void) 43 | { 44 | term_write("\x1b[2J", 4) /* clear screen */ 45 | term_write("\x1b[H", 3) /* cursor topleft */ 46 | } 47 | 48 | void term_suspend(void) 49 | { 50 | term_done(); 51 | kill(0, SIGSTOP); 52 | term_init(); 53 | } 54 | 55 | void term_commit(void) 56 | { 57 | term_write(term_sbuf->s, term_sbuf->s_n) 58 | sbuf_cut(term_sbuf, 0) 59 | term_record = 0; 60 | } 61 | 62 | static void term_out(char *s) 63 | { 64 | if (term_record) 65 | sbufn_str(term_sbuf, s) 66 | else 67 | term_write(s, strlen(s)) 68 | } 69 | 70 | void term_chr(int ch) 71 | { 72 | char s[4] = {ch}; 73 | term_out(s); 74 | } 75 | 76 | void term_kill(void) 77 | { 78 | term_out("\33[K"); 79 | } 80 | 81 | void term_room(int n) 82 | { 83 | char cmd[64] = "\33["; 84 | if (!n) 85 | return; 86 | char *s = itoa(abs(n), cmd+2); 87 | s[0] = n < 0 ? 'M' : 'L'; 88 | s[1] = '\0'; 89 | term_out(cmd); 90 | } 91 | 92 | void term_pos(int r, int c) 93 | { 94 | char buf[64] = "\r\33[", *s; 95 | if (r < 0) { 96 | memcpy(itoa(MAX(0, c), buf+3), c > 0 ? "C" : "D", 2); 97 | term_out(buf); 98 | } else { 99 | s = itoa(r + 1, buf+3); 100 | if (c > 0) { 101 | *s++ = ';'; 102 | s = itoa(c + 1, s); 103 | } 104 | memcpy(s, "H", 2); 105 | term_out(buf+1); 106 | } 107 | } 108 | 109 | /* read s before reading from the terminal */ 110 | void term_push(char *s, unsigned int n) 111 | { 112 | static unsigned int tibuf_pos, tibuf_cnt; 113 | if (texec == '@' && xquit > 0) { 114 | xquit = 0; 115 | tn = 0; 116 | ibuf_cnt = tibuf_cnt; 117 | ibuf_pos = tibuf_cnt; 118 | } 119 | if (ibuf_cnt + n >= ibuf_sz || ibuf_sz - ibuf_cnt + n > 128) { 120 | ibuf_sz = ibuf_cnt + n + 128; 121 | ibuf = erealloc(ibuf, ibuf_sz); 122 | } 123 | if (texec) { 124 | if (tibuf_pos != ibuf_pos) 125 | tn = 0; 126 | memmove(ibuf + ibuf_pos + n + tn, 127 | ibuf + ibuf_pos + tn, ibuf_cnt - ibuf_pos - tn); 128 | memcpy(ibuf + ibuf_pos + tn, s, n); 129 | tn += n; 130 | tibuf_pos = ibuf_pos; 131 | } else 132 | memcpy(ibuf + ibuf_cnt, s, n); 133 | tibuf_cnt = ibuf_cnt; 134 | ibuf_cnt += n; 135 | } 136 | 137 | void term_back(int c) 138 | { 139 | char s[1] = {c}; 140 | term_push(s, 1); 141 | } 142 | 143 | int term_read(void) 144 | { 145 | struct pollfd ufds[1]; 146 | if (ibuf_pos >= ibuf_cnt) { 147 | if (texec) { 148 | xquit = !xquit ? 1 : xquit; 149 | if (texec == '&') 150 | goto err; 151 | } 152 | ufds[0].fd = STDIN_FILENO; 153 | ufds[0].events = POLLIN; 154 | /* read a single input character */ 155 | if (xquit < 0 || poll(ufds, 1, -1) <= 0 || 156 | read(STDIN_FILENO, ibuf, 1) <= 0) { 157 | xquit = !isatty(STDIN_FILENO) ? -1 : xquit; 158 | err: 159 | *ibuf = 0; 160 | } 161 | ibuf_cnt = 1; 162 | ibuf_pos = 0; 163 | } 164 | if (icmd_pos < sizeof(icmd)) 165 | icmd[icmd_pos++] = ibuf[ibuf_pos]; 166 | return ibuf[ibuf_pos++]; 167 | } 168 | 169 | /* return a static string that changes text attributes to att */ 170 | char *term_att(int att) 171 | { 172 | static char buf[128]; 173 | char *s = buf; 174 | int fg = SYN_FG(att); 175 | int bg = SYN_BG(att); 176 | *s++ = '\x1b'; 177 | *s++ = '['; 178 | if (att & SYN_BD) 179 | {*s++ = ';'; *s++ = '1';} 180 | if (att & SYN_IT) 181 | {*s++ = ';'; *s++ = '3';} 182 | else if (att & SYN_RV) 183 | {*s++ = ';'; *s++ = '7';} 184 | if (SYN_FGSET(att)) { 185 | *s++ = ';'; 186 | if ((fg & 0xff) < 8) 187 | s = itoa(30 + (fg & 0xff), s); 188 | else 189 | s = itoa(fg & 0xff, (char*)memcpy(s, "38;5;", 5)+5); 190 | } 191 | if (SYN_BGSET(att)) { 192 | *s++ = ';'; 193 | if ((bg & 0xff) < 8) 194 | s = itoa(40 + (bg & 0xff), s); 195 | else 196 | s = itoa(bg & 0xff, (char*)memcpy(s, "48;5;", 5)+5); 197 | } 198 | s[0] = 'm'; 199 | s[1] = '\0'; 200 | return buf; 201 | } 202 | 203 | static int cmd_make(char **argv, int *ifd, int *ofd) 204 | { 205 | int pid; 206 | int pipefds0[2] = {-1, -1}; 207 | int pipefds1[2] = {-1, -1}; 208 | if (ifd) 209 | pipe(pipefds0); 210 | if (ofd) 211 | pipe(pipefds1); 212 | if (!(pid = fork())) { 213 | if (ifd) { /* setting up stdin */ 214 | close(0); 215 | dup(pipefds0[0]); 216 | close(pipefds0[1]); 217 | close(pipefds0[0]); 218 | } 219 | if (ofd) { /* setting up stdout and stderr */ 220 | close(1); 221 | dup(pipefds1[1]); 222 | close(2); 223 | dup(pipefds1[1]); 224 | close(pipefds1[0]); 225 | close(pipefds1[1]); 226 | } 227 | execvp(argv[0], argv); 228 | exit(1); 229 | } 230 | if (ifd) 231 | close(pipefds0[0]); 232 | if (ofd) 233 | close(pipefds1[1]); 234 | if (pid < 0) { 235 | if (ifd) 236 | close(pipefds0[1]); 237 | if (ofd) 238 | close(pipefds1[0]); 239 | return -1; 240 | } 241 | if (ifd) 242 | *ifd = pipefds0[1]; 243 | if (ofd) 244 | *ofd = pipefds1[0]; 245 | return pid; 246 | } 247 | 248 | char *xgetenv(char **q) 249 | { 250 | char *r = NULL; 251 | while (*q && !r) { 252 | if (**q == '$') 253 | r = getenv(*q+1); 254 | else 255 | return *q; 256 | q++; 257 | } 258 | return r; 259 | } 260 | 261 | /* execute a command; pass in input if ibuf and process output if oproc */ 262 | char *cmd_pipe(char *cmd, char *ibuf, int oproc) 263 | { 264 | static char *sh[] = {"$SHELL", "sh", NULL}; 265 | struct pollfd fds[3]; 266 | char buf[512]; 267 | int ifd = -1, ofd = -1; 268 | int slen = ibuf ? strlen(ibuf) : 0; 269 | int nw = 0; 270 | char *argv[5]; 271 | argv[0] = xgetenv(sh); 272 | argv[1] = xish ? "-i" : argv[0]; 273 | argv[2] = "-c"; 274 | argv[3] = cmd; 275 | argv[4] = NULL; 276 | int pid = cmd_make(argv+!xish, ibuf ? &ifd : NULL, oproc ? &ofd : NULL); 277 | if (pid <= 0) 278 | return NULL; 279 | sbuf_smake(sb, sizeof(buf)) 280 | if (!ibuf) { 281 | signal(SIGINT, SIG_IGN); 282 | term_done(); 283 | } else if (ifd >= 0) 284 | fcntl(ifd, F_SETFL, fcntl(ifd, F_GETFL, 0) | O_NONBLOCK); 285 | fds[0].fd = ofd; 286 | fds[0].events = POLLIN; 287 | fds[1].fd = ifd; 288 | fds[1].events = POLLOUT; 289 | fds[2].fd = ibuf ? 0 : -1; 290 | fds[2].events = POLLIN; 291 | while ((fds[0].fd >= 0 || fds[1].fd >= 0) && poll(fds, 3, 200) >= 0) { 292 | if (fds[0].revents & POLLIN) { 293 | int ret = read(fds[0].fd, buf, sizeof(buf)); 294 | if (ret > 0 && oproc == 2) 295 | term_write(buf, ret) 296 | if (ret > 0) 297 | sbuf_mem(sb, buf, ret) 298 | else { 299 | close(fds[0].fd); 300 | fds[0].fd = -1; 301 | } 302 | } else if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { 303 | close(fds[0].fd); 304 | fds[0].fd = -1; 305 | } 306 | if (fds[1].revents & POLLOUT) { 307 | int ret = write(fds[1].fd, ibuf + nw, slen - nw); 308 | if (ret > 0) 309 | nw += ret; 310 | if (ret <= 0 || nw == slen) { 311 | close(fds[1].fd); 312 | fds[1].fd = -1; 313 | } 314 | } else if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) { 315 | close(fds[1].fd); 316 | fds[1].fd = -1; 317 | } 318 | if (fds[2].revents & POLLIN) { 319 | int ret = read(fds[2].fd, buf, sizeof(buf)); 320 | for (int i = 0; i < ret; i++) 321 | if ((unsigned char) buf[i] == TK_CTL('c')) 322 | kill(pid, SIGINT); 323 | } else if (fds[2].revents & (POLLERR | POLLHUP | POLLNVAL)) 324 | fds[2].fd = -1; 325 | } 326 | if (fds[0].fd >= 0) 327 | close(ofd); 328 | if (fds[1].fd >= 0) 329 | close(ifd); 330 | waitpid(pid, NULL, 0); 331 | signal(SIGTTOU, SIG_IGN); 332 | tcsetpgrp(STDIN_FILENO, getpgrp()); 333 | signal(SIGTTOU, SIG_DFL); 334 | if (!ibuf) { 335 | term_init(); 336 | signal(SIGINT, SIG_DFL); 337 | } 338 | if (oproc) 339 | sbufn_sret(sb) 340 | free(sb->s); 341 | return NULL; 342 | } 343 | -------------------------------------------------------------------------------- /uc.c: -------------------------------------------------------------------------------- 1 | unsigned char utf8_length[256] = { 2 | /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ 3 | /* 0 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4 | /* 1 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5 | /* 2 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6 | /* 3 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7 | /* 4 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8 | /* 5 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9 | /* 6 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10 | /* 7 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11 | /* 8 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12 | /* 9 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13 | /* A */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14 | /* B */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15 | /* C */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 16 | /* D */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 17 | /* E */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 18 | /* F */ 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1 19 | }; 20 | 21 | /* the number of utf-8 characters in a fat nulled s */ 22 | int uc_slen(char *s) 23 | { 24 | int n; 25 | for (n = 0; uc_len(s); n++) 26 | s += uc_len(s); 27 | return n; 28 | } 29 | 30 | /* find the beginning of the character at s[i] */ 31 | char *uc_beg(char *beg, char *s) 32 | { 33 | if (utf8_length[0xc0] == 1) 34 | return s; 35 | for (; s > beg && ((unsigned char)*s & 0xc0) == 0x80; s--); 36 | return s; 37 | } 38 | 39 | /* allocate and return an array for the characters in s */ 40 | char **uc_chop(char *s, unsigned int *n) 41 | { 42 | *n = uc_slen(s); 43 | int i, c = *n + 1; 44 | char **chrs = emalloc(c * sizeof(chrs[0])); 45 | for (i = 0; i < c; i++) { 46 | chrs[i] = s; 47 | s += uc_len(s); 48 | } 49 | return chrs; 50 | } 51 | 52 | char *uc_chr(char *s, int off) 53 | { 54 | int i = 0; 55 | if (!s) 56 | return ""; 57 | while (uc_len(s)) { 58 | if (i++ == off) 59 | return s; 60 | s += uc_len(s); 61 | } 62 | return s; 63 | } 64 | 65 | /* the number of characters between s and s + off */ 66 | int uc_off(char *s, int off) 67 | { 68 | char *e = s + off; 69 | int i; 70 | for (i = 0; s < e && uc_len(s); i++) 71 | s += uc_len(s); 72 | return i; 73 | } 74 | 75 | char *uc_subl(char *s, int beg, int end, int *rlen) 76 | { 77 | char *sbeg = uc_chr(s, beg); 78 | char *send = uc_chr(sbeg, end - beg); 79 | int len = sbeg < send ? send - sbeg : 0; 80 | char *r = emalloc(len + 4); 81 | memcpy(r, sbeg, len); 82 | memset(r+len, '\0', 4); 83 | *rlen = len; 84 | return r; 85 | } 86 | 87 | char *uc_dup(const char *s) 88 | { 89 | char *r = emalloc(strlen(s) + 1); 90 | return r ? strcpy(r, s) : NULL; 91 | } 92 | 93 | int uc_isspace(char *s) 94 | { 95 | int c = s ? (unsigned char) *s : 0; 96 | return c < 0x7f && isspace(c); 97 | } 98 | 99 | int uc_isprint(char *s) 100 | { 101 | int c = s ? (unsigned char) *s : 0; 102 | return c > 0x7f || isprint(c); 103 | } 104 | 105 | int uc_isalpha(char *s) 106 | { 107 | int c = s ? (unsigned char) *s : 0; 108 | return c > 0x7f || isalpha(c); 109 | } 110 | 111 | int uc_isdigit(char *s) 112 | { 113 | int c = s ? (unsigned char) *s : 0; 114 | return c < 0x7f && isdigit(c); 115 | } 116 | 117 | int uc_kind(char *c) 118 | { 119 | if (uc_isspace(c)) 120 | return 0; 121 | if (uc_isalpha(c) || uc_isdigit(c) || c[0] == '_') 122 | return 1; 123 | return 2; 124 | } 125 | 126 | #define UC_R2L(ch) (((ch) & 0xff00) == 0x0600 || \ 127 | ((ch) & 0xfffc) == 0x200c || \ 128 | ((ch) & 0xff00) == 0xfb00 || \ 129 | ((ch) & 0xff00) == 0xfc00 || \ 130 | ((ch) & 0xff00) == 0xfe00) 131 | 132 | /* sorted list of characters that can be shaped */ 133 | static struct achar { 134 | unsigned int c; /* utf-8 code */ 135 | unsigned int s; /* single form */ 136 | unsigned int i; /* initial form */ 137 | unsigned int m; /* medial form */ 138 | unsigned int f; /* final form */ 139 | } achars[] = { 140 | {0x0621, 0xfe80}, /* hamza */ 141 | {0x0622, 0xfe81, 0, 0, 0xfe82}, /* alef madda */ 142 | {0x0623, 0xfe83, 0, 0, 0xfe84}, /* alef hamza above */ 143 | {0x0624, 0xfe85, 0, 0, 0xfe86}, /* waw hamza */ 144 | {0x0625, 0xfe87, 0, 0, 0xfe88}, /* alef hamza below */ 145 | {0x0626, 0xfe89, 0xfe8b, 0xfe8c, 0xfe8a}, /* yeh hamza */ 146 | {0x0627, 0xfe8d, 0, 0, 0xfe8e}, /* alef */ 147 | {0x0628, 0xfe8f, 0xfe91, 0xfe92, 0xfe90}, /* beh */ 148 | {0x0629, 0xfe93, 0, 0, 0xfe94}, /* teh marbuta */ 149 | {0x062a, 0xfe95, 0xfe97, 0xfe98, 0xfe96}, /* teh */ 150 | {0x062b, 0xfe99, 0xfe9b, 0xfe9c, 0xfe9a}, /* theh */ 151 | {0x062c, 0xfe9d, 0xfe9f, 0xfea0, 0xfe9e}, /* jeem */ 152 | {0x062d, 0xfea1, 0xfea3, 0xfea4, 0xfea2}, /* hah */ 153 | {0x062e, 0xfea5, 0xfea7, 0xfea8, 0xfea6}, /* khah */ 154 | {0x062f, 0xfea9, 0, 0, 0xfeaa}, /* dal */ 155 | {0x0630, 0xfeab, 0, 0, 0xfeac}, /* thal */ 156 | {0x0631, 0xfead, 0, 0, 0xfeae}, /* reh */ 157 | {0x0632, 0xfeaf, 0, 0, 0xfeb0}, /* zain */ 158 | {0x0633, 0xfeb1, 0xfeb3, 0xfeb4, 0xfeb2}, /* seen */ 159 | {0x0634, 0xfeb5, 0xfeb7, 0xfeb8, 0xfeb6}, /* sheen */ 160 | {0x0635, 0xfeb9, 0xfebb, 0xfebc, 0xfeba}, /* sad */ 161 | {0x0636, 0xfebd, 0xfebf, 0xfec0, 0xfebe}, /* dad */ 162 | {0x0637, 0xfec1, 0xfec3, 0xfec4, 0xfec2}, /* tah */ 163 | {0x0638, 0xfec5, 0xfec7, 0xfec8, 0xfec6}, /* zah */ 164 | {0x0639, 0xfec9, 0xfecb, 0xfecc, 0xfeca}, /* ain */ 165 | {0x063a, 0xfecd, 0xfecf, 0xfed0, 0xfece}, /* ghain */ 166 | {0x0640, 0x640, 0x640, 0x640}, /* tatweel */ 167 | {0x0641, 0xfed1, 0xfed3, 0xfed4, 0xfed2}, /* feh */ 168 | {0x0642, 0xfed5, 0xfed7, 0xfed8, 0xfed6}, /* qaf */ 169 | {0x0643, 0xfed9, 0xfedb, 0xfedc, 0xfeda}, /* kaf */ 170 | {0x0644, 0xfedd, 0xfedf, 0xfee0, 0xfede}, /* lam */ 171 | {0x0645, 0xfee1, 0xfee3, 0xfee4, 0xfee2}, /* meem */ 172 | {0x0646, 0xfee5, 0xfee7, 0xfee8, 0xfee6}, /* noon */ 173 | {0x0647, 0xfee9, 0xfeeb, 0xfeec, 0xfeea}, /* heh */ 174 | {0x0648, 0xfeed, 0, 0, 0xfeee}, /* waw */ 175 | {0x0649, 0xfeef, 0, 0, 0xfef0}, /* alef maksura */ 176 | {0x064a, 0xfef1, 0xfef3, 0xfef4, 0xfef2}, /* yeh */ 177 | {0x067e, 0xfb56, 0xfb58, 0xfb59, 0xfb57}, /* peh */ 178 | {0x0686, 0xfb7a, 0xfb7c, 0xfb7d, 0xfb7b}, /* tcheh */ 179 | {0x0698, 0xfb8a, 0, 0, 0xfb8b}, /* jeh */ 180 | {0x06a9, 0xfb8e, 0xfb90, 0xfb91, 0xfb8f}, /* fkaf */ 181 | {0x06af, 0xfb92, 0xfb94, 0xfb95, 0xfb93}, /* gaf */ 182 | {0x06cc, 0xfbfc, 0xfbfe, 0xfbff, 0xfbfd}, /* fyeh */ 183 | {0x200c}, /* ZWNJ */ 184 | {0x200d, 0, 0x200d, 0x200d}, /* ZWJ */ 185 | }; 186 | 187 | static struct achar *find_achar(unsigned int c) 188 | { 189 | int h, m, l; 190 | h = LEN(achars); 191 | l = 0; 192 | /* using binary search to find c */ 193 | while (l < h) { 194 | m = (h + l) >> 1; 195 | if (achars[m].c == c) 196 | return &achars[m]; 197 | if (c < achars[m].c) 198 | h = m; 199 | else 200 | l = m + 1; 201 | } 202 | return NULL; 203 | } 204 | 205 | static int can_join(int c1, int c2) 206 | { 207 | struct achar *a1 = find_achar(c1); 208 | struct achar *a2 = find_achar(c2); 209 | return a1 && a2 && (a1->i || a1->m) && (a2->f || a2->m); 210 | } 211 | 212 | static int uc_cshape(int cur, int prev, int next) 213 | { 214 | int c = cur; 215 | int join_prev, join_next; 216 | struct achar *ac = find_achar(c); 217 | if (!ac) /* ignore non-Arabic characters */ 218 | return c; 219 | join_prev = can_join(prev, c); 220 | join_next = can_join(c, next); 221 | if (join_prev && join_next) 222 | c = ac->m; 223 | if (join_prev && !join_next) 224 | c = ac->f; 225 | if (!join_prev && join_next) 226 | c = ac->i; 227 | if (!join_prev && !join_next) 228 | c = ac->c; /* some fonts do not have a glyph for ac->s */ 229 | return c ? c : cur; 230 | } 231 | 232 | /* 233 | * return nonzero for Arabic combining characters 234 | * 235 | * The standard Arabic diacritics: 236 | * + 0x064b: fathatan 237 | * + 0x064c: dammatan 238 | * + 0x064d: kasratan 239 | * + 0x064e: fatha 240 | * + 0x064f: damma 241 | * + 0x0650: kasra 242 | * + 0x0651: shadda 243 | * + 0x0652: sukun 244 | * + 0x0653: madda above 245 | * + 0x0654: hamza above 246 | * + 0x0655: hamza below 247 | * + 0x0670: superscript alef 248 | */ 249 | int uc_acomb(int c) 250 | { 251 | return (c >= 0x064b && c <= 0x0655) || /* the standard diacritics */ 252 | (c >= 0xfc5e && c <= 0xfc63) || /* shadda ligatures */ 253 | c == 0x0670; /* superscript alef */ 254 | } 255 | 256 | static void uc_cput(char *d, int c) 257 | { 258 | int l = 0; 259 | if (c > 0xffff) { 260 | *d++ = 0xf0 | (c >> 18); 261 | l = 3; 262 | } else if (c > 0x7ff) { 263 | *d++ = 0xe0 | (c >> 12); 264 | l = 2; 265 | } else if (c > 0x7f) { 266 | *d++ = 0xc0 | (c >> 6); 267 | l = 1; 268 | } else { 269 | *d++ = c; 270 | } 271 | while (l--) 272 | *d++ = 0x80 | ((c >> (l * 6)) & 0x3f); 273 | *d = '\0'; 274 | } 275 | 276 | /* shape the given arabic character; returns a static buffer */ 277 | char *uc_shape(char *beg, char *s, int c) 278 | { 279 | static char out[16]; 280 | char *r; 281 | int tmp, l, prev = 0, next = 0; 282 | if (!c || !UC_R2L(c)) 283 | return NULL; 284 | r = s; 285 | while (r > beg) { 286 | r = uc_beg(beg, r - 1); 287 | uc_code(tmp, r, l) 288 | if (!uc_acomb(tmp)) { 289 | uc_code(prev, r, l) 290 | break; 291 | } 292 | } 293 | r = s; 294 | while (uc_len(r)) { 295 | r += uc_len(r); 296 | uc_code(tmp, r, l) 297 | if (!uc_acomb(tmp)) { 298 | uc_code(next, r, l) 299 | break; 300 | } 301 | } 302 | uc_cput(out, uc_cshape(c, prev, next)); 303 | return out; 304 | } 305 | 306 | static int dwchars[][2] = { 307 | {0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x2329}, {0x232A, 0x232A}, 308 | {0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3}, {0x25FD, 0x25FE}, 309 | {0x2614, 0x2615}, {0x2648, 0x2653}, {0x267F, 0x267F}, {0x2693, 0x2693}, 310 | {0x26A1, 0x26A1}, {0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5}, 311 | {0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA}, {0x26F2, 0x26F3}, 312 | {0x26F5, 0x26F5}, {0x26FA, 0x26FA}, {0x26FD, 0x26FD}, {0x2705, 0x2705}, 313 | {0x270A, 0x270B}, {0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E}, 314 | {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797}, {0x27B0, 0x27B0}, 315 | {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C}, {0x2B50, 0x2B50}, {0x2B55, 0x2B55}, 316 | {0x2E80, 0x2E99}, {0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFF}, 317 | {0x3000, 0x3000}, {0x3001, 0x3003}, {0x3004, 0x3004}, {0x3005, 0x3005}, 318 | {0x3006, 0x3006}, {0x3007, 0x3007}, {0x3008, 0x3008}, {0x3009, 0x3009}, 319 | {0x300A, 0x300A}, {0x300B, 0x300B}, {0x300C, 0x300C}, {0x300D, 0x300D}, 320 | {0x300E, 0x300E}, {0x300F, 0x300F}, {0x3010, 0x3010}, {0x3011, 0x3011}, 321 | {0x3012, 0x3013}, {0x3014, 0x3014}, {0x3015, 0x3015}, {0x3016, 0x3016}, 322 | {0x3017, 0x3017}, {0x3018, 0x3018}, {0x3019, 0x3019}, {0x301A, 0x301A}, 323 | {0x301B, 0x301B}, {0x301C, 0x301C}, {0x301D, 0x301D}, {0x301E, 0x301F}, 324 | {0x3020, 0x3020}, {0x3021, 0x3029}, {0x302A, 0x302D}, {0x302E, 0x302F}, 325 | {0x3030, 0x3030}, {0x3031, 0x3035}, {0x3036, 0x3037}, {0x3038, 0x303A}, 326 | {0x303B, 0x303B}, {0x303C, 0x303C}, {0x303D, 0x303D}, {0x303E, 0x303E}, 327 | {0x3041, 0x3096}, {0x3099, 0x309A}, {0x309B, 0x309C}, {0x309D, 0x309E}, 328 | {0x309F, 0x309F}, {0x30A0, 0x30A0}, {0x30A1, 0x30FA}, {0x30FB, 0x30FB}, 329 | {0x30FC, 0x30FE}, {0x30FF, 0x30FF}, {0x3105, 0x312F}, {0x3131, 0x318E}, 330 | {0x3190, 0x3191}, {0x3192, 0x3195}, {0x3196, 0x319F}, {0x31A0, 0x31BF}, 331 | {0x31C0, 0x31E3}, {0x31EF, 0x31EF}, {0x31F0, 0x31FF}, {0x3200, 0x321E}, 332 | {0x3220, 0x3229}, {0x322A, 0x3247}, {0x3250, 0x3250}, {0x3251, 0x325F}, 333 | {0x3260, 0x327F}, {0x3280, 0x3289}, {0x328A, 0x32B0}, {0x32B1, 0x32BF}, 334 | {0x32C0, 0x32FF}, {0x3300, 0x33FF}, {0x3400, 0x4DBF}, {0x4E00, 0x9FFF}, 335 | {0xA000, 0xA014}, {0xA015, 0xA015}, {0xA016, 0xA48C}, {0xA490, 0xA4C6}, 336 | {0xA960, 0xA97C}, {0xAC00, 0xD7A3}, {0xF900, 0xFA6D}, {0xFA6E, 0xFA6F}, 337 | {0xFA70, 0xFAD9}, {0xFADA, 0xFAFF}, {0xFE10, 0xFE16}, {0xFE17, 0xFE17}, 338 | {0xFE18, 0xFE18}, {0xFE19, 0xFE19}, {0xFE30, 0xFE30}, {0xFE31, 0xFE32}, 339 | {0xFE33, 0xFE34}, {0xFE35, 0xFE35}, {0xFE36, 0xFE36}, {0xFE37, 0xFE37}, 340 | {0xFE38, 0xFE38}, {0xFE39, 0xFE39}, {0xFE3A, 0xFE3A}, {0xFE3B, 0xFE3B}, 341 | {0xFE3C, 0xFE3C}, {0xFE3D, 0xFE3D}, {0xFE3E, 0xFE3E}, {0xFE3F, 0xFE3F}, 342 | {0xFE40, 0xFE40}, {0xFE41, 0xFE41}, {0xFE42, 0xFE42}, {0xFE43, 0xFE43}, 343 | {0xFE44, 0xFE44}, {0xFE45, 0xFE46}, {0xFE47, 0xFE47}, {0xFE48, 0xFE48}, 344 | {0xFE49, 0xFE4C}, {0xFE4D, 0xFE4F}, {0xFE50, 0xFE52}, {0xFE54, 0xFE57}, 345 | {0xFE58, 0xFE58}, {0xFE59, 0xFE59}, {0xFE5A, 0xFE5A}, {0xFE5B, 0xFE5B}, 346 | {0xFE5C, 0xFE5C}, {0xFE5D, 0xFE5D}, {0xFE5E, 0xFE5E}, {0xFE5F, 0xFE61}, 347 | {0xFE62, 0xFE62}, {0xFE63, 0xFE63}, {0xFE64, 0xFE66}, {0xFE68, 0xFE68}, 348 | {0xFE69, 0xFE69}, {0xFE6A, 0xFE6B}, {0xFF01, 0xFF03}, {0xFF04, 0xFF04}, 349 | {0xFF05, 0xFF07}, {0xFF08, 0xFF08}, {0xFF09, 0xFF09}, {0xFF0A, 0xFF0A}, 350 | {0xFF0B, 0xFF0B}, {0xFF0C, 0xFF0C}, {0xFF0D, 0xFF0D}, {0xFF0E, 0xFF0F}, 351 | {0xFF10, 0xFF19}, {0xFF1A, 0xFF1B}, {0xFF1C, 0xFF1E}, {0xFF1F, 0xFF20}, 352 | {0xFF21, 0xFF3A}, {0xFF3B, 0xFF3B}, {0xFF3C, 0xFF3C}, {0xFF3D, 0xFF3D}, 353 | {0xFF3E, 0xFF3E}, {0xFF3F, 0xFF3F}, {0xFF40, 0xFF40}, {0xFF41, 0xFF5A}, 354 | {0xFF5B, 0xFF5B}, {0xFF5C, 0xFF5C}, {0xFF5D, 0xFF5D}, {0xFF5E, 0xFF5E}, 355 | {0xFF5F, 0xFF5F}, {0xFF60, 0xFF60}, {0xFFE0, 0xFFE1}, {0xFFE2, 0xFFE2}, 356 | {0xFFE3, 0xFFE3}, {0xFFE4, 0xFFE4}, {0xFFE5, 0xFFE6}, {0x16FE0, 0x16FE1}, 357 | {0x16FE2, 0x16FE2}, {0x16FE3, 0x16FE3}, {0x16FE4, 0x16FE4}, {0x16FF0, 0x16FF1}, 358 | {0x17000, 0x187F7}, {0x18800, 0x18AFF}, {0x18B00, 0x18CD5}, {0x18D00, 0x18D08}, 359 | {0x1AFF0, 0x1AFF3}, {0x1AFF5, 0x1AFFB}, {0x1AFFD, 0x1AFFE}, {0x1B000, 0x1B0FF}, 360 | {0x1B100, 0x1B122}, {0x1B132, 0x1B132}, {0x1B150, 0x1B152}, {0x1B155, 0x1B155}, 361 | {0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, {0x1F004, 0x1F004}, {0x1F0CF, 0x1F0CF}, 362 | {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A}, {0x1F200, 0x1F202}, {0x1F210, 0x1F23B}, 363 | {0x1F240, 0x1F248}, {0x1F250, 0x1F251}, {0x1F260, 0x1F265}, {0x1F300, 0x1F320}, 364 | {0x1F32D, 0x1F335}, {0x1F337, 0x1F37C}, {0x1F37E, 0x1F393}, {0x1F3A0, 0x1F3CA}, 365 | {0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0}, {0x1F3F4, 0x1F3F4}, {0x1F3F8, 0x1F3FA}, 366 | {0x1F3FB, 0x1F3FF}, {0x1F400, 0x1F43E}, {0x1F440, 0x1F440}, {0x1F442, 0x1F4FC}, 367 | {0x1F4FF, 0x1F53D}, {0x1F54B, 0x1F54E}, {0x1F550, 0x1F567}, {0x1F57A, 0x1F57A}, 368 | {0x1F595, 0x1F596}, {0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F5FF}, {0x1F600, 0x1F64F}, 369 | {0x1F680, 0x1F6C5}, {0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2}, {0x1F6D5, 0x1F6D7}, 370 | {0x1F6DC, 0x1F6DF}, {0x1F6EB, 0x1F6EC}, {0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB}, 371 | {0x1F7F0, 0x1F7F0}, {0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F9FF}, 372 | {0x1FA70, 0x1FA7C}, {0x1FA80, 0x1FA88}, {0x1FA90, 0x1FABD}, {0x1FABF, 0x1FAC5}, 373 | {0x1FACE, 0x1FADB}, {0x1FAE0, 0x1FAE8}, {0x1FAF0, 0x1FAF8}, {0x20000, 0x2A6DF}, 374 | {0x2A6E0, 0x2A6FF}, {0x2A700, 0x2B739}, {0x2B73A, 0x2B73F}, {0x2B740, 0x2B81D}, 375 | {0x2B81E, 0x2B81F}, {0x2B820, 0x2CEA1}, {0x2CEA2, 0x2CEAF}, {0x2CEB0, 0x2EBE0}, 376 | {0x2EBE1, 0x2EBEF}, {0x2EBF0, 0x2EE5D}, {0x2EE5E, 0x2F7FF}, {0x2F800, 0x2FA1D}, 377 | {0x2FA1E, 0x2FA1F}, {0x2FA20, 0x2FFFD}, {0x30000, 0x3134A}, {0x3134B, 0x3134F}, 378 | {0x31350, 0x323AF}, {0x323B0, 0x3FFFD}, 379 | }; 380 | 381 | static int zwchars[][2] = { 382 | {0xAD, 0xAD}, {0x300, 0x36F}, {0x483, 0x489}, {0x591, 0x5BD}, 383 | {0x5BF, 0x5BF}, {0x5C1, 0x5C2}, {0x5C4, 0x5C5}, {0x5C7, 0x5C7}, 384 | {0x600, 0x605}, {0x610, 0x61A}, {0x61C, 0x61C}, {0x64B, 0x65F}, 385 | {0x670, 0x670}, {0x6D6, 0x6DD}, {0x6DF, 0x6E4}, {0x6E7, 0x6E8}, 386 | {0x6EA, 0x6ED}, {0x70F, 0x70F}, {0x711, 0x711}, {0x730, 0x74A}, 387 | {0x7A6, 0x7B0}, {0x7EB, 0x7F3}, {0x7FD, 0x7FD}, {0x816, 0x819}, 388 | {0x81B, 0x823}, {0x825, 0x827}, {0x829, 0x82D}, {0x859, 0x85B}, 389 | {0x890, 0x891}, {0x898, 0x89F}, {0x8CA, 0x902}, {0x93A, 0x93A}, 390 | {0x93C, 0x93C}, {0x941, 0x948}, {0x94D, 0x94D}, {0x951, 0x957}, 391 | {0x962, 0x963}, {0x981, 0x981}, {0x9BC, 0x9BC}, {0x9C1, 0x9C4}, 392 | {0x9CD, 0x9CD}, {0x9E2, 0x9E3}, {0x9FE, 0x9FE}, {0xA01, 0xA02}, 393 | {0xA3C, 0xA3C}, {0xA41, 0xA42}, {0xA47, 0xA48}, {0xA4B, 0xA4D}, 394 | {0xA51, 0xA51}, {0xA70, 0xA71}, {0xA75, 0xA75}, {0xA81, 0xA82}, 395 | {0xABC, 0xABC}, {0xAC1, 0xAC5}, {0xAC7, 0xAC8}, {0xACD, 0xACD}, 396 | {0xAE2, 0xAE3}, {0xAFA, 0xAFF}, {0xB01, 0xB01}, {0xB3C, 0xB3C}, 397 | {0xB3F, 0xB3F}, {0xB41, 0xB44}, {0xB4D, 0xB4D}, {0xB55, 0xB56}, 398 | {0xB62, 0xB63}, {0xB82, 0xB82}, {0xBC0, 0xBC0}, {0xBCD, 0xBCD}, 399 | {0xC00, 0xC00}, {0xC04, 0xC04}, {0xC3C, 0xC3C}, {0xC3E, 0xC40}, 400 | {0xC46, 0xC48}, {0xC4A, 0xC4D}, {0xC55, 0xC56}, {0xC62, 0xC63}, 401 | {0xC81, 0xC81}, {0xCBC, 0xCBC}, {0xCBF, 0xCBF}, {0xCC6, 0xCC6}, 402 | {0xCCC, 0xCCD}, {0xCE2, 0xCE3}, {0xD00, 0xD01}, {0xD3B, 0xD3C}, 403 | {0xD41, 0xD44}, {0xD4D, 0xD4D}, {0xD62, 0xD63}, {0xD81, 0xD81}, 404 | {0xDCA, 0xDCA}, {0xDD2, 0xDD4}, {0xDD6, 0xDD6}, {0xE31, 0xE31}, 405 | {0xE34, 0xE3A}, {0xE47, 0xE4E}, {0xEB1, 0xEB1}, {0xEB4, 0xEBC}, 406 | {0xEC8, 0xECE}, {0xF18, 0xF19}, {0xF35, 0xF35}, {0xF37, 0xF37}, 407 | {0xF39, 0xF39}, {0xF71, 0xF7E}, {0xF80, 0xF84}, {0xF86, 0xF87}, 408 | {0xF8D, 0xF97}, {0xF99, 0xFBC}, {0xFC6, 0xFC6}, {0x102D, 0x1030}, 409 | {0x1032, 0x1037}, {0x1039, 0x103A}, {0x103D, 0x103E}, {0x1058, 0x1059}, 410 | {0x105E, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082}, {0x1085, 0x1086}, 411 | {0x108D, 0x108D}, {0x109D, 0x109D}, {0x1160, 0x11FF}, {0x135D, 0x135F}, 412 | {0x1712, 0x1714}, {0x1732, 0x1733}, {0x1752, 0x1753}, {0x1772, 0x1773}, 413 | {0x17B4, 0x17B5}, {0x17B7, 0x17BD}, {0x17C6, 0x17C6}, {0x17C9, 0x17D3}, 414 | {0x17DD, 0x17DD}, {0x180B, 0x180F}, {0x1885, 0x1886}, {0x18A9, 0x18A9}, 415 | {0x1920, 0x1922}, {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193B}, 416 | {0x1A17, 0x1A18}, {0x1A1B, 0x1A1B}, {0x1A56, 0x1A56}, {0x1A58, 0x1A5E}, 417 | {0x1A60, 0x1A60}, {0x1A62, 0x1A62}, {0x1A65, 0x1A6C}, {0x1A73, 0x1A7C}, 418 | {0x1A7F, 0x1A7F}, {0x1AB0, 0x1ACE}, {0x1B00, 0x1B03}, {0x1B34, 0x1B34}, 419 | {0x1B36, 0x1B3A}, {0x1B3C, 0x1B3C}, {0x1B42, 0x1B42}, {0x1B6B, 0x1B73}, 420 | {0x1B80, 0x1B81}, {0x1BA2, 0x1BA5}, {0x1BA8, 0x1BA9}, {0x1BAB, 0x1BAD}, 421 | {0x1BE6, 0x1BE6}, {0x1BE8, 0x1BE9}, {0x1BED, 0x1BED}, {0x1BEF, 0x1BF1}, 422 | {0x1C2C, 0x1C33}, {0x1C36, 0x1C37}, {0x1CD0, 0x1CD2}, {0x1CD4, 0x1CE0}, 423 | {0x1CE2, 0x1CE8}, {0x1CED, 0x1CED}, {0x1CF4, 0x1CF4}, {0x1CF8, 0x1CF9}, 424 | {0x1DC0, 0x1DFF}, {0x200B, 0x200F}, {0x202A, 0x202E}, {0x2060, 0x2064}, 425 | {0x2066, 0x206F}, {0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2D7F, 0x2D7F}, 426 | {0x2DE0, 0x2DFF}, {0x302A, 0x302D}, {0x3099, 0x309A}, {0xA66F, 0xA672}, 427 | {0xA674, 0xA67D}, {0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA802, 0xA802}, 428 | {0xA806, 0xA806}, {0xA80B, 0xA80B}, {0xA825, 0xA826}, {0xA82C, 0xA82C}, 429 | {0xA8C4, 0xA8C5}, {0xA8E0, 0xA8F1}, {0xA8FF, 0xA8FF}, {0xA926, 0xA92D}, 430 | {0xA947, 0xA951}, {0xA980, 0xA982}, {0xA9B3, 0xA9B3}, {0xA9B6, 0xA9B9}, 431 | {0xA9BC, 0xA9BD}, {0xA9E5, 0xA9E5}, {0xAA29, 0xAA2E}, {0xAA31, 0xAA32}, 432 | {0xAA35, 0xAA36}, {0xAA43, 0xAA43}, {0xAA4C, 0xAA4C}, {0xAA7C, 0xAA7C}, 433 | {0xAAB0, 0xAAB0}, {0xAAB2, 0xAAB4}, {0xAAB7, 0xAAB8}, {0xAABE, 0xAABF}, 434 | {0xAAC1, 0xAAC1}, {0xAAEC, 0xAAED}, {0xAAF6, 0xAAF6}, {0xABE5, 0xABE5}, 435 | {0xABE8, 0xABE8}, {0xABED, 0xABED}, {0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, 436 | {0xFB1E, 0xFB1E}, {0xFE00, 0xFE0F}, {0xFE20, 0xFE2F}, {0xFEFF, 0xFEFF}, 437 | {0xFFF9, 0xFFFB}, {0x101FD, 0x101FD}, {0x102E0, 0x102E0}, {0x10376, 0x1037A}, 438 | {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, 439 | {0x10A3F, 0x10A3F}, {0x10AE5, 0x10AE6}, {0x10D24, 0x10D27}, {0x10EAB, 0x10EAC}, 440 | {0x10EFD, 0x10EFF}, {0x10F46, 0x10F50}, {0x10F82, 0x10F85}, {0x11001, 0x11001}, 441 | {0x11038, 0x11046}, {0x11070, 0x11070}, {0x11073, 0x11074}, {0x1107F, 0x11081}, 442 | {0x110B3, 0x110B6}, {0x110B9, 0x110BA}, {0x110BD, 0x110BD}, {0x110C2, 0x110C2}, 443 | {0x110CD, 0x110CD}, {0x11100, 0x11102}, {0x11127, 0x1112B}, {0x1112D, 0x11134}, 444 | {0x11173, 0x11173}, {0x11180, 0x11181}, {0x111B6, 0x111BE}, {0x111C9, 0x111CC}, 445 | {0x111CF, 0x111CF}, {0x1122F, 0x11231}, {0x11234, 0x11234}, {0x11236, 0x11237}, 446 | {0x1123E, 0x1123E}, {0x11241, 0x11241}, {0x112DF, 0x112DF}, {0x112E3, 0x112EA}, 447 | {0x11300, 0x11301}, {0x1133B, 0x1133C}, {0x11340, 0x11340}, {0x11366, 0x1136C}, 448 | {0x11370, 0x11374}, {0x11438, 0x1143F}, {0x11442, 0x11444}, {0x11446, 0x11446}, 449 | {0x1145E, 0x1145E}, {0x114B3, 0x114B8}, {0x114BA, 0x114BA}, {0x114BF, 0x114C0}, 450 | {0x114C2, 0x114C3}, {0x115B2, 0x115B5}, {0x115BC, 0x115BD}, {0x115BF, 0x115C0}, 451 | {0x115DC, 0x115DD}, {0x11633, 0x1163A}, {0x1163D, 0x1163D}, {0x1163F, 0x11640}, 452 | {0x116AB, 0x116AB}, {0x116AD, 0x116AD}, {0x116B0, 0x116B5}, {0x116B7, 0x116B7}, 453 | {0x1171D, 0x1171F}, {0x11722, 0x11725}, {0x11727, 0x1172B}, {0x1182F, 0x11837}, 454 | {0x11839, 0x1183A}, {0x1193B, 0x1193C}, {0x1193E, 0x1193E}, {0x11943, 0x11943}, 455 | {0x119D4, 0x119D7}, {0x119DA, 0x119DB}, {0x119E0, 0x119E0}, {0x11A01, 0x11A0A}, 456 | {0x11A33, 0x11A38}, {0x11A3B, 0x11A3E}, {0x11A47, 0x11A47}, {0x11A51, 0x11A56}, 457 | {0x11A59, 0x11A5B}, {0x11A8A, 0x11A96}, {0x11A98, 0x11A99}, {0x11C30, 0x11C36}, 458 | {0x11C38, 0x11C3D}, {0x11C3F, 0x11C3F}, {0x11C92, 0x11CA7}, {0x11CAA, 0x11CB0}, 459 | {0x11CB2, 0x11CB3}, {0x11CB5, 0x11CB6}, {0x11D31, 0x11D36}, {0x11D3A, 0x11D3A}, 460 | {0x11D3C, 0x11D3D}, {0x11D3F, 0x11D45}, {0x11D47, 0x11D47}, {0x11D90, 0x11D91}, 461 | {0x11D95, 0x11D95}, {0x11D97, 0x11D97}, {0x11EF3, 0x11EF4}, {0x11F00, 0x11F01}, 462 | {0x11F36, 0x11F3A}, {0x11F40, 0x11F40}, {0x11F42, 0x11F42}, {0x13430, 0x13440}, 463 | {0x13447, 0x13455}, {0x16AF0, 0x16AF4}, {0x16B30, 0x16B36}, {0x16F4F, 0x16F4F}, 464 | {0x16F8F, 0x16F92}, {0x16FE4, 0x16FE4}, {0x1BC9D, 0x1BC9E}, {0x1BCA0, 0x1BCA3}, 465 | {0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46}, {0x1D167, 0x1D169}, {0x1D173, 0x1D182}, 466 | {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0x1DA00, 0x1DA36}, 467 | {0x1DA3B, 0x1DA6C}, {0x1DA75, 0x1DA75}, {0x1DA84, 0x1DA84}, {0x1DA9B, 0x1DA9F}, 468 | {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, 469 | {0x1E023, 0x1E024}, {0x1E026, 0x1E02A}, {0x1E08F, 0x1E08F}, {0x1E130, 0x1E136}, 470 | {0x1E2AE, 0x1E2AE}, {0x1E2EC, 0x1E2EF}, {0x1E4EC, 0x1E4EF}, {0x1E8D0, 0x1E8D6}, 471 | {0x1E944, 0x1E94A}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F}, 472 | }; 473 | int zwlen = LEN(zwchars); 474 | int def_zwlen = LEN(zwchars); 475 | 476 | static int bchars[][2] = { 477 | {0x00000, 0x0001f}, {0x00080, 0x0009f}, {0x00300, 0x0036f}, 478 | {0x00379, 0x00379}, {0x00380, 0x00383}, {0x0038d, 0x0038d}, 479 | {0x00483, 0x00489}, {0x00527, 0x00530}, {0x00558, 0x00558}, 480 | {0x00588, 0x00588}, {0x0058c, 0x005bd}, {0x005c1, 0x005c2}, 481 | {0x005c5, 0x005c5}, {0x005c8, 0x005cf}, {0x005ec, 0x005ef}, 482 | {0x005f6, 0x00605}, {0x00611, 0x0061a}, {0x0061d, 0x0061d}, 483 | {0x0064b, 0x0065f}, {0x006d6, 0x006e4}, {0x006e8, 0x006e8}, 484 | {0x006eb, 0x006ed}, {0x0070f, 0x0070f}, {0x00730, 0x0074c}, 485 | {0x007a7, 0x007b0}, {0x007b3, 0x007bf}, {0x007ec, 0x007f3}, 486 | {0x007fc, 0x007ff}, {0x00817, 0x00819}, {0x0081c, 0x00823}, 487 | {0x00826, 0x00827}, {0x0082a, 0x0082f}, {0x00840, 0x00903}, 488 | {0x0093b, 0x0093c}, {0x0093f, 0x0094f}, {0x00952, 0x00957}, 489 | {0x00963, 0x00963}, {0x00974, 0x00978}, {0x00981, 0x00984}, 490 | {0x0098e, 0x0098e}, {0x00992, 0x00992}, {0x009b1, 0x009b1}, 491 | {0x009b4, 0x009b5}, {0x009bb, 0x009bc}, {0x009bf, 0x009cd}, 492 | {0x009d0, 0x009db}, {0x009e2, 0x009e5}, {0x009fd, 0x00a04}, 493 | {0x00a0c, 0x00a0e}, {0x00a12, 0x00a12}, {0x00a31, 0x00a31}, 494 | {0x00a37, 0x00a37}, {0x00a3b, 0x00a58}, {0x00a5f, 0x00a65}, 495 | {0x00a71, 0x00a71}, {0x00a76, 0x00a84}, {0x00a92, 0x00a92}, 496 | {0x00ab1, 0x00ab1}, {0x00aba, 0x00abc}, {0x00abf, 0x00acf}, 497 | {0x00ad2, 0x00adf}, {0x00ae3, 0x00ae5}, {0x00af2, 0x00b04}, 498 | {0x00b0e, 0x00b0e}, {0x00b12, 0x00b12}, {0x00b31, 0x00b31}, 499 | {0x00b3a, 0x00b3c}, {0x00b3f, 0x00b5b}, {0x00b62, 0x00b65}, 500 | {0x00b73, 0x00b82}, {0x00b8b, 0x00b8d}, {0x00b96, 0x00b98}, 501 | {0x00b9d, 0x00b9d}, {0x00ba1, 0x00ba2}, {0x00ba6, 0x00ba7}, 502 | {0x00bac, 0x00bad}, {0x00bbb, 0x00bcf}, {0x00bd2, 0x00be5}, 503 | {0x00bfc, 0x00c04}, {0x00c11, 0x00c11}, {0x00c34, 0x00c34}, 504 | {0x00c3b, 0x00c3c}, {0x00c3f, 0x00c57}, {0x00c5b, 0x00c5f}, 505 | {0x00c63, 0x00c65}, {0x00c71, 0x00c77}, {0x00c81, 0x00c84}, 506 | {0x00c91, 0x00c91}, {0x00cb4, 0x00cb4}, {0x00cbb, 0x00cbc}, 507 | {0x00cbf, 0x00cdd}, {0x00ce2, 0x00ce5}, {0x00cf3, 0x00d04}, 508 | {0x00d11, 0x00d11}, {0x00d3a, 0x00d3c}, {0x00d3f, 0x00d5f}, 509 | {0x00d63, 0x00d65}, {0x00d77, 0x00d78}, {0x00d81, 0x00d84}, 510 | {0x00d98, 0x00d99}, {0x00dbc, 0x00dbc}, {0x00dbf, 0x00dbf}, 511 | {0x00dc8, 0x00df3}, {0x00df6, 0x00e00}, {0x00e34, 0x00e3e}, 512 | {0x00e48, 0x00e4e}, {0x00e5d, 0x00e80}, {0x00e85, 0x00e86}, 513 | {0x00e8b, 0x00e8c}, {0x00e8f, 0x00e93}, {0x00ea0, 0x00ea0}, 514 | {0x00ea6, 0x00ea6}, {0x00ea9, 0x00ea9}, {0x00eb1, 0x00eb1}, 515 | {0x00eb5, 0x00ebc}, {0x00ebf, 0x00ebf}, {0x00ec7, 0x00ecf}, 516 | {0x00edb, 0x00edb}, {0x00edf, 0x00eff}, {0x00f19, 0x00f19}, 517 | {0x00f37, 0x00f37}, {0x00f3e, 0x00f3f}, {0x00f6d, 0x00f84}, 518 | {0x00f87, 0x00f87}, {0x00f8d, 0x00fbd}, {0x00fcd, 0x00fcd}, 519 | {0x00fda, 0x00fff}, {0x0102c, 0x0103e}, {0x01057, 0x01059}, 520 | {0x0105f, 0x01060}, {0x01063, 0x01064}, {0x01068, 0x0106d}, 521 | {0x01072, 0x01074}, {0x01083, 0x0108d}, {0x0109a, 0x0109d}, 522 | {0x010c7, 0x010cf}, {0x010fe, 0x010ff}, {0x0124e, 0x0124f}, 523 | {0x01259, 0x01259}, {0x0125f, 0x0125f}, {0x0128e, 0x0128f}, 524 | {0x012b6, 0x012b7}, {0x012c1, 0x012c1}, {0x012c7, 0x012c7}, 525 | {0x01311, 0x01311}, {0x01317, 0x01317}, {0x0135c, 0x0135f}, 526 | {0x0137e, 0x0137f}, {0x0139b, 0x0139f}, {0x013f6, 0x013ff}, 527 | {0x0169e, 0x0169f}, {0x016f2, 0x016ff}, {0x01712, 0x0171f}, 528 | {0x01733, 0x01734}, {0x01738, 0x0173f}, {0x01753, 0x0175f}, 529 | {0x01771, 0x0177f}, {0x017b5, 0x017d3}, {0x017de, 0x017df}, 530 | {0x017eb, 0x017ef}, {0x017fb, 0x017ff}, {0x0180c, 0x0180d}, 531 | {0x0181a, 0x0181f}, {0x01879, 0x0187f}, {0x018ab, 0x018af}, 532 | {0x018f7, 0x018ff}, {0x0191e, 0x0193f}, {0x01942, 0x01943}, 533 | {0x0196f, 0x0196f}, {0x01976, 0x0197f}, {0x019ad, 0x019c0}, 534 | {0x019c9, 0x019cf}, {0x019dc, 0x019dd}, {0x01a18, 0x01a1d}, 535 | {0x01a56, 0x01a7f}, {0x01a8b, 0x01a8f}, {0x01a9b, 0x01a9f}, 536 | {0x01aaf, 0x01b04}, {0x01b35, 0x01b44}, {0x01b4d, 0x01b4f}, 537 | {0x01b6c, 0x01b73}, {0x01b7e, 0x01b82}, {0x01ba2, 0x01bad}, 538 | {0x01bbb, 0x01bff}, {0x01c25, 0x01c3a}, {0x01c4b, 0x01c4c}, 539 | {0x01c81, 0x01cd2}, {0x01cd5, 0x01ce8}, {0x01cf2, 0x01cff}, 540 | {0x01dc1, 0x01dff}, {0x01f17, 0x01f17}, {0x01f1f, 0x01f1f}, 541 | {0x01f47, 0x01f47}, {0x01f4f, 0x01f4f}, {0x01f5a, 0x01f5a}, 542 | {0x01f5e, 0x01f5e}, {0x01f7f, 0x01f7f}, {0x01fc5, 0x01fc5}, 543 | {0x01fd5, 0x01fd5}, {0x01ff0, 0x01ff1}, {0x01fff, 0x01fff}, 544 | {0x0200c, 0x0200f}, {0x02029, 0x0202e}, {0x02061, 0x0206f}, 545 | {0x02073, 0x02073}, {0x02095, 0x0209f}, {0x020ba, 0x020ff}, 546 | {0x0218b, 0x0218f}, {0x023ea, 0x023ff}, {0x02428, 0x0243f}, 547 | {0x0244c, 0x0245f}, {0x026e2, 0x026e2}, {0x026e5, 0x026e7}, 548 | {0x02705, 0x02705}, {0x0270b, 0x0270b}, {0x0274c, 0x0274c}, 549 | {0x02753, 0x02755}, {0x02760, 0x02760}, {0x02796, 0x02797}, 550 | {0x027bf, 0x027bf}, {0x027cd, 0x027cf}, {0x02b4e, 0x02b4f}, 551 | {0x02b5b, 0x02bff}, {0x02c5f, 0x02c5f}, {0x02cf0, 0x02cf8}, 552 | {0x02d27, 0x02d2f}, {0x02d67, 0x02d6e}, {0x02d71, 0x02d7f}, 553 | {0x02d98, 0x02d9f}, {0x02daf, 0x02daf}, {0x02dbf, 0x02dbf}, 554 | {0x02dcf, 0x02dcf}, {0x02ddf, 0x02dff}, {0x02e33, 0x02e7f}, 555 | {0x02ef4, 0x02eff}, {0x02fd7, 0x02fef}, {0x02ffd, 0x02fff}, 556 | {0x0302b, 0x0302f}, {0x03097, 0x0309a}, {0x03101, 0x03104}, 557 | {0x0312f, 0x03130}, {0x031b8, 0x031bf}, {0x031e5, 0x031ef}, 558 | {0x032ff, 0x032ff}, {0x04db7, 0x04dbf}, {0x09fcd, 0x09fff}, 559 | {0x0a48e, 0x0a48f}, {0x0a4c8, 0x0a4cf}, {0x0a62d, 0x0a63f}, 560 | {0x0a661, 0x0a661}, {0x0a670, 0x0a672}, {0x0a675, 0x0a67d}, 561 | {0x0a699, 0x0a69f}, {0x0a6f1, 0x0a6f1}, {0x0a6f9, 0x0a6ff}, 562 | {0x0a78e, 0x0a7fa}, {0x0a806, 0x0a806}, {0x0a823, 0x0a827}, 563 | {0x0a82d, 0x0a82f}, {0x0a83b, 0x0a83f}, {0x0a879, 0x0a881}, 564 | {0x0a8b5, 0x0a8cd}, {0x0a8db, 0x0a8f1}, {0x0a8fd, 0x0a8ff}, 565 | {0x0a927, 0x0a92d}, {0x0a948, 0x0a95e}, {0x0a97e, 0x0a983}, 566 | {0x0a9b4, 0x0a9c0}, {0x0a9da, 0x0a9dd}, {0x0a9e1, 0x0a9ff}, 567 | {0x0aa2a, 0x0aa3f}, {0x0aa4c, 0x0aa4f}, {0x0aa5b, 0x0aa5b}, 568 | {0x0aa7c, 0x0aa7f}, {0x0aab2, 0x0aab4}, {0x0aab8, 0x0aab8}, 569 | {0x0aabf, 0x0aabf}, {0x0aac3, 0x0aada}, {0x0aae1, 0x0abbf}, 570 | {0x0abe4, 0x0abea}, {0x0abed, 0x0abef}, {0x0abfb, 0x0abff}, 571 | {0x0d7a5, 0x0d7af}, {0x0d7c8, 0x0d7ca}, {0x0d7fd, 0x0f8ff}, 572 | {0x0fa2f, 0x0fa2f}, {0x0fa6f, 0x0fa6f}, {0x0fadb, 0x0faff}, 573 | {0x0fb08, 0x0fb12}, {0x0fb19, 0x0fb1c}, {0x0fb37, 0x0fb37}, 574 | {0x0fb3f, 0x0fb3f}, {0x0fb45, 0x0fb45}, {0x0fbb3, 0x0fbd2}, 575 | {0x0fd41, 0x0fd4f}, {0x0fd91, 0x0fd91}, {0x0fdc9, 0x0fdef}, 576 | {0x0fdff, 0x0fe0f}, {0x0fe1b, 0x0fe2f}, {0x0fe67, 0x0fe67}, 577 | {0x0fe6d, 0x0fe6f}, {0x0fefd, 0x0ff00}, {0x0ffc0, 0x0ffc1}, 578 | {0x0ffc9, 0x0ffc9}, {0x0ffd1, 0x0ffd1}, {0x0ffd9, 0x0ffd9}, 579 | {0x0ffde, 0x0ffdf}, {0x0ffef, 0x0fffb}, {0x0ffff, 0x0ffff}, 580 | {0x10027, 0x10027}, {0x1003e, 0x1003e}, {0x1004f, 0x1004f}, 581 | {0x1005f, 0x1007f}, {0x100fc, 0x100ff}, {0x10104, 0x10106}, 582 | {0x10135, 0x10136}, {0x1018c, 0x1018f}, {0x1019d, 0x101cf}, 583 | {0x101fe, 0x1027f}, {0x1029e, 0x1029f}, {0x102d2, 0x102ff}, 584 | {0x10324, 0x1032f}, {0x1034c, 0x1037f}, {0x103c4, 0x103c7}, 585 | {0x103d7, 0x103ff}, {0x1049f, 0x1049f}, {0x104ab, 0x107ff}, 586 | {0x10807, 0x10807}, {0x10836, 0x10836}, {0x1083a, 0x1083b}, 587 | {0x1083e, 0x1083e}, {0x10860, 0x108ff}, {0x1091d, 0x1091e}, 588 | {0x1093b, 0x1093e}, {0x10941, 0x109ff}, {0x10a02, 0x10a0f}, 589 | {0x10a18, 0x10a18}, {0x10a35, 0x10a3f}, {0x10a49, 0x10a4f}, 590 | {0x10a5a, 0x10a5f}, {0x10a81, 0x10aff}, {0x10b37, 0x10b38}, 591 | {0x10b57, 0x10b57}, {0x10b74, 0x10b77}, {0x10b81, 0x10bff}, 592 | {0x10c4a, 0x10e5f}, {0x10e80, 0x11082}, {0x110b1, 0x110ba}, 593 | {0x110c2, 0x11fff}, {0x12370, 0x123ff}, {0x12464, 0x1246f}, 594 | {0x12475, 0x12fff}, {0x13430, 0x1cfff}, {0x1d0f7, 0x1d0ff}, 595 | {0x1d128, 0x1d128}, {0x1d166, 0x1d169}, {0x1d16e, 0x1d182}, 596 | {0x1d186, 0x1d18b}, {0x1d1ab, 0x1d1ad}, {0x1d1df, 0x1d1ff}, 597 | {0x1d243, 0x1d244}, {0x1d247, 0x1d2ff}, {0x1d358, 0x1d35f}, 598 | {0x1d373, 0x1d3ff}, {0x1d49d, 0x1d49d}, {0x1d4a1, 0x1d4a1}, 599 | {0x1d4a4, 0x1d4a4}, {0x1d4a8, 0x1d4a8}, {0x1d4ba, 0x1d4ba}, 600 | {0x1d4c4, 0x1d4c4}, {0x1d50b, 0x1d50c}, {0x1d51d, 0x1d51d}, 601 | {0x1d53f, 0x1d53f}, {0x1d547, 0x1d549}, {0x1d6a6, 0x1d6a7}, 602 | {0x1d7cd, 0x1d7cd}, {0x1d801, 0x1efff}, {0x1f02d, 0x1f02f}, 603 | {0x1f095, 0x1f0ff}, {0x1f10c, 0x1f10f}, {0x1f130, 0x1f130}, 604 | {0x1f133, 0x1f13c}, {0x1f140, 0x1f141}, {0x1f144, 0x1f145}, 605 | {0x1f148, 0x1f149}, {0x1f150, 0x1f156}, {0x1f159, 0x1f15e}, 606 | {0x1f161, 0x1f178}, {0x1f17d, 0x1f17e}, {0x1f181, 0x1f189}, 607 | {0x1f18f, 0x1f18f}, {0x1f192, 0x1f1ff}, {0x1f202, 0x1f20f}, 608 | {0x1f233, 0x1f23f}, {0x1f24a, 0x1ffff}, {0x2a6d8, 0x2a6ff}, 609 | {0x2b736, 0x2f7ff}, {0x2fa1f, 0x10ffff}, 610 | }; 611 | int bclen = LEN(bchars); 612 | int def_bclen = LEN(bchars); 613 | 614 | static int find(int c, int tab[][2], int n) 615 | { 616 | if (c < tab[0][0] || !n) 617 | return 0; 618 | int m, l = 0; 619 | int h = n - 1; 620 | while (l <= h) { 621 | m = (h + l) / 2; 622 | if (c >= tab[m][0] && c <= tab[m][1]) 623 | return 1; 624 | if (c < tab[m][0]) 625 | h = m - 1; 626 | else 627 | l = m + 1; 628 | } 629 | return 0; 630 | } 631 | 632 | /* double-width characters */ 633 | static int uc_isdw(int c) 634 | { 635 | return find(c, dwchars, LEN(dwchars)); 636 | } 637 | 638 | /* (nonprintable) zero width & combining characters */ 639 | int uc_isbell(int c) 640 | { 641 | return find(c, zwchars, zwlen) || find(c, bchars, bclen); 642 | } 643 | 644 | /* printing width */ 645 | int uc_wid(int c) 646 | { 647 | if (uc_isdw(c)) 648 | return 2; 649 | return zwlen || !find(c, zwchars, def_zwlen); 650 | } 651 | -------------------------------------------------------------------------------- /vi.h: -------------------------------------------------------------------------------- 1 | /* 2 | Nextvi main header 3 | ================== 4 | The purpose of this file is to provide high level overview 5 | of entire Nextvi. Due to absence of any build system some of 6 | these definitions may not be required to successfully compile 7 | Nextvi. They are kept here for your benefit and organization. 8 | If something is listed here, it must be used across multiple 9 | files and thus is never static. 10 | */ 11 | 12 | /* helper macros */ 13 | #define LEN(a) (int)(sizeof(a) / sizeof((a)[0])) 14 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 15 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 16 | /* for debug; printf() but to file */ 17 | #define p(s, ...)\ 18 | {FILE *f = fopen("file", "a");\ 19 | fprintf(f, s, ##__VA_ARGS__);\ 20 | fclose(f);}\ 21 | /* ease up ridiculous global stuffing */ 22 | #define preserve(type, name, value) \ 23 | type tmp##name = name; \ 24 | name = value; \ 25 | 26 | #define restore(name) \ 27 | name = tmp##name; \ 28 | 29 | /* utility funcs */ 30 | void *emalloc(size_t size); 31 | void *erealloc(void *p, size_t size); 32 | int dstrlen(const char *s, char delim); 33 | char *itoa(int n, char s[]); 34 | 35 | /* main functions */ 36 | extern int xgrec; 37 | void vi(int init); 38 | void ex(void); 39 | 40 | /* sbuf string buffer, variable-sized string */ 41 | #define NEXTSZ(o, r) MAX(o * 2, o + r) 42 | typedef struct sbuf { 43 | char *s; /* allocated buffer */ 44 | int s_n; /* length of the string stored in s[] */ 45 | int s_sz; /* size of memory allocated for s[] */ 46 | } sbuf; 47 | 48 | #define sbuf_extend(sb, newsz) \ 49 | { \ 50 | char *s = sb->s; \ 51 | sb->s_sz = newsz; \ 52 | sb->s = emalloc(sb->s_sz); \ 53 | memcpy(sb->s, s, sb->s_n); \ 54 | free(s); \ 55 | } \ 56 | 57 | #define _sbuf_make(sb, newsz, alloc) \ 58 | { \ 59 | alloc; \ 60 | sb->s_sz = newsz; \ 61 | sb->s = emalloc(newsz); \ 62 | sb->s_n = 0; \ 63 | } \ 64 | 65 | #define sbuf_chr(sb, c) \ 66 | { \ 67 | if (sb->s_n + 1 >= sb->s_sz) \ 68 | sbuf_extend(sb, NEXTSZ(sb->s_sz, 1)) \ 69 | sb->s[sb->s_n++] = c; \ 70 | } \ 71 | 72 | #define sbuf_(sb, x, len, func) \ 73 | if (sb->s_n + len >= sb->s_sz) \ 74 | sbuf_extend(sb, NEXTSZ(sb->s_sz, len + 1)) \ 75 | mem##func(sb->s + sb->s_n, x, len); \ 76 | sb->s_n += len; \ 77 | 78 | #define sbuf_smake(sb, newsz) sbuf _##sb, *sb = &_##sb; _sbuf_make(sb, newsz,) 79 | #define sbuf_make(sb, newsz) { _sbuf_make(sb, newsz, sb = emalloc(sizeof(*sb))) } 80 | #define sbuf_free(sb) { free(sb->s); free(sb); } 81 | #define sbuf_set(sb, ch, len) { sbuf_(sb, ch, len, set) } 82 | #define sbuf_mem(sb, s, len) { sbuf_(sb, s, len, cpy) } 83 | #define sbuf_str(sb, s) { const char *p = s; while(*p) sbuf_chr(sb, *p++) } 84 | #define sbuf_cut(sb, len) { sb->s_n = len; } 85 | /* sbuf functions that NULL terminate strings */ 86 | #define sbuf_null(sb) { sb->s[sb->s_n] = '\0'; } 87 | #define sbufn_make(sb, newsz) { sbuf_make(sb, newsz) sbuf_null(sb) } 88 | #define sbufn_set(sb, ch, len) { sbuf_set(sb, ch, len) sbuf_null(sb) } 89 | #define sbufn_mem(sb, s, len) { sbuf_mem(sb, s, len) sbuf_null(sb) } 90 | #define sbufn_str(sb, s) { sbuf_str(sb, s) sbuf_null(sb) } 91 | #define sbufn_cut(sb, len) { sbuf_cut(sb, len) sbuf_null(sb) } 92 | #define sbufn_chr(sb, c) { sbuf_chr(sb, c) sbuf_null(sb) } 93 | #define sbufn_sret(sb) { sbuf_set(sb, '\0', 4) return sb->s; } 94 | 95 | /* regex.c regular expression sets */ 96 | #define REG_ICASE 0x01 97 | #define REG_NEWLINE 0x02 /* Unlike posix, controls termination by '\n' */ 98 | #define REG_NOTBOL 0x04 99 | #define REG_NOTEOL 0x08 100 | typedef struct { 101 | char *rm_so; 102 | char *rm_eo; 103 | } regmatch_t; 104 | struct rcode { 105 | struct rcode **la; /* lookahead expressions */ 106 | int laidx; /* lookahead index */ 107 | int unilen; /* number of integers in insts */ 108 | int len; /* number of atoms/instructions */ 109 | int sub; /* interim val = save count; final val = nsubs size */ 110 | int presub; /* interim val = save count; final val = 1 rsub size */ 111 | int splits; /* number of split insts */ 112 | int sparsesz; /* sdense size */ 113 | int flg; /* stored flags */ 114 | int insts[]; /* re code */ 115 | }; 116 | typedef struct rcode rcode; 117 | /* regular expression set */ 118 | typedef struct { 119 | rcode *regex; /* the combined regular expression */ 120 | int n; /* number of regular expressions in this set */ 121 | int *grp; /* the group assigned to each subgroup */ 122 | int *setgrpcnt; /* number of groups in each regular expression */ 123 | int grpcnt; /* group count */ 124 | } rset; 125 | rset *rset_make(int n, char **pat, int flg); 126 | rset *rset_smake(char *pat, int flg) 127 | { char *ss[1] = {pat}; return rset_make(1, ss, flg); } 128 | int rset_find(rset *re, char *s, int *grps, int flg); 129 | void rset_free(rset *re); 130 | char *re_read(char **src); 131 | 132 | /* lbuf.c line buffer, managing a number of lines */ 133 | #define NMARKS_BASE ('z' - 'a' + 2) 134 | #define NMARKS 32 135 | struct lopt { 136 | char *ins; /* inserted text */ 137 | char *del; /* deleted text */ 138 | int pos, n_ins, n_del; /* modification location */ 139 | int pos_off; /* cursor line offset */ 140 | int seq; /* operation number */ 141 | int *mark, *mark_off; /* saved marks */ 142 | }; 143 | struct linfo { 144 | int len; 145 | int grec; 146 | }; 147 | struct lbuf { 148 | char **ln; /* buffer lines */ 149 | struct lopt *hist; /* buffer history */ 150 | int mark[NMARKS]; /* mark lines */ 151 | int mark_off[NMARKS]; /* mark line offsets */ 152 | int ln_n; /* number of lines in ln[] */ 153 | int ln_sz; /* size of ln[] */ 154 | int useq; /* current operation sequence */ 155 | int hist_sz; /* size of hist[] */ 156 | int hist_n; /* current history head in hist[] */ 157 | int hist_u; /* current undo head in hist[] */ 158 | int useq_zero; /* useq for lbuf_saved() */ 159 | int useq_last; /* useq before hist[] */ 160 | }; 161 | #define lbuf_len(lb) lb->ln_n 162 | #define lbuf_s(ln) ((struct linfo*)(ln - sizeof(struct linfo))) 163 | #define lbuf_i(lb, pos) ((struct linfo*)(lb->ln[pos] - sizeof(struct linfo))) 164 | struct lbuf *lbuf_make(void); 165 | void lbuf_free(struct lbuf *lbuf); 166 | int lbuf_rd(struct lbuf *lbuf, int fd, int beg, int end, int init); 167 | int lbuf_wr(struct lbuf *lbuf, int fd, int beg, int end); 168 | void lbuf_iedit(struct lbuf *lbuf, char *s, int beg, int end, int init); 169 | #define lbuf_edit(lb, s, beg, end) lbuf_iedit(lb, s, beg, end, 0) 170 | char *lbuf_cp(struct lbuf *lbuf, int beg, int end); 171 | char *lbuf_get(struct lbuf *lbuf, int pos); 172 | void lbuf_emark(struct lbuf *lb, struct lopt *lo, int beg, int end); 173 | struct lopt *lbuf_opt(struct lbuf *lb, char *buf, int pos, int n_del, int init); 174 | void lbuf_mark(struct lbuf *lbuf, int mark, int pos, int off); 175 | int lbuf_jump(struct lbuf *lbuf, int mark, int *pos, int *off); 176 | int lbuf_undo(struct lbuf *lbuf); 177 | int lbuf_redo(struct lbuf *lbuf); 178 | int lbuf_modified(struct lbuf *lb); 179 | void lbuf_saved(struct lbuf *lb, int clear); 180 | int lbuf_indents(struct lbuf *lb, int r); 181 | int lbuf_eol(struct lbuf *lb, int r); 182 | int lbuf_findchar(struct lbuf *lb, char *cs, int cmd, int n, int *r, int *o); 183 | int lbuf_search(struct lbuf *lb, rset *re, int dir, int *r, 184 | int *o, int ln_n, int skip); 185 | #define lbuf_dedup(lb, str, n) \ 186 | { for (int i = 0; i < lbuf_len(lb);) { \ 187 | char *s = lbuf_get(lb, i); \ 188 | if (n == lbuf_s(s)->len && !memcmp(str, s, n)) \ 189 | lbuf_edit(lb, NULL, i, i + 1); \ 190 | else \ 191 | i++; \ 192 | }} \ 193 | 194 | /* regions */ 195 | int lbuf_sectionbeg(struct lbuf *lb, int dir, int *row, int *off, int ch); 196 | int lbuf_wordbeg(struct lbuf *lb, int big, int dir, int *row, int *off); 197 | int lbuf_wordend(struct lbuf *lb, int big, int dir, int *row, int *off); 198 | int lbuf_pair(struct lbuf *lb, int *row, int *off); 199 | 200 | /* ren.c rendering lines */ 201 | typedef struct { 202 | char **chrs; 203 | char *s; /* to prevent redundant computations, ensure pointer uniqueness */ 204 | int *wid; 205 | int *col; 206 | int *pos; 207 | int n; 208 | int cmax; 209 | int ctx; 210 | } ren_state; 211 | extern ren_state *rstate; 212 | ren_state *ren_position(char *s); 213 | int ren_next(char *s, int p, int dir); 214 | int ren_eol(char *s, int dir); 215 | int ren_pos(char *s, int off); 216 | int ren_cursor(char *s, int pos); 217 | int ren_noeol(char *s, int p); 218 | int ren_off(char *s, int p); 219 | char *ren_translate(char *s, char *ln); 220 | /* text direction */ 221 | int dir_context(char *s); 222 | void dir_init(void); 223 | /* syntax highlighting */ 224 | #define SYN_BD 0x010000 225 | #define SYN_IT 0x020000 226 | #define SYN_RV 0x040000 227 | #define SYN_FGMK(f) (0x100000 | (f)) 228 | #define SYN_BGMK(b) (0x200000 | ((b) << 8)) 229 | #define SYN_FLG 0xff0000 230 | #define SYN_FGSET(a) ((a) & 0x1000ff) 231 | #define SYN_BGSET(a) ((a) & 0x20ff00) 232 | #define SYN_FG(a) ((a) & 0xff) 233 | #define SYN_BG(a) (((a) >> 8) & 0xff) 234 | extern int syn_reload; 235 | extern int syn_blockhl; 236 | char *syn_setft(char *ft); 237 | void syn_scdir(int scdir); 238 | void syn_highlight(int *att, char *s, int n); 239 | char *syn_filetype(char *path); 240 | int syn_merge(int old, int new); 241 | void syn_reloadft(void); 242 | int syn_findhl(int id); 243 | void syn_addhl(char *reg, int id, int reload); 244 | void syn_init(void); 245 | 246 | /* uc.c utf-8 helper functions */ 247 | extern unsigned char utf8_length[256]; 248 | extern int zwlen, def_zwlen; 249 | extern int bclen, def_bclen; 250 | /* return the length of a utf-8 character */ 251 | #define uc_len(s) utf8_length[(unsigned char)s[0]] 252 | /* the unicode codepoint of the given utf-8 character */ 253 | #define uc_code(dst, s, l) \ 254 | dst = (unsigned char)s[0]; \ 255 | l = utf8_length[dst]; \ 256 | if (l == 1); \ 257 | else if (l == 2) \ 258 | dst = ((dst & 0x1f) << 6) | (s[1] & 0x3f); \ 259 | else if (l == 3) \ 260 | dst = ((dst & 0x0f) << 12) | ((s[1] & 0x3f) << 6) | (s[2] & 0x3f); \ 261 | else if (l == 4) \ 262 | dst = ((dst & 0x07) << 18) | ((s[1] & 0x3f) << 12) | \ 263 | ((s[2] & 0x3f) << 6) | (s[3] & 0x3f); \ 264 | else \ 265 | dst = 0; \ 266 | 267 | int uc_wid(int c); 268 | int uc_slen(char *s); 269 | char *uc_chr(char *s, int off); 270 | int uc_off(char *s, int off); 271 | char *uc_subl(char *s, int beg, int end, int *rlen); 272 | char *uc_sub(char *s, int beg, int end) 273 | { int l; return uc_subl(s, beg, end, &l); } 274 | char *uc_dup(const char *s); 275 | int uc_isspace(char *s); 276 | int uc_isprint(char *s); 277 | int uc_isdigit(char *s); 278 | int uc_isalpha(char *s); 279 | int uc_kind(char *c); 280 | int uc_isbell(int c); 281 | int uc_acomb(int c); 282 | char **uc_chop(char *s, unsigned int *n); 283 | char *uc_beg(char *beg, char *s); 284 | char *uc_shape(char *beg, char *s, int c); 285 | 286 | /* term.c managing the terminal */ 287 | extern sbuf *term_sbuf; 288 | extern int term_record; 289 | extern int xrows, xcols; 290 | extern unsigned int ibuf_pos, ibuf_cnt, ibuf_sz, icmd_pos; 291 | extern unsigned char *ibuf, icmd[4096]; 292 | extern unsigned int texec, tn; 293 | #define term_write(s, n) if (xled) write(1, s, n); 294 | void term_init(void); 295 | void term_done(void); 296 | void term_clean(void); 297 | void term_suspend(void); 298 | void term_chr(int ch); 299 | void term_pos(int r, int c); 300 | void term_kill(void); 301 | void term_room(int n); 302 | int term_read(void); 303 | void term_commit(void); 304 | char *term_att(int att); 305 | void term_push(char *s, unsigned int n); 306 | void term_back(int c); 307 | #define term_dec() ibuf_pos--; icmd_pos--; 308 | #define term_exec(s, n, type) \ 309 | { \ 310 | preserve(int, ibuf_cnt, ibuf_cnt) \ 311 | preserve(int, ibuf_pos, ibuf_cnt) \ 312 | term_push(s, n); \ 313 | preserve(int, texec, type) \ 314 | tn = 0; \ 315 | vi(0); \ 316 | tn = 0; \ 317 | restore(texec) \ 318 | if (xquit > 0) \ 319 | xquit = 0; \ 320 | restore(ibuf_pos) \ 321 | restore(ibuf_cnt) \ 322 | } \ 323 | 324 | /* process management */ 325 | char *cmd_pipe(char *cmd, char *ibuf, int oproc); 326 | char *xgetenv(char* q[]); 327 | 328 | #define TK_ESC (TK_CTL('[')) 329 | #define TK_CTL(x) ((x) & 037) 330 | #define TK_INT(c) ((c) <= 0 || (c) == TK_ESC || (c) == TK_CTL('c')) 331 | 332 | /* led.c line-oriented input and output */ 333 | typedef struct { 334 | char *s; 335 | int off; 336 | int att; 337 | } led_att; 338 | extern sbuf *led_attsb; 339 | char *led_prompt(char *pref, char *post, char *insert, int *kmap); 340 | sbuf *led_input(char *pref, char **post, int row, int lsh); 341 | void led_render(char *s0, int cbeg, int cend); 342 | #define _led_render(msg, row, col, beg, end, kill) \ 343 | { \ 344 | int record = term_record; \ 345 | term_record = 1; \ 346 | term_pos(row, col); \ 347 | kill \ 348 | led_render(msg, beg, end); \ 349 | if (!record) \ 350 | term_commit(); \ 351 | } \ 352 | 353 | #define led_prender(msg, row, col, beg, end) _led_render(msg, row, col, beg, end, /**/) 354 | #define led_crender(msg, row, col, beg, end) _led_render(msg, row, col, beg, end, term_kill();) 355 | #define led_recrender(msg, row, col, beg, end) \ 356 | { rstate->s = NULL; led_crender(msg, row, col, beg, end); } 357 | char *led_read(int *kmap, int c); 358 | int led_pos(char *s, int pos); 359 | void led_done(void); 360 | 361 | /* ex.c ex commands */ 362 | extern char *xregs[256]; 363 | struct buf { 364 | char *ft; /* file type */ 365 | char *path; /* file path */ 366 | struct lbuf *lb; 367 | int plen, row, off, top; 368 | long mtime; /* modification time */ 369 | signed char td; /* text direction */ 370 | }; 371 | extern int xbufcur; 372 | extern struct buf *ex_buf; 373 | extern struct buf *ex_pbuf; 374 | extern struct buf *bufs; 375 | extern struct buf tempbufs[2]; 376 | #define istempbuf(buf) (buf - bufs < 0 || buf - bufs >= xbufcur) 377 | #define EXLEN 512 /* ex line length */ 378 | #define ex_path ex_buf->path 379 | #define ex_ft ex_buf->ft 380 | #define xb ex_buf->lb 381 | #define exbuf_load(buf) \ 382 | xrow = buf->row; \ 383 | xoff = buf->off; \ 384 | xtop = buf->top; \ 385 | xtd = buf->td; \ 386 | 387 | #define exbuf_save(buf) \ 388 | buf->row = xrow; \ 389 | buf->off = xoff; \ 390 | buf->top = xtop; \ 391 | buf->td = xtd; \ 392 | 393 | void temp_open(int i, char *name, char *ft); 394 | void temp_switch(int i); 395 | void temp_write(int i, char *str); 396 | void temp_pos(int i, int row, int off, int top); 397 | int ex_exec(const char *ln); 398 | #define ex_command(ln) { ex_exec(ln); vi_regputraw(':', ln, 0, 0); } 399 | char *ex_read(char *msg); 400 | void ex_cprint(char *line, int r, int c, int ln); 401 | #define ex_print(line) ex_cprint(line, -1, 0, 1) 402 | void ex_init(char **files, int n); 403 | void ex_bufpostfix(struct buf *p, int clear); 404 | int ex_krs(rset **krs, int *dir); 405 | void ex_krsset(char *kwd, int dir); 406 | int ex_edit(const char *path, int len); 407 | void ec_bufferi(int id); 408 | void bufs_switch(int idx); 409 | #define bufs_switchwft(idx) \ 410 | { if (&bufs[idx] != ex_buf) { bufs_switch(idx); syn_setft(ex_ft); } } \ 411 | 412 | /* conf.c configuration variables */ 413 | /* map file names to file types */ 414 | extern int conf_mode; 415 | struct filetype { 416 | char *ft; /* file type */ 417 | char *pat; /* file name pattern */ 418 | }; 419 | extern struct filetype fts[]; 420 | extern int ftslen; 421 | /* syntax highlighting patterns */ 422 | struct highlight { 423 | char *ft; /* the filetype of this pattern */ 424 | char *pat; /* regular expression */ 425 | int att[16]; /* attributes of the matched groups */ 426 | signed char end[16]; /* the group ending this pattern; 427 | if set on multi-line the block emits all other matches in rset 428 | else defines hl continuation for the group: 429 | positive value - continue at rm_so 430 | zero (default) - continue at rm_eo 431 | negative value - ignore it or continue at sp+1 */ 432 | signed char blkend; /* the ending group for multi-line patterns; 433 | negative group is able to start and end itself */ 434 | char id; /* id of this hl */ 435 | }; 436 | extern struct highlight hls[]; 437 | extern int hlslen; 438 | /* direction context patterns; specifies the direction of a whole line */ 439 | struct dircontext { 440 | int dir; 441 | char *pat; 442 | }; 443 | extern struct dircontext dctxs[]; 444 | extern int dctxlen; 445 | /* direction marks; the direction of a few words in a line */ 446 | struct dirmark { 447 | int ctx; /* the direction context for this mark; 0 means any */ 448 | int dir[8]; /* the direction of a matched text group */ 449 | char *pat; 450 | }; 451 | extern struct dirmark dmarks[]; 452 | extern int dmarkslen; 453 | /* character placeholders */ 454 | struct placeholder { 455 | int cp[2]; /* the source character codepoint */ 456 | char d[8]; /* the placeholder */ 457 | int wid; /* the width of the placeholder */ 458 | int l; /* the length of the codepoint */ 459 | }; 460 | extern struct placeholder _ph[]; 461 | extern struct placeholder *ph; 462 | extern int phlen; 463 | extern int conf_hlrev; 464 | char **conf_kmap(int id); 465 | int conf_kmapfind(char *name); 466 | char *conf_digraph(int c1, int c2); 467 | 468 | /* vi.c */ 469 | void vi_regputraw(unsigned char c, const char *s, int ln, int append); 470 | void vi_regput(int c, const char *s, int ln); 471 | /* file system */ 472 | void dir_calc(char *path); 473 | /* global variables */ 474 | extern int xrow; 475 | extern int xoff; 476 | extern int xtop; 477 | extern int xleft; 478 | extern int xvis; 479 | extern int xled; 480 | extern int xquit; 481 | extern int xic; 482 | extern int xai; 483 | extern int xtd; 484 | extern int xshape; 485 | extern int xorder; 486 | extern int xhl; 487 | extern int xhll; 488 | extern int xhlw; 489 | extern int xhlp; 490 | extern int xhlr; 491 | extern int xkmap; 492 | extern int xkmap_alt; 493 | extern int xtabspc; 494 | extern int xish; 495 | extern int xgrp; 496 | extern int xpac; 497 | extern int xkwdcnt; 498 | extern int xkwddir; 499 | extern int xmpt; 500 | extern int xpr; 501 | extern int xsep; 502 | extern rset *xkwdrs; 503 | extern sbuf *xacreg; 504 | extern rset *fsincl; 505 | extern char *fs_exdir; 506 | extern int vi_hidch; 507 | extern int vi_insmov; 508 | extern int vi_lncol; 509 | --------------------------------------------------------------------------------