3
0

vi.c 109 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * tiny vi.c: A small 'vi' clone
  4. * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
  5. *
  6. * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  7. */
  8. /*
  9. * Things To Do:
  10. * EXINIT
  11. * $HOME/.exrc and ./.exrc
  12. * add magic to search /foo.*bar
  13. * add :help command
  14. * :map macros
  15. * if mark[] values were line numbers rather than pointers
  16. * it would be easier to change the mark when add/delete lines
  17. * More intelligence in refresh()
  18. * ":r !cmd" and "!cmd" to filter text through an external command
  19. * A true "undo" facility
  20. * An "ex" line oriented mode- maybe using "cmdedit"
  21. */
  22. //config:config VI
  23. //config: bool "vi"
  24. //config: default y
  25. //config: help
  26. //config: 'vi' is a text editor. More specifically, it is the One True
  27. //config: text editor <grin>. It does, however, have a rather steep
  28. //config: learning curve. If you are not already comfortable with 'vi'
  29. //config: you may wish to use something else.
  30. //config:
  31. //config:config FEATURE_VI_MAX_LEN
  32. //config: int "Maximum screen width in vi"
  33. //config: range 256 16384
  34. //config: default 4096
  35. //config: depends on VI
  36. //config: help
  37. //config: Contrary to what you may think, this is not eating much.
  38. //config: Make it smaller than 4k only if you are very limited on memory.
  39. //config:
  40. //config:config FEATURE_VI_8BIT
  41. //config: bool "Allow vi to display 8-bit chars (otherwise shows dots)"
  42. //config: default n
  43. //config: depends on VI
  44. //config: help
  45. //config: If your terminal can display characters with high bit set,
  46. //config: you may want to enable this. Note: vi is not Unicode-capable.
  47. //config: If your terminal combines several 8-bit bytes into one character
  48. //config: (as in Unicode mode), this will not work properly.
  49. //config:
  50. //config:config FEATURE_VI_COLON
  51. //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
  52. //config: default y
  53. //config: depends on VI
  54. //config: help
  55. //config: Enable a limited set of colon commands for vi. This does not
  56. //config: provide an "ex" mode.
  57. //config:
  58. //config:config FEATURE_VI_YANKMARK
  59. //config: bool "Enable yank/put commands and mark cmds"
  60. //config: default y
  61. //config: depends on VI
  62. //config: help
  63. //config: This will enable you to use yank and put, as well as mark in
  64. //config: busybox vi.
  65. //config:
  66. //config:config FEATURE_VI_SEARCH
  67. //config: bool "Enable search and replace cmds"
  68. //config: default y
  69. //config: depends on VI
  70. //config: help
  71. //config: Select this if you wish to be able to do search and replace in
  72. //config: busybox vi.
  73. //config:
  74. //config:config FEATURE_VI_REGEX_SEARCH
  75. //config: bool "Enable regex in search and replace"
  76. //config: default n # Uses GNU regex, which may be unavailable. FIXME
  77. //config: depends on FEATURE_VI_SEARCH
  78. //config: help
  79. //config: Use extended regex search.
  80. //config:
  81. //config:config FEATURE_VI_USE_SIGNALS
  82. //config: bool "Catch signals"
  83. //config: default y
  84. //config: depends on VI
  85. //config: help
  86. //config: Selecting this option will make busybox vi signal aware. This will
  87. //config: make busybox vi support SIGWINCH to deal with Window Changes, catch
  88. //config: Ctrl-Z and Ctrl-C and alarms.
  89. //config:
  90. //config:config FEATURE_VI_DOT_CMD
  91. //config: bool "Remember previous cmd and \".\" cmd"
  92. //config: default y
  93. //config: depends on VI
  94. //config: help
  95. //config: Make busybox vi remember the last command and be able to repeat it.
  96. //config:
  97. //config:config FEATURE_VI_READONLY
  98. //config: bool "Enable -R option and \"view\" mode"
  99. //config: default y
  100. //config: depends on VI
  101. //config: help
  102. //config: Enable the read-only command line option, which allows the user to
  103. //config: open a file in read-only mode.
  104. //config:
  105. //config:config FEATURE_VI_SETOPTS
  106. //config: bool "Enable set-able options, ai ic showmatch"
  107. //config: default y
  108. //config: depends on VI
  109. //config: help
  110. //config: Enable the editor to set some (ai, ic, showmatch) options.
  111. //config:
  112. //config:config FEATURE_VI_SET
  113. //config: bool "Support for :set"
  114. //config: default y
  115. //config: depends on VI
  116. //config: help
  117. //config: Support for ":set".
  118. //config:
  119. //config:config FEATURE_VI_WIN_RESIZE
  120. //config: bool "Handle window resize"
  121. //config: default y
  122. //config: depends on VI
  123. //config: help
  124. //config: Make busybox vi behave nicely with terminals that get resized.
  125. //config:
  126. //config:config FEATURE_VI_ASK_TERMINAL
  127. //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
  128. //config: default y
  129. //config: depends on VI
  130. //config: help
  131. //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
  132. //config: this option makes vi perform a last-ditch effort to find it:
  133. //config: position cursor to 999,999 and ask terminal to report real
  134. //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
  135. //config:
  136. //config: This is not clean but helps a lot on serial lines and such.
  137. //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
  138. //kbuild:lib-$(CONFIG_VI) += vi.o
  139. //usage:#define vi_trivial_usage
  140. //usage: "[OPTIONS] [FILE]..."
  141. //usage:#define vi_full_usage "\n\n"
  142. //usage: "Edit FILE\n"
  143. //usage: IF_FEATURE_VI_COLON(
  144. //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
  145. //usage: )
  146. //usage: IF_FEATURE_VI_READONLY(
  147. //usage: "\n -R Read-only"
  148. //usage: )
  149. //usage: "\n -H List available features"
  150. #include "libbb.h"
  151. /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
  152. #if ENABLE_FEATURE_VI_REGEX_SEARCH
  153. # include <regex.h>
  154. #endif
  155. /* the CRASHME code is unmaintained, and doesn't currently build */
  156. #define ENABLE_FEATURE_VI_CRASHME 0
  157. #if ENABLE_LOCALE_SUPPORT
  158. #if ENABLE_FEATURE_VI_8BIT
  159. //FIXME: this does not work properly for Unicode anyway
  160. # define Isprint(c) (isprint)(c)
  161. #else
  162. # define Isprint(c) isprint_asciionly(c)
  163. #endif
  164. #else
  165. /* 0x9b is Meta-ESC */
  166. #if ENABLE_FEATURE_VI_8BIT
  167. # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
  168. #else
  169. # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
  170. #endif
  171. #endif
  172. enum {
  173. MAX_TABSTOP = 32, // sanity limit
  174. // User input len. Need not be extra big.
  175. // Lines in file being edited *can* be bigger than this.
  176. MAX_INPUT_LEN = 128,
  177. // Sanity limits. We have only one buffer of this size.
  178. MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
  179. MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
  180. };
  181. /* VT102 ESC sequences.
  182. * See "Xterm Control Sequences"
  183. * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
  184. */
  185. /* Inverse/Normal text */
  186. #define ESC_BOLD_TEXT "\033[7m"
  187. #define ESC_NORM_TEXT "\033[0m"
  188. /* Bell */
  189. #define ESC_BELL "\007"
  190. /* Clear-to-end-of-line */
  191. #define ESC_CLEAR2EOL "\033[K"
  192. /* Clear-to-end-of-screen.
  193. * (We use default param here.
  194. * Full sequence is "ESC [ <num> J",
  195. * <num> is 0/1/2 = "erase below/above/all".)
  196. */
  197. #define ESC_CLEAR2EOS "\033[J"
  198. /* Cursor to given coordinate (1,1: top left) */
  199. #define ESC_SET_CURSOR_POS "\033[%u;%uH"
  200. //UNUSED
  201. ///* Cursor up and down */
  202. //#define ESC_CURSOR_UP "\033[A"
  203. //#define ESC_CURSOR_DOWN "\n"
  204. #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
  205. // cmds modifying text[]
  206. // vda: removed "aAiIs" as they switch us into insert mode
  207. // and remembering input for replay after them makes no sense
  208. static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
  209. #endif
  210. enum {
  211. YANKONLY = FALSE,
  212. YANKDEL = TRUE,
  213. FORWARD = 1, // code depends on "1" for array index
  214. BACK = -1, // code depends on "-1" for array index
  215. LIMITED = 0, // how much of text[] in char_search
  216. FULL = 1, // how much of text[] in char_search
  217. S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
  218. S_TO_WS = 2, // used in skip_thing() for moving "dot"
  219. S_OVER_WS = 3, // used in skip_thing() for moving "dot"
  220. S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
  221. S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
  222. };
  223. /* vi.c expects chars to be unsigned. */
  224. /* busybox build system provides that, but it's better */
  225. /* to audit and fix the source */
  226. struct globals {
  227. /* many references - keep near the top of globals */
  228. char *text, *end; // pointers to the user data in memory
  229. char *dot; // where all the action takes place
  230. int text_size; // size of the allocated buffer
  231. /* the rest */
  232. smallint vi_setops;
  233. #define VI_AUTOINDENT 1
  234. #define VI_SHOWMATCH 2
  235. #define VI_IGNORECASE 4
  236. #define VI_ERR_METHOD 8
  237. #define autoindent (vi_setops & VI_AUTOINDENT)
  238. #define showmatch (vi_setops & VI_SHOWMATCH )
  239. #define ignorecase (vi_setops & VI_IGNORECASE)
  240. /* indicate error with beep or flash */
  241. #define err_method (vi_setops & VI_ERR_METHOD)
  242. #if ENABLE_FEATURE_VI_READONLY
  243. smallint readonly_mode;
  244. #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
  245. #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
  246. #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
  247. #else
  248. #define SET_READONLY_FILE(flags) ((void)0)
  249. #define SET_READONLY_MODE(flags) ((void)0)
  250. #define UNSET_READONLY_FILE(flags) ((void)0)
  251. #endif
  252. smallint editing; // >0 while we are editing a file
  253. // [code audit says "can be 0, 1 or 2 only"]
  254. smallint cmd_mode; // 0=command 1=insert 2=replace
  255. int file_modified; // buffer contents changed (counter, not flag!)
  256. int last_file_modified; // = -1;
  257. int save_argc; // how many file names on cmd line
  258. int cmdcnt; // repetition count
  259. unsigned rows, columns; // the terminal screen is this size
  260. #if ENABLE_FEATURE_VI_ASK_TERMINAL
  261. int get_rowcol_error;
  262. #endif
  263. int crow, ccol; // cursor is on Crow x Ccol
  264. int offset; // chars scrolled off the screen to the left
  265. int have_status_msg; // is default edit status needed?
  266. // [don't make smallint!]
  267. int last_status_cksum; // hash of current status line
  268. char *current_filename;
  269. char *screenbegin; // index into text[], of top line on the screen
  270. char *screen; // pointer to the virtual screen buffer
  271. int screensize; // and its size
  272. int tabstop;
  273. int last_forward_char; // last char searched for with 'f' (int because of Unicode)
  274. char erase_char; // the users erase character
  275. char last_input_char; // last char read from user
  276. #if ENABLE_FEATURE_VI_DOT_CMD
  277. smallint adding2q; // are we currently adding user input to q
  278. int lmc_len; // length of last_modifying_cmd
  279. char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
  280. #endif
  281. #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
  282. int my_pid;
  283. #endif
  284. #if ENABLE_FEATURE_VI_SEARCH
  285. char *last_search_pattern; // last pattern from a '/' or '?' search
  286. #endif
  287. /* former statics */
  288. #if ENABLE_FEATURE_VI_YANKMARK
  289. char *edit_file__cur_line;
  290. #endif
  291. int refresh__old_offset;
  292. int format_edit_status__tot;
  293. /* a few references only */
  294. #if ENABLE_FEATURE_VI_YANKMARK
  295. int YDreg, Ureg; // default delete register and orig line for "U"
  296. char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
  297. char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
  298. char *context_start, *context_end;
  299. #endif
  300. #if ENABLE_FEATURE_VI_USE_SIGNALS
  301. sigjmp_buf restart; // catch_sig()
  302. #endif
  303. struct termios term_orig, term_vi; // remember what the cooked mode was
  304. #if ENABLE_FEATURE_VI_COLON
  305. char *initial_cmds[3]; // currently 2 entries, NULL terminated
  306. #endif
  307. // Should be just enough to hold a key sequence,
  308. // but CRASHME mode uses it as generated command buffer too
  309. #if ENABLE_FEATURE_VI_CRASHME
  310. char readbuffer[128];
  311. #else
  312. char readbuffer[KEYCODE_BUFFER_SIZE];
  313. #endif
  314. #define STATUS_BUFFER_LEN 200
  315. char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
  316. #if ENABLE_FEATURE_VI_DOT_CMD
  317. char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
  318. #endif
  319. char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
  320. char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
  321. };
  322. #define G (*ptr_to_globals)
  323. #define text (G.text )
  324. #define text_size (G.text_size )
  325. #define end (G.end )
  326. #define dot (G.dot )
  327. #define reg (G.reg )
  328. #define vi_setops (G.vi_setops )
  329. #define editing (G.editing )
  330. #define cmd_mode (G.cmd_mode )
  331. #define file_modified (G.file_modified )
  332. #define last_file_modified (G.last_file_modified )
  333. #define save_argc (G.save_argc )
  334. #define cmdcnt (G.cmdcnt )
  335. #define rows (G.rows )
  336. #define columns (G.columns )
  337. #define crow (G.crow )
  338. #define ccol (G.ccol )
  339. #define offset (G.offset )
  340. #define status_buffer (G.status_buffer )
  341. #define have_status_msg (G.have_status_msg )
  342. #define last_status_cksum (G.last_status_cksum )
  343. #define current_filename (G.current_filename )
  344. #define screen (G.screen )
  345. #define screensize (G.screensize )
  346. #define screenbegin (G.screenbegin )
  347. #define tabstop (G.tabstop )
  348. #define last_forward_char (G.last_forward_char )
  349. #define erase_char (G.erase_char )
  350. #define last_input_char (G.last_input_char )
  351. #if ENABLE_FEATURE_VI_READONLY
  352. #define readonly_mode (G.readonly_mode )
  353. #else
  354. #define readonly_mode 0
  355. #endif
  356. #define adding2q (G.adding2q )
  357. #define lmc_len (G.lmc_len )
  358. #define ioq (G.ioq )
  359. #define ioq_start (G.ioq_start )
  360. #define my_pid (G.my_pid )
  361. #define last_search_pattern (G.last_search_pattern)
  362. #define edit_file__cur_line (G.edit_file__cur_line)
  363. #define refresh__old_offset (G.refresh__old_offset)
  364. #define format_edit_status__tot (G.format_edit_status__tot)
  365. #define YDreg (G.YDreg )
  366. #define Ureg (G.Ureg )
  367. #define mark (G.mark )
  368. #define context_start (G.context_start )
  369. #define context_end (G.context_end )
  370. #define restart (G.restart )
  371. #define term_orig (G.term_orig )
  372. #define term_vi (G.term_vi )
  373. #define initial_cmds (G.initial_cmds )
  374. #define readbuffer (G.readbuffer )
  375. #define scr_out_buf (G.scr_out_buf )
  376. #define last_modifying_cmd (G.last_modifying_cmd )
  377. #define get_input_line__buf (G.get_input_line__buf)
  378. #define INIT_G() do { \
  379. SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
  380. last_file_modified = -1; \
  381. /* "" but has space for 2 chars: */ \
  382. IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
  383. } while (0)
  384. static int init_text_buffer(char *); // init from file or create new
  385. static void edit_file(char *); // edit one file
  386. static void do_cmd(int); // execute a command
  387. static int next_tabstop(int);
  388. static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
  389. static char *begin_line(char *); // return pointer to cur line B-o-l
  390. static char *end_line(char *); // return pointer to cur line E-o-l
  391. static char *prev_line(char *); // return pointer to prev line B-o-l
  392. static char *next_line(char *); // return pointer to next line B-o-l
  393. static char *end_screen(void); // get pointer to last char on screen
  394. static int count_lines(char *, char *); // count line from start to stop
  395. static char *find_line(int); // find begining of line #li
  396. static char *move_to_col(char *, int); // move "p" to column l
  397. static void dot_left(void); // move dot left- dont leave line
  398. static void dot_right(void); // move dot right- dont leave line
  399. static void dot_begin(void); // move dot to B-o-l
  400. static void dot_end(void); // move dot to E-o-l
  401. static void dot_next(void); // move dot to next line B-o-l
  402. static void dot_prev(void); // move dot to prev line B-o-l
  403. static void dot_scroll(int, int); // move the screen up or down
  404. static void dot_skip_over_ws(void); // move dot pat WS
  405. static void dot_delete(void); // delete the char at 'dot'
  406. static char *bound_dot(char *); // make sure text[0] <= P < "end"
  407. static char *new_screen(int, int); // malloc virtual screen memory
  408. static char *char_insert(char *, char); // insert the char c at 'p'
  409. // might reallocate text[]! use p += stupid_insert(p, ...),
  410. // and be careful to not use pointers into potentially freed text[]!
  411. static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
  412. static int find_range(char **, char **, char); // return pointers for an object
  413. static int st_test(char *, int, int, char *); // helper for skip_thing()
  414. static char *skip_thing(char *, int, int, int); // skip some object
  415. static char *find_pair(char *, char); // find matching pair () [] {}
  416. static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
  417. // might reallocate text[]! use p += text_hole_make(p, ...),
  418. // and be careful to not use pointers into potentially freed text[]!
  419. static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
  420. static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
  421. static void show_help(void); // display some help info
  422. static void rawmode(void); // set "raw" mode on tty
  423. static void cookmode(void); // return to "cooked" mode on tty
  424. // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
  425. static int mysleep(int);
  426. static int readit(void); // read (maybe cursor) key from stdin
  427. static int get_one_char(void); // read 1 char from stdin
  428. static int file_size(const char *); // what is the byte size of "fn"
  429. #if !ENABLE_FEATURE_VI_READONLY
  430. #define file_insert(fn, p, update_ro_status) file_insert(fn, p)
  431. #endif
  432. // file_insert might reallocate text[]!
  433. static int file_insert(const char *, char *, int);
  434. static int file_write(char *, char *, char *);
  435. static void place_cursor(int, int);
  436. static void screen_erase(void);
  437. static void clear_to_eol(void);
  438. static void clear_to_eos(void);
  439. static void go_bottom_and_clear_to_eol(void);
  440. static void standout_start(void); // send "start reverse video" sequence
  441. static void standout_end(void); // send "end reverse video" sequence
  442. static void flash(int); // flash the terminal screen
  443. static void show_status_line(void); // put a message on the bottom line
  444. static void status_line(const char *, ...); // print to status buf
  445. static void status_line_bold(const char *, ...);
  446. static void status_line_bold_errno(const char *fn);
  447. static void not_implemented(const char *); // display "Not implemented" message
  448. static int format_edit_status(void); // format file status on status line
  449. static void redraw(int); // force a full screen refresh
  450. static char* format_line(char* /*, int*/);
  451. static void refresh(int); // update the terminal from screen[]
  452. static void Indicate_Error(void); // use flash or beep to indicate error
  453. #define indicate_error(c) Indicate_Error()
  454. static void Hit_Return(void);
  455. #if ENABLE_FEATURE_VI_SEARCH
  456. static char *char_search(char *, const char *, int, int); // search for pattern starting at p
  457. #endif
  458. #if ENABLE_FEATURE_VI_COLON
  459. static char *get_one_address(char *, int *); // get colon addr, if present
  460. static char *get_address(char *, int *, int *); // get two colon addrs, if present
  461. static void colon(char *); // execute the "colon" mode cmds
  462. #endif
  463. #if ENABLE_FEATURE_VI_USE_SIGNALS
  464. static void winch_sig(int); // catch window size changes
  465. static void suspend_sig(int); // catch ctrl-Z
  466. static void catch_sig(int); // catch ctrl-C and alarm time-outs
  467. #endif
  468. #if ENABLE_FEATURE_VI_DOT_CMD
  469. static void start_new_cmd_q(char); // new queue for command
  470. static void end_cmd_q(void); // stop saving input chars
  471. #else
  472. #define end_cmd_q() ((void)0)
  473. #endif
  474. #if ENABLE_FEATURE_VI_SETOPTS
  475. static void showmatching(char *); // show the matching pair () [] {}
  476. #endif
  477. #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
  478. // might reallocate text[]! use p += string_insert(p, ...),
  479. // and be careful to not use pointers into potentially freed text[]!
  480. static uintptr_t string_insert(char *, const char *); // insert the string at 'p'
  481. #endif
  482. #if ENABLE_FEATURE_VI_YANKMARK
  483. static char *text_yank(char *, char *, int); // save copy of "p" into a register
  484. static char what_reg(void); // what is letter of current YDreg
  485. static void check_context(char); // remember context for '' command
  486. #endif
  487. #if ENABLE_FEATURE_VI_CRASHME
  488. static void crash_dummy();
  489. static void crash_test();
  490. static int crashme = 0;
  491. #endif
  492. static void write1(const char *out)
  493. {
  494. fputs(out, stdout);
  495. }
  496. int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  497. int vi_main(int argc, char **argv)
  498. {
  499. int c;
  500. INIT_G();
  501. #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
  502. my_pid = getpid();
  503. #endif
  504. #if ENABLE_FEATURE_VI_CRASHME
  505. srand((long) my_pid);
  506. #endif
  507. #ifdef NO_SUCH_APPLET_YET
  508. /* If we aren't "vi", we are "view" */
  509. if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
  510. SET_READONLY_MODE(readonly_mode);
  511. }
  512. #endif
  513. // autoindent is not default in vim 7.3
  514. vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
  515. // 1- process $HOME/.exrc file (not inplemented yet)
  516. // 2- process EXINIT variable from environment
  517. // 3- process command line args
  518. #if ENABLE_FEATURE_VI_COLON
  519. {
  520. char *p = getenv("EXINIT");
  521. if (p && *p)
  522. initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
  523. }
  524. #endif
  525. while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
  526. switch (c) {
  527. #if ENABLE_FEATURE_VI_CRASHME
  528. case 'C':
  529. crashme = 1;
  530. break;
  531. #endif
  532. #if ENABLE_FEATURE_VI_READONLY
  533. case 'R': // Read-only flag
  534. SET_READONLY_MODE(readonly_mode);
  535. break;
  536. #endif
  537. #if ENABLE_FEATURE_VI_COLON
  538. case 'c': // cmd line vi command
  539. if (*optarg)
  540. initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
  541. break;
  542. #endif
  543. case 'H':
  544. show_help();
  545. /* fall through */
  546. default:
  547. bb_show_usage();
  548. return 1;
  549. }
  550. }
  551. // The argv array can be used by the ":next" and ":rewind" commands
  552. argv += optind;
  553. argc -= optind;
  554. //----- This is the main file handling loop --------------
  555. save_argc = argc;
  556. optind = 0;
  557. // "Save cursor, use alternate screen buffer, clear screen"
  558. write1("\033[?1049h");
  559. while (1) {
  560. edit_file(argv[optind]); /* param might be NULL */
  561. if (++optind >= argc)
  562. break;
  563. }
  564. // "Use normal screen buffer, restore cursor"
  565. write1("\033[?1049l");
  566. //-----------------------------------------------------------
  567. return 0;
  568. }
  569. /* read text from file or create an empty buf */
  570. /* will also update current_filename */
  571. static int init_text_buffer(char *fn)
  572. {
  573. int rc;
  574. int size = file_size(fn); // file size. -1 means does not exist.
  575. /* allocate/reallocate text buffer */
  576. free(text);
  577. text_size = size + 10240;
  578. screenbegin = dot = end = text = xzalloc(text_size);
  579. if (fn != current_filename) {
  580. free(current_filename);
  581. current_filename = xstrdup(fn);
  582. }
  583. if (size < 0) {
  584. // file dont exist. Start empty buf with dummy line
  585. char_insert(text, '\n');
  586. rc = 0;
  587. } else {
  588. rc = file_insert(fn, text, 1);
  589. }
  590. file_modified = 0;
  591. last_file_modified = -1;
  592. #if ENABLE_FEATURE_VI_YANKMARK
  593. /* init the marks. */
  594. memset(mark, 0, sizeof(mark));
  595. #endif
  596. return rc;
  597. }
  598. #if ENABLE_FEATURE_VI_WIN_RESIZE
  599. static int query_screen_dimensions(void)
  600. {
  601. int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
  602. if (rows > MAX_SCR_ROWS)
  603. rows = MAX_SCR_ROWS;
  604. if (columns > MAX_SCR_COLS)
  605. columns = MAX_SCR_COLS;
  606. return err;
  607. }
  608. #else
  609. # define query_screen_dimensions() (0)
  610. #endif
  611. static void edit_file(char *fn)
  612. {
  613. #if ENABLE_FEATURE_VI_YANKMARK
  614. #define cur_line edit_file__cur_line
  615. #endif
  616. int c;
  617. #if ENABLE_FEATURE_VI_USE_SIGNALS
  618. int sig;
  619. #endif
  620. editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
  621. rawmode();
  622. rows = 24;
  623. columns = 80;
  624. IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
  625. #if ENABLE_FEATURE_VI_ASK_TERMINAL
  626. if (G.get_rowcol_error /* TODO? && no input on stdin */) {
  627. uint64_t k;
  628. write1("\033[999;999H" "\033[6n");
  629. fflush_all();
  630. k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
  631. if ((int32_t)k == KEYCODE_CURSOR_POS) {
  632. uint32_t rc = (k >> 32);
  633. columns = (rc & 0x7fff);
  634. if (columns > MAX_SCR_COLS)
  635. columns = MAX_SCR_COLS;
  636. rows = ((rc >> 16) & 0x7fff);
  637. if (rows > MAX_SCR_ROWS)
  638. rows = MAX_SCR_ROWS;
  639. }
  640. }
  641. #endif
  642. new_screen(rows, columns); // get memory for virtual screen
  643. init_text_buffer(fn);
  644. #if ENABLE_FEATURE_VI_YANKMARK
  645. YDreg = 26; // default Yank/Delete reg
  646. Ureg = 27; // hold orig line for "U" cmd
  647. mark[26] = mark[27] = text; // init "previous context"
  648. #endif
  649. last_forward_char = last_input_char = '\0';
  650. crow = 0;
  651. ccol = 0;
  652. #if ENABLE_FEATURE_VI_USE_SIGNALS
  653. signal(SIGINT, catch_sig);
  654. signal(SIGWINCH, winch_sig);
  655. signal(SIGTSTP, suspend_sig);
  656. sig = sigsetjmp(restart, 1);
  657. if (sig != 0) {
  658. screenbegin = dot = text;
  659. }
  660. #endif
  661. cmd_mode = 0; // 0=command 1=insert 2='R'eplace
  662. cmdcnt = 0;
  663. tabstop = 8;
  664. offset = 0; // no horizontal offset
  665. c = '\0';
  666. #if ENABLE_FEATURE_VI_DOT_CMD
  667. free(ioq_start);
  668. ioq = ioq_start = NULL;
  669. lmc_len = 0;
  670. adding2q = 0;
  671. #endif
  672. #if ENABLE_FEATURE_VI_COLON
  673. {
  674. char *p, *q;
  675. int n = 0;
  676. while ((p = initial_cmds[n]) != NULL) {
  677. do {
  678. q = p;
  679. p = strchr(q, '\n');
  680. if (p)
  681. while (*p == '\n')
  682. *p++ = '\0';
  683. if (*q)
  684. colon(q);
  685. } while (p);
  686. free(initial_cmds[n]);
  687. initial_cmds[n] = NULL;
  688. n++;
  689. }
  690. }
  691. #endif
  692. redraw(FALSE); // dont force every col re-draw
  693. //------This is the main Vi cmd handling loop -----------------------
  694. while (editing > 0) {
  695. #if ENABLE_FEATURE_VI_CRASHME
  696. if (crashme > 0) {
  697. if ((end - text) > 1) {
  698. crash_dummy(); // generate a random command
  699. } else {
  700. crashme = 0;
  701. string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
  702. dot = text;
  703. refresh(FALSE);
  704. }
  705. }
  706. #endif
  707. last_input_char = c = get_one_char(); // get a cmd from user
  708. #if ENABLE_FEATURE_VI_YANKMARK
  709. // save a copy of the current line- for the 'U" command
  710. if (begin_line(dot) != cur_line) {
  711. cur_line = begin_line(dot);
  712. text_yank(begin_line(dot), end_line(dot), Ureg);
  713. }
  714. #endif
  715. #if ENABLE_FEATURE_VI_DOT_CMD
  716. // These are commands that change text[].
  717. // Remember the input for the "." command
  718. if (!adding2q && ioq_start == NULL
  719. && cmd_mode == 0 // command mode
  720. && c > '\0' // exclude NUL and non-ASCII chars
  721. && c < 0x7f // (Unicode and such)
  722. && strchr(modifying_cmds, c)
  723. ) {
  724. start_new_cmd_q(c);
  725. }
  726. #endif
  727. do_cmd(c); // execute the user command
  728. // poll to see if there is input already waiting. if we are
  729. // not able to display output fast enough to keep up, skip
  730. // the display update until we catch up with input.
  731. if (!readbuffer[0] && mysleep(0) == 0) {
  732. // no input pending - so update output
  733. refresh(FALSE);
  734. show_status_line();
  735. }
  736. #if ENABLE_FEATURE_VI_CRASHME
  737. if (crashme > 0)
  738. crash_test(); // test editor variables
  739. #endif
  740. }
  741. //-------------------------------------------------------------------
  742. go_bottom_and_clear_to_eol();
  743. cookmode();
  744. #undef cur_line
  745. }
  746. //----- The Colon commands -------------------------------------
  747. #if ENABLE_FEATURE_VI_COLON
  748. static char *get_one_address(char *p, int *addr) // get colon addr, if present
  749. {
  750. int st;
  751. char *q;
  752. IF_FEATURE_VI_YANKMARK(char c;)
  753. IF_FEATURE_VI_SEARCH(char *pat;)
  754. *addr = -1; // assume no addr
  755. if (*p == '.') { // the current line
  756. p++;
  757. q = begin_line(dot);
  758. *addr = count_lines(text, q);
  759. }
  760. #if ENABLE_FEATURE_VI_YANKMARK
  761. else if (*p == '\'') { // is this a mark addr
  762. p++;
  763. c = tolower(*p);
  764. p++;
  765. if (c >= 'a' && c <= 'z') {
  766. // we have a mark
  767. c = c - 'a';
  768. q = mark[(unsigned char) c];
  769. if (q != NULL) { // is mark valid
  770. *addr = count_lines(text, q);
  771. }
  772. }
  773. }
  774. #endif
  775. #if ENABLE_FEATURE_VI_SEARCH
  776. else if (*p == '/') { // a search pattern
  777. q = strchrnul(++p, '/');
  778. pat = xstrndup(p, q - p); // save copy of pattern
  779. p = q;
  780. if (*p == '/')
  781. p++;
  782. q = char_search(dot, pat, FORWARD, FULL);
  783. if (q != NULL) {
  784. *addr = count_lines(text, q);
  785. }
  786. free(pat);
  787. }
  788. #endif
  789. else if (*p == '$') { // the last line in file
  790. p++;
  791. q = begin_line(end - 1);
  792. *addr = count_lines(text, q);
  793. } else if (isdigit(*p)) { // specific line number
  794. sscanf(p, "%d%n", addr, &st);
  795. p += st;
  796. } else {
  797. // unrecognized address - assume -1
  798. *addr = -1;
  799. }
  800. return p;
  801. }
  802. static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
  803. {
  804. //----- get the address' i.e., 1,3 'a,'b -----
  805. // get FIRST addr, if present
  806. while (isblank(*p))
  807. p++; // skip over leading spaces
  808. if (*p == '%') { // alias for 1,$
  809. p++;
  810. *b = 1;
  811. *e = count_lines(text, end-1);
  812. goto ga0;
  813. }
  814. p = get_one_address(p, b);
  815. while (isblank(*p))
  816. p++;
  817. if (*p == ',') { // is there a address separator
  818. p++;
  819. while (isblank(*p))
  820. p++;
  821. // get SECOND addr, if present
  822. p = get_one_address(p, e);
  823. }
  824. ga0:
  825. while (isblank(*p))
  826. p++; // skip over trailing spaces
  827. return p;
  828. }
  829. #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
  830. static void setops(const char *args, const char *opname, int flg_no,
  831. const char *short_opname, int opt)
  832. {
  833. const char *a = args + flg_no;
  834. int l = strlen(opname) - 1; /* opname have + ' ' */
  835. // maybe strncmp? we had tons of erroneous strncasecmp's...
  836. if (strncasecmp(a, opname, l) == 0
  837. || strncasecmp(a, short_opname, 2) == 0
  838. ) {
  839. if (flg_no)
  840. vi_setops &= ~opt;
  841. else
  842. vi_setops |= opt;
  843. }
  844. }
  845. #endif
  846. // buf must be no longer than MAX_INPUT_LEN!
  847. static void colon(char *buf)
  848. {
  849. char c, *orig_buf, *buf1, *q, *r;
  850. char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
  851. int i, l, li, ch, b, e;
  852. int useforce, forced = FALSE;
  853. // :3154 // if (-e line 3154) goto it else stay put
  854. // :4,33w! foo // write a portion of buffer to file "foo"
  855. // :w // write all of buffer to current file
  856. // :q // quit
  857. // :q! // quit- dont care about modified file
  858. // :'a,'z!sort -u // filter block through sort
  859. // :'f // goto mark "f"
  860. // :'fl // list literal the mark "f" line
  861. // :.r bar // read file "bar" into buffer before dot
  862. // :/123/,/abc/d // delete lines from "123" line to "abc" line
  863. // :/xyz/ // goto the "xyz" line
  864. // :s/find/replace/ // substitute pattern "find" with "replace"
  865. // :!<cmd> // run <cmd> then return
  866. //
  867. if (!buf[0])
  868. goto ret;
  869. if (*buf == ':')
  870. buf++; // move past the ':'
  871. li = ch = i = 0;
  872. b = e = -1;
  873. q = text; // assume 1,$ for the range
  874. r = end - 1;
  875. li = count_lines(text, end - 1);
  876. fn = current_filename;
  877. // look for optional address(es) :. :1 :1,9 :'q,'a :%
  878. buf = get_address(buf, &b, &e);
  879. // remember orig command line
  880. orig_buf = buf;
  881. // get the COMMAND into cmd[]
  882. buf1 = cmd;
  883. while (*buf != '\0') {
  884. if (isspace(*buf))
  885. break;
  886. *buf1++ = *buf++;
  887. }
  888. *buf1 = '\0';
  889. // get any ARGuments
  890. while (isblank(*buf))
  891. buf++;
  892. strcpy(args, buf);
  893. useforce = FALSE;
  894. buf1 = last_char_is(cmd, '!');
  895. if (buf1) {
  896. useforce = TRUE;
  897. *buf1 = '\0'; // get rid of !
  898. }
  899. if (b >= 0) {
  900. // if there is only one addr, then the addr
  901. // is the line number of the single line the
  902. // user wants. So, reset the end
  903. // pointer to point at end of the "b" line
  904. q = find_line(b); // what line is #b
  905. r = end_line(q);
  906. li = 1;
  907. }
  908. if (e >= 0) {
  909. // we were given two addrs. change the
  910. // end pointer to the addr given by user.
  911. r = find_line(e); // what line is #e
  912. r = end_line(r);
  913. li = e - b + 1;
  914. }
  915. // ------------ now look for the command ------------
  916. i = strlen(cmd);
  917. if (i == 0) { // :123CR goto line #123
  918. if (b >= 0) {
  919. dot = find_line(b); // what line is #b
  920. dot_skip_over_ws();
  921. }
  922. }
  923. #if ENABLE_FEATURE_ALLOW_EXEC
  924. else if (cmd[0] == '!') { // run a cmd
  925. int retcode;
  926. // :!ls run the <cmd>
  927. go_bottom_and_clear_to_eol();
  928. cookmode();
  929. retcode = system(orig_buf + 1); // run the cmd
  930. if (retcode)
  931. printf("\nshell returned %i\n\n", retcode);
  932. rawmode();
  933. Hit_Return(); // let user see results
  934. }
  935. #endif
  936. else if (cmd[0] == '=' && !cmd[1]) { // where is the address
  937. if (b < 0) { // no addr given- use defaults
  938. b = e = count_lines(text, dot);
  939. }
  940. status_line("%d", b);
  941. } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
  942. if (b < 0) { // no addr given- use defaults
  943. q = begin_line(dot); // assume .,. for the range
  944. r = end_line(dot);
  945. }
  946. dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
  947. dot_skip_over_ws();
  948. } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
  949. // don't edit, if the current file has been modified
  950. if (file_modified && !useforce) {
  951. status_line_bold("No write since last change (:%s! overrides)", cmd);
  952. goto ret;
  953. }
  954. if (args[0]) {
  955. // the user supplied a file name
  956. fn = args;
  957. } else if (current_filename && current_filename[0]) {
  958. // no user supplied name- use the current filename
  959. // fn = current_filename; was set by default
  960. } else {
  961. // no user file name, no current name- punt
  962. status_line_bold("No current filename");
  963. goto ret;
  964. }
  965. if (init_text_buffer(fn) < 0)
  966. goto ret;
  967. #if ENABLE_FEATURE_VI_YANKMARK
  968. if (Ureg >= 0 && Ureg < 28) {
  969. free(reg[Ureg]); // free orig line reg- for 'U'
  970. reg[Ureg] = NULL;
  971. }
  972. if (YDreg >= 0 && YDreg < 28) {
  973. free(reg[YDreg]); // free default yank/delete register
  974. reg[YDreg] = NULL;
  975. }
  976. #endif
  977. // how many lines in text[]?
  978. li = count_lines(text, end - 1);
  979. status_line("'%s'%s"
  980. IF_FEATURE_VI_READONLY("%s")
  981. " %dL, %dC", current_filename,
  982. (file_size(fn) < 0 ? " [New file]" : ""),
  983. IF_FEATURE_VI_READONLY(
  984. ((readonly_mode) ? " [Readonly]" : ""),
  985. )
  986. li, ch);
  987. } else if (strncmp(cmd, "file", i) == 0) { // what File is this
  988. if (b != -1 || e != -1) {
  989. status_line_bold("No address allowed on this command");
  990. goto ret;
  991. }
  992. if (args[0]) {
  993. // user wants a new filename
  994. free(current_filename);
  995. current_filename = xstrdup(args);
  996. } else {
  997. // user wants file status info
  998. last_status_cksum = 0; // force status update
  999. }
  1000. } else if (strncmp(cmd, "features", i) == 0) { // what features are available
  1001. // print out values of all features
  1002. go_bottom_and_clear_to_eol();
  1003. cookmode();
  1004. show_help();
  1005. rawmode();
  1006. Hit_Return();
  1007. } else if (strncmp(cmd, "list", i) == 0) { // literal print line
  1008. if (b < 0) { // no addr given- use defaults
  1009. q = begin_line(dot); // assume .,. for the range
  1010. r = end_line(dot);
  1011. }
  1012. go_bottom_and_clear_to_eol();
  1013. puts("\r");
  1014. for (; q <= r; q++) {
  1015. int c_is_no_print;
  1016. c = *q;
  1017. c_is_no_print = (c & 0x80) && !Isprint(c);
  1018. if (c_is_no_print) {
  1019. c = '.';
  1020. standout_start();
  1021. }
  1022. if (c == '\n') {
  1023. write1("$\r");
  1024. } else if (c < ' ' || c == 127) {
  1025. bb_putchar('^');
  1026. if (c == 127)
  1027. c = '?';
  1028. else
  1029. c += '@';
  1030. }
  1031. bb_putchar(c);
  1032. if (c_is_no_print)
  1033. standout_end();
  1034. }
  1035. Hit_Return();
  1036. } else if (strncmp(cmd, "quit", i) == 0 // quit
  1037. || strncmp(cmd, "next", i) == 0 // edit next file
  1038. || strncmp(cmd, "prev", i) == 0 // edit previous file
  1039. ) {
  1040. int n;
  1041. if (useforce) {
  1042. if (*cmd == 'q') {
  1043. // force end of argv list
  1044. optind = save_argc;
  1045. }
  1046. editing = 0;
  1047. goto ret;
  1048. }
  1049. // don't exit if the file been modified
  1050. if (file_modified) {
  1051. status_line_bold("No write since last change (:%s! overrides)", cmd);
  1052. goto ret;
  1053. }
  1054. // are there other file to edit
  1055. n = save_argc - optind - 1;
  1056. if (*cmd == 'q' && n > 0) {
  1057. status_line_bold("%d more file(s) to edit", n);
  1058. goto ret;
  1059. }
  1060. if (*cmd == 'n' && n <= 0) {
  1061. status_line_bold("No more files to edit");
  1062. goto ret;
  1063. }
  1064. if (*cmd == 'p') {
  1065. // are there previous files to edit
  1066. if (optind < 1) {
  1067. status_line_bold("No previous files to edit");
  1068. goto ret;
  1069. }
  1070. optind -= 2;
  1071. }
  1072. editing = 0;
  1073. } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
  1074. fn = args;
  1075. if (!fn[0]) {
  1076. status_line_bold("No filename given");
  1077. goto ret;
  1078. }
  1079. if (b < 0) { // no addr given- use defaults
  1080. q = begin_line(dot); // assume "dot"
  1081. }
  1082. // read after current line- unless user said ":0r foo"
  1083. if (b != 0)
  1084. q = next_line(q);
  1085. { // dance around potentially-reallocated text[]
  1086. uintptr_t ofs = q - text;
  1087. ch = file_insert(fn, q, 0);
  1088. q = text + ofs;
  1089. }
  1090. if (ch < 0)
  1091. goto ret; // nothing was inserted
  1092. // how many lines in text[]?
  1093. li = count_lines(q, q + ch - 1);
  1094. status_line("'%s'"
  1095. IF_FEATURE_VI_READONLY("%s")
  1096. " %dL, %dC", fn,
  1097. IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
  1098. li, ch);
  1099. if (ch > 0) {
  1100. // if the insert is before "dot" then we need to update
  1101. if (q <= dot)
  1102. dot += ch;
  1103. /*file_modified++; - done by file_insert */
  1104. }
  1105. } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
  1106. if (file_modified && !useforce) {
  1107. status_line_bold("No write since last change (:%s! overrides)", cmd);
  1108. } else {
  1109. // reset the filenames to edit
  1110. optind = -1; /* start from 0th file */
  1111. editing = 0;
  1112. }
  1113. #if ENABLE_FEATURE_VI_SET
  1114. } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
  1115. #if ENABLE_FEATURE_VI_SETOPTS
  1116. char *argp;
  1117. #endif
  1118. i = 0; // offset into args
  1119. // only blank is regarded as args delimiter. What about tab '\t'?
  1120. if (!args[0] || strcasecmp(args, "all") == 0) {
  1121. // print out values of all options
  1122. #if ENABLE_FEATURE_VI_SETOPTS
  1123. status_line_bold(
  1124. "%sautoindent "
  1125. "%sflash "
  1126. "%signorecase "
  1127. "%sshowmatch "
  1128. "tabstop=%u",
  1129. autoindent ? "" : "no",
  1130. err_method ? "" : "no",
  1131. ignorecase ? "" : "no",
  1132. showmatch ? "" : "no",
  1133. tabstop
  1134. );
  1135. #endif
  1136. goto ret;
  1137. }
  1138. #if ENABLE_FEATURE_VI_SETOPTS
  1139. argp = args;
  1140. while (*argp) {
  1141. if (strncmp(argp, "no", 2) == 0)
  1142. i = 2; // ":set noautoindent"
  1143. setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
  1144. setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
  1145. setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
  1146. setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
  1147. if (strncmp(argp + i, "tabstop=", 8) == 0) {
  1148. int t = 0;
  1149. sscanf(argp + i+8, "%u", &t);
  1150. if (t > 0 && t <= MAX_TABSTOP)
  1151. tabstop = t;
  1152. }
  1153. argp = skip_non_whitespace(argp);
  1154. argp = skip_whitespace(argp);
  1155. }
  1156. #endif /* FEATURE_VI_SETOPTS */
  1157. #endif /* FEATURE_VI_SET */
  1158. #if ENABLE_FEATURE_VI_SEARCH
  1159. } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
  1160. char *F, *R, *flags;
  1161. size_t len_F, len_R;
  1162. int gflag; // global replace flag
  1163. // F points to the "find" pattern
  1164. // R points to the "replace" pattern
  1165. // replace the cmd line delimiters "/" with NULs
  1166. c = orig_buf[1]; // what is the delimiter
  1167. F = orig_buf + 2; // start of "find"
  1168. R = strchr(F, c); // middle delimiter
  1169. if (!R)
  1170. goto colon_s_fail;
  1171. len_F = R - F;
  1172. *R++ = '\0'; // terminate "find"
  1173. flags = strchr(R, c);
  1174. if (!flags)
  1175. goto colon_s_fail;
  1176. len_R = flags - R;
  1177. *flags++ = '\0'; // terminate "replace"
  1178. gflag = *flags;
  1179. q = begin_line(q);
  1180. if (b < 0) { // maybe :s/foo/bar/
  1181. q = begin_line(dot); // start with cur line
  1182. b = count_lines(text, q); // cur line number
  1183. }
  1184. if (e < 0)
  1185. e = b; // maybe :.s/foo/bar/
  1186. for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
  1187. char *ls = q; // orig line start
  1188. char *found;
  1189. vc4:
  1190. found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
  1191. if (found) {
  1192. uintptr_t bias;
  1193. // we found the "find" pattern - delete it
  1194. text_hole_delete(found, found + len_F - 1);
  1195. // inset the "replace" patern
  1196. bias = string_insert(found, R); // insert the string
  1197. found += bias;
  1198. ls += bias;
  1199. /*q += bias; - recalculated anyway */
  1200. // check for "global" :s/foo/bar/g
  1201. if (gflag == 'g') {
  1202. if ((found + len_R) < end_line(ls)) {
  1203. q = found + len_R;
  1204. goto vc4; // don't let q move past cur line
  1205. }
  1206. }
  1207. }
  1208. q = next_line(ls);
  1209. }
  1210. #endif /* FEATURE_VI_SEARCH */
  1211. } else if (strncmp(cmd, "version", i) == 0) { // show software version
  1212. status_line(BB_VER " " BB_BT);
  1213. } else if (strncmp(cmd, "write", i) == 0 // write text to file
  1214. || strncmp(cmd, "wq", i) == 0
  1215. || strncmp(cmd, "wn", i) == 0
  1216. || (cmd[0] == 'x' && !cmd[1])
  1217. ) {
  1218. // is there a file name to write to?
  1219. if (args[0]) {
  1220. fn = args;
  1221. }
  1222. #if ENABLE_FEATURE_VI_READONLY
  1223. if (readonly_mode && !useforce) {
  1224. status_line_bold("'%s' is read only", fn);
  1225. goto ret;
  1226. }
  1227. #endif
  1228. // how many lines in text[]?
  1229. li = count_lines(q, r);
  1230. ch = r - q + 1;
  1231. // see if file exists- if not, its just a new file request
  1232. if (useforce) {
  1233. // if "fn" is not write-able, chmod u+w
  1234. // sprintf(syscmd, "chmod u+w %s", fn);
  1235. // system(syscmd);
  1236. forced = TRUE;
  1237. }
  1238. l = file_write(fn, q, r);
  1239. if (useforce && forced) {
  1240. // chmod u-w
  1241. // sprintf(syscmd, "chmod u-w %s", fn);
  1242. // system(syscmd);
  1243. forced = FALSE;
  1244. }
  1245. if (l < 0) {
  1246. if (l == -1)
  1247. status_line_bold_errno(fn);
  1248. } else {
  1249. status_line("'%s' %dL, %dC", fn, li, l);
  1250. if (q == text && r == end - 1 && l == ch) {
  1251. file_modified = 0;
  1252. last_file_modified = -1;
  1253. }
  1254. if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
  1255. || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
  1256. )
  1257. && l == ch
  1258. ) {
  1259. editing = 0;
  1260. }
  1261. }
  1262. #if ENABLE_FEATURE_VI_YANKMARK
  1263. } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
  1264. if (b < 0) { // no addr given- use defaults
  1265. q = begin_line(dot); // assume .,. for the range
  1266. r = end_line(dot);
  1267. }
  1268. text_yank(q, r, YDreg);
  1269. li = count_lines(q, r);
  1270. status_line("Yank %d lines (%d chars) into [%c]",
  1271. li, strlen(reg[YDreg]), what_reg());
  1272. #endif
  1273. } else {
  1274. // cmd unknown
  1275. not_implemented(cmd);
  1276. }
  1277. ret:
  1278. dot = bound_dot(dot); // make sure "dot" is valid
  1279. return;
  1280. #if ENABLE_FEATURE_VI_SEARCH
  1281. colon_s_fail:
  1282. status_line(":s expression missing delimiters");
  1283. #endif
  1284. }
  1285. #endif /* FEATURE_VI_COLON */
  1286. static void Hit_Return(void)
  1287. {
  1288. int c;
  1289. standout_start();
  1290. write1("[Hit return to continue]");
  1291. standout_end();
  1292. while ((c = get_one_char()) != '\n' && c != '\r')
  1293. continue;
  1294. redraw(TRUE); // force redraw all
  1295. }
  1296. static int next_tabstop(int col)
  1297. {
  1298. return col + ((tabstop - 1) - (col % tabstop));
  1299. }
  1300. //----- Synchronize the cursor to Dot --------------------------
  1301. static NOINLINE void sync_cursor(char *d, int *row, int *col)
  1302. {
  1303. char *beg_cur; // begin and end of "d" line
  1304. char *tp;
  1305. int cnt, ro, co;
  1306. beg_cur = begin_line(d); // first char of cur line
  1307. if (beg_cur < screenbegin) {
  1308. // "d" is before top line on screen
  1309. // how many lines do we have to move
  1310. cnt = count_lines(beg_cur, screenbegin);
  1311. sc1:
  1312. screenbegin = beg_cur;
  1313. if (cnt > (rows - 1) / 2) {
  1314. // we moved too many lines. put "dot" in middle of screen
  1315. for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
  1316. screenbegin = prev_line(screenbegin);
  1317. }
  1318. }
  1319. } else {
  1320. char *end_scr; // begin and end of screen
  1321. end_scr = end_screen(); // last char of screen
  1322. if (beg_cur > end_scr) {
  1323. // "d" is after bottom line on screen
  1324. // how many lines do we have to move
  1325. cnt = count_lines(end_scr, beg_cur);
  1326. if (cnt > (rows - 1) / 2)
  1327. goto sc1; // too many lines
  1328. for (ro = 0; ro < cnt - 1; ro++) {
  1329. // move screen begin the same amount
  1330. screenbegin = next_line(screenbegin);
  1331. // now, move the end of screen
  1332. end_scr = next_line(end_scr);
  1333. end_scr = end_line(end_scr);
  1334. }
  1335. }
  1336. }
  1337. // "d" is on screen- find out which row
  1338. tp = screenbegin;
  1339. for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
  1340. if (tp == beg_cur)
  1341. break;
  1342. tp = next_line(tp);
  1343. }
  1344. // find out what col "d" is on
  1345. co = 0;
  1346. while (tp < d) { // drive "co" to correct column
  1347. if (*tp == '\n') //vda || *tp == '\0')
  1348. break;
  1349. if (*tp == '\t') {
  1350. // handle tabs like real vi
  1351. if (d == tp && cmd_mode) {
  1352. break;
  1353. }
  1354. co = next_tabstop(co);
  1355. } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
  1356. co++; // display as ^X, use 2 columns
  1357. }
  1358. co++;
  1359. tp++;
  1360. }
  1361. // "co" is the column where "dot" is.
  1362. // The screen has "columns" columns.
  1363. // The currently displayed columns are 0+offset -- columns+ofset
  1364. // |-------------------------------------------------------------|
  1365. // ^ ^ ^
  1366. // offset | |------- columns ----------------|
  1367. //
  1368. // If "co" is already in this range then we do not have to adjust offset
  1369. // but, we do have to subtract the "offset" bias from "co".
  1370. // If "co" is outside this range then we have to change "offset".
  1371. // If the first char of a line is a tab the cursor will try to stay
  1372. // in column 7, but we have to set offset to 0.
  1373. if (co < 0 + offset) {
  1374. offset = co;
  1375. }
  1376. if (co >= columns + offset) {
  1377. offset = co - columns + 1;
  1378. }
  1379. // if the first char of the line is a tab, and "dot" is sitting on it
  1380. // force offset to 0.
  1381. if (d == beg_cur && *d == '\t') {
  1382. offset = 0;
  1383. }
  1384. co -= offset;
  1385. *row = ro;
  1386. *col = co;
  1387. }
  1388. //----- Text Movement Routines ---------------------------------
  1389. static char *begin_line(char *p) // return pointer to first char cur line
  1390. {
  1391. if (p > text) {
  1392. p = memrchr(text, '\n', p - text);
  1393. if (!p)
  1394. return text;
  1395. return p + 1;
  1396. }
  1397. return p;
  1398. }
  1399. static char *end_line(char *p) // return pointer to NL of cur line
  1400. {
  1401. if (p < end - 1) {
  1402. p = memchr(p, '\n', end - p - 1);
  1403. if (!p)
  1404. return end - 1;
  1405. }
  1406. return p;
  1407. }
  1408. static char *dollar_line(char *p) // return pointer to just before NL line
  1409. {
  1410. p = end_line(p);
  1411. // Try to stay off of the Newline
  1412. if (*p == '\n' && (p - begin_line(p)) > 0)
  1413. p--;
  1414. return p;
  1415. }
  1416. static char *prev_line(char *p) // return pointer first char prev line
  1417. {
  1418. p = begin_line(p); // goto begining of cur line
  1419. if (p > text && p[-1] == '\n')
  1420. p--; // step to prev line
  1421. p = begin_line(p); // goto begining of prev line
  1422. return p;
  1423. }
  1424. static char *next_line(char *p) // return pointer first char next line
  1425. {
  1426. p = end_line(p);
  1427. if (p < end - 1 && *p == '\n')
  1428. p++; // step to next line
  1429. return p;
  1430. }
  1431. //----- Text Information Routines ------------------------------
  1432. static char *end_screen(void)
  1433. {
  1434. char *q;
  1435. int cnt;
  1436. // find new bottom line
  1437. q = screenbegin;
  1438. for (cnt = 0; cnt < rows - 2; cnt++)
  1439. q = next_line(q);
  1440. q = end_line(q);
  1441. return q;
  1442. }
  1443. // count line from start to stop
  1444. static int count_lines(char *start, char *stop)
  1445. {
  1446. char *q;
  1447. int cnt;
  1448. if (stop < start) { // start and stop are backwards- reverse them
  1449. q = start;
  1450. start = stop;
  1451. stop = q;
  1452. }
  1453. cnt = 0;
  1454. stop = end_line(stop);
  1455. while (start <= stop && start <= end - 1) {
  1456. start = end_line(start);
  1457. if (*start == '\n')
  1458. cnt++;
  1459. start++;
  1460. }
  1461. return cnt;
  1462. }
  1463. static char *find_line(int li) // find begining of line #li
  1464. {
  1465. char *q;
  1466. for (q = text; li > 1; li--) {
  1467. q = next_line(q);
  1468. }
  1469. return q;
  1470. }
  1471. //----- Dot Movement Routines ----------------------------------
  1472. static void dot_left(void)
  1473. {
  1474. if (dot > text && dot[-1] != '\n')
  1475. dot--;
  1476. }
  1477. static void dot_right(void)
  1478. {
  1479. if (dot < end - 1 && *dot != '\n')
  1480. dot++;
  1481. }
  1482. static void dot_begin(void)
  1483. {
  1484. dot = begin_line(dot); // return pointer to first char cur line
  1485. }
  1486. static void dot_end(void)
  1487. {
  1488. dot = end_line(dot); // return pointer to last char cur line
  1489. }
  1490. static char *move_to_col(char *p, int l)
  1491. {
  1492. int co;
  1493. p = begin_line(p);
  1494. co = 0;
  1495. while (co < l && p < end) {
  1496. if (*p == '\n') //vda || *p == '\0')
  1497. break;
  1498. if (*p == '\t') {
  1499. co = next_tabstop(co);
  1500. } else if (*p < ' ' || *p == 127) {
  1501. co++; // display as ^X, use 2 columns
  1502. }
  1503. co++;
  1504. p++;
  1505. }
  1506. return p;
  1507. }
  1508. static void dot_next(void)
  1509. {
  1510. dot = next_line(dot);
  1511. }
  1512. static void dot_prev(void)
  1513. {
  1514. dot = prev_line(dot);
  1515. }
  1516. static void dot_scroll(int cnt, int dir)
  1517. {
  1518. char *q;
  1519. for (; cnt > 0; cnt--) {
  1520. if (dir < 0) {
  1521. // scroll Backwards
  1522. // ctrl-Y scroll up one line
  1523. screenbegin = prev_line(screenbegin);
  1524. } else {
  1525. // scroll Forwards
  1526. // ctrl-E scroll down one line
  1527. screenbegin = next_line(screenbegin);
  1528. }
  1529. }
  1530. // make sure "dot" stays on the screen so we dont scroll off
  1531. if (dot < screenbegin)
  1532. dot = screenbegin;
  1533. q = end_screen(); // find new bottom line
  1534. if (dot > q)
  1535. dot = begin_line(q); // is dot is below bottom line?
  1536. dot_skip_over_ws();
  1537. }
  1538. static void dot_skip_over_ws(void)
  1539. {
  1540. // skip WS
  1541. while (isspace(*dot) && *dot != '\n' && dot < end - 1)
  1542. dot++;
  1543. }
  1544. static void dot_delete(void) // delete the char at 'dot'
  1545. {
  1546. text_hole_delete(dot, dot);
  1547. }
  1548. static char *bound_dot(char *p) // make sure text[0] <= P < "end"
  1549. {
  1550. if (p >= end && end > text) {
  1551. p = end - 1;
  1552. indicate_error('1');
  1553. }
  1554. if (p < text) {
  1555. p = text;
  1556. indicate_error('2');
  1557. }
  1558. return p;
  1559. }
  1560. //----- Helper Utility Routines --------------------------------
  1561. //----------------------------------------------------------------
  1562. //----- Char Routines --------------------------------------------
  1563. /* Chars that are part of a word-
  1564. * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
  1565. * Chars that are Not part of a word (stoppers)
  1566. * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
  1567. * Chars that are WhiteSpace
  1568. * TAB NEWLINE VT FF RETURN SPACE
  1569. * DO NOT COUNT NEWLINE AS WHITESPACE
  1570. */
  1571. static char *new_screen(int ro, int co)
  1572. {
  1573. int li;
  1574. free(screen);
  1575. screensize = ro * co + 8;
  1576. screen = xmalloc(screensize);
  1577. // initialize the new screen. assume this will be a empty file.
  1578. screen_erase();
  1579. // non-existent text[] lines start with a tilde (~).
  1580. for (li = 1; li < ro - 1; li++) {
  1581. screen[(li * co) + 0] = '~';
  1582. }
  1583. return screen;
  1584. }
  1585. #if ENABLE_FEATURE_VI_SEARCH
  1586. # if ENABLE_FEATURE_VI_REGEX_SEARCH
  1587. // search for pattern starting at p
  1588. static char *char_search(char *p, const char *pat, int dir, int range)
  1589. {
  1590. struct re_pattern_buffer preg;
  1591. const char *err;
  1592. char *q;
  1593. int i;
  1594. int size;
  1595. re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
  1596. if (ignorecase)
  1597. re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
  1598. memset(&preg, 0, sizeof(preg));
  1599. err = re_compile_pattern(pat, strlen(pat), &preg);
  1600. if (err != NULL) {
  1601. status_line_bold("bad search pattern '%s': %s", pat, err);
  1602. return p;
  1603. }
  1604. // assume a LIMITED forward search
  1605. q = end - 1;
  1606. if (dir == BACK)
  1607. q = text;
  1608. // RANGE could be negative if we are searching backwards
  1609. range = q - p;
  1610. q = p;
  1611. size = range;
  1612. if (range < 0) {
  1613. size = -size;
  1614. q = p - size;
  1615. if (q < text)
  1616. q = text;
  1617. }
  1618. // search for the compiled pattern, preg, in p[]
  1619. // range < 0: search backward
  1620. // range > 0: search forward
  1621. // 0 < start < size
  1622. // re_search() < 0: not found or error
  1623. // re_search() >= 0: index of found pattern
  1624. // struct pattern char int int int struct reg
  1625. // re_search(*pattern_buffer, *string, size, start, range, *regs)
  1626. i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
  1627. regfree(&preg);
  1628. if (i < 0)
  1629. return NULL;
  1630. if (dir == FORWARD)
  1631. p = p + i;
  1632. else
  1633. p = p - i;
  1634. return p;
  1635. }
  1636. # else
  1637. # if ENABLE_FEATURE_VI_SETOPTS
  1638. static int mycmp(const char *s1, const char *s2, int len)
  1639. {
  1640. if (ignorecase) {
  1641. return strncasecmp(s1, s2, len);
  1642. }
  1643. return strncmp(s1, s2, len);
  1644. }
  1645. # else
  1646. # define mycmp strncmp
  1647. # endif
  1648. static char *char_search(char *p, const char *pat, int dir, int range)
  1649. {
  1650. char *start, *stop;
  1651. int len;
  1652. len = strlen(pat);
  1653. if (dir == FORWARD) {
  1654. stop = end - 1; // assume range is p..end-1
  1655. if (range == LIMITED)
  1656. stop = next_line(p); // range is to next line
  1657. for (start = p; start < stop; start++) {
  1658. if (mycmp(start, pat, len) == 0) {
  1659. return start;
  1660. }
  1661. }
  1662. } else if (dir == BACK) {
  1663. stop = text; // assume range is text..p
  1664. if (range == LIMITED)
  1665. stop = prev_line(p); // range is to prev line
  1666. for (start = p - len; start >= stop; start--) {
  1667. if (mycmp(start, pat, len) == 0) {
  1668. return start;
  1669. }
  1670. }
  1671. }
  1672. // pattern not found
  1673. return NULL;
  1674. }
  1675. # endif
  1676. #endif /* FEATURE_VI_SEARCH */
  1677. static char *char_insert(char *p, char c) // insert the char c at 'p'
  1678. {
  1679. if (c == 22) { // Is this an ctrl-V?
  1680. p += stupid_insert(p, '^'); // use ^ to indicate literal next
  1681. refresh(FALSE); // show the ^
  1682. c = get_one_char();
  1683. *p = c;
  1684. p++;
  1685. file_modified++;
  1686. } else if (c == 27) { // Is this an ESC?
  1687. cmd_mode = 0;
  1688. cmdcnt = 0;
  1689. end_cmd_q(); // stop adding to q
  1690. last_status_cksum = 0; // force status update
  1691. if ((p[-1] != '\n') && (dot > text)) {
  1692. p--;
  1693. }
  1694. } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
  1695. // 123456789
  1696. if ((p[-1] != '\n') && (dot>text)) {
  1697. p--;
  1698. p = text_hole_delete(p, p); // shrink buffer 1 char
  1699. }
  1700. } else {
  1701. #if ENABLE_FEATURE_VI_SETOPTS
  1702. // insert a char into text[]
  1703. char *sp; // "save p"
  1704. #endif
  1705. if (c == 13)
  1706. c = '\n'; // translate \r to \n
  1707. #if ENABLE_FEATURE_VI_SETOPTS
  1708. sp = p; // remember addr of insert
  1709. #endif
  1710. p += 1 + stupid_insert(p, c); // insert the char
  1711. #if ENABLE_FEATURE_VI_SETOPTS
  1712. if (showmatch && strchr(")]}", *sp) != NULL) {
  1713. showmatching(sp);
  1714. }
  1715. if (autoindent && c == '\n') { // auto indent the new line
  1716. char *q;
  1717. size_t len;
  1718. q = prev_line(p); // use prev line as template
  1719. len = strspn(q, " \t"); // space or tab
  1720. if (len) {
  1721. uintptr_t bias;
  1722. bias = text_hole_make(p, len);
  1723. p += bias;
  1724. q += bias;
  1725. memcpy(p, q, len);
  1726. p += len;
  1727. }
  1728. }
  1729. #endif
  1730. }
  1731. return p;
  1732. }
  1733. // might reallocate text[]! use p += stupid_insert(p, ...),
  1734. // and be careful to not use pointers into potentially freed text[]!
  1735. static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
  1736. {
  1737. uintptr_t bias;
  1738. bias = text_hole_make(p, 1);
  1739. p += bias;
  1740. *p = c;
  1741. //file_modified++; - done by text_hole_make()
  1742. return bias;
  1743. }
  1744. static int find_range(char **start, char **stop, char c)
  1745. {
  1746. char *save_dot, *p, *q, *t;
  1747. int cnt, multiline = 0;
  1748. save_dot = dot;
  1749. p = q = dot;
  1750. if (strchr("cdy><", c)) {
  1751. // these cmds operate on whole lines
  1752. p = q = begin_line(p);
  1753. for (cnt = 1; cnt < cmdcnt; cnt++) {
  1754. q = next_line(q);
  1755. }
  1756. q = end_line(q);
  1757. } else if (strchr("^%$0bBeEfth\b\177", c)) {
  1758. // These cmds operate on char positions
  1759. do_cmd(c); // execute movement cmd
  1760. q = dot;
  1761. } else if (strchr("wW", c)) {
  1762. do_cmd(c); // execute movement cmd
  1763. // if we are at the next word's first char
  1764. // step back one char
  1765. // but check the possibilities when it is true
  1766. if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
  1767. || (ispunct(dot[-1]) && !ispunct(dot[0]))
  1768. || (isalnum(dot[-1]) && !isalnum(dot[0]))))
  1769. dot--; // move back off of next word
  1770. if (dot > text && *dot == '\n')
  1771. dot--; // stay off NL
  1772. q = dot;
  1773. } else if (strchr("H-k{", c)) {
  1774. // these operate on multi-lines backwards
  1775. q = end_line(dot); // find NL
  1776. do_cmd(c); // execute movement cmd
  1777. dot_begin();
  1778. p = dot;
  1779. } else if (strchr("L+j}\r\n", c)) {
  1780. // these operate on multi-lines forwards
  1781. p = begin_line(dot);
  1782. do_cmd(c); // execute movement cmd
  1783. dot_end(); // find NL
  1784. q = dot;
  1785. } else {
  1786. // nothing -- this causes any other values of c to
  1787. // represent the one-character range under the
  1788. // cursor. this is correct for ' ' and 'l', but
  1789. // perhaps no others.
  1790. //
  1791. }
  1792. if (q < p) {
  1793. t = q;
  1794. q = p;
  1795. p = t;
  1796. }
  1797. // backward char movements don't include start position
  1798. if (q > p && strchr("^0bBh\b\177", c)) q--;
  1799. multiline = 0;
  1800. for (t = p; t <= q; t++) {
  1801. if (*t == '\n') {
  1802. multiline = 1;
  1803. break;
  1804. }
  1805. }
  1806. *start = p;
  1807. *stop = q;
  1808. dot = save_dot;
  1809. return multiline;
  1810. }
  1811. static int st_test(char *p, int type, int dir, char *tested)
  1812. {
  1813. char c, c0, ci;
  1814. int test, inc;
  1815. inc = dir;
  1816. c = c0 = p[0];
  1817. ci = p[inc];
  1818. test = 0;
  1819. if (type == S_BEFORE_WS) {
  1820. c = ci;
  1821. test = (!isspace(c) || c == '\n');
  1822. }
  1823. if (type == S_TO_WS) {
  1824. c = c0;
  1825. test = (!isspace(c) || c == '\n');
  1826. }
  1827. if (type == S_OVER_WS) {
  1828. c = c0;
  1829. test = isspace(c);
  1830. }
  1831. if (type == S_END_PUNCT) {
  1832. c = ci;
  1833. test = ispunct(c);
  1834. }
  1835. if (type == S_END_ALNUM) {
  1836. c = ci;
  1837. test = (isalnum(c) || c == '_');
  1838. }
  1839. *tested = c;
  1840. return test;
  1841. }
  1842. static char *skip_thing(char *p, int linecnt, int dir, int type)
  1843. {
  1844. char c;
  1845. while (st_test(p, type, dir, &c)) {
  1846. // make sure we limit search to correct number of lines
  1847. if (c == '\n' && --linecnt < 1)
  1848. break;
  1849. if (dir >= 0 && p >= end - 1)
  1850. break;
  1851. if (dir < 0 && p <= text)
  1852. break;
  1853. p += dir; // move to next char
  1854. }
  1855. return p;
  1856. }
  1857. // find matching char of pair () [] {}
  1858. static char *find_pair(char *p, const char c)
  1859. {
  1860. char match, *q;
  1861. int dir, level;
  1862. match = ')';
  1863. level = 1;
  1864. dir = 1; // assume forward
  1865. switch (c) {
  1866. case '(': match = ')'; break;
  1867. case '[': match = ']'; break;
  1868. case '{': match = '}'; break;
  1869. case ')': match = '('; dir = -1; break;
  1870. case ']': match = '['; dir = -1; break;
  1871. case '}': match = '{'; dir = -1; break;
  1872. }
  1873. for (q = p + dir; text <= q && q < end; q += dir) {
  1874. // look for match, count levels of pairs (( ))
  1875. if (*q == c)
  1876. level++; // increase pair levels
  1877. if (*q == match)
  1878. level--; // reduce pair level
  1879. if (level == 0)
  1880. break; // found matching pair
  1881. }
  1882. if (level != 0)
  1883. q = NULL; // indicate no match
  1884. return q;
  1885. }
  1886. #if ENABLE_FEATURE_VI_SETOPTS
  1887. // show the matching char of a pair, () [] {}
  1888. static void showmatching(char *p)
  1889. {
  1890. char *q, *save_dot;
  1891. // we found half of a pair
  1892. q = find_pair(p, *p); // get loc of matching char
  1893. if (q == NULL) {
  1894. indicate_error('3'); // no matching char
  1895. } else {
  1896. // "q" now points to matching pair
  1897. save_dot = dot; // remember where we are
  1898. dot = q; // go to new loc
  1899. refresh(FALSE); // let the user see it
  1900. mysleep(40); // give user some time
  1901. dot = save_dot; // go back to old loc
  1902. refresh(FALSE);
  1903. }
  1904. }
  1905. #endif /* FEATURE_VI_SETOPTS */
  1906. // open a hole in text[]
  1907. // might reallocate text[]! use p += text_hole_make(p, ...),
  1908. // and be careful to not use pointers into potentially freed text[]!
  1909. static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
  1910. {
  1911. uintptr_t bias = 0;
  1912. if (size <= 0)
  1913. return bias;
  1914. end += size; // adjust the new END
  1915. if (end >= (text + text_size)) {
  1916. char *new_text;
  1917. text_size += end - (text + text_size) + 10240;
  1918. new_text = xrealloc(text, text_size);
  1919. bias = (new_text - text);
  1920. screenbegin += bias;
  1921. dot += bias;
  1922. end += bias;
  1923. p += bias;
  1924. #if ENABLE_FEATURE_VI_YANKMARK
  1925. {
  1926. int i;
  1927. for (i = 0; i < ARRAY_SIZE(mark); i++)
  1928. if (mark[i])
  1929. mark[i] += bias;
  1930. }
  1931. #endif
  1932. text = new_text;
  1933. }
  1934. memmove(p + size, p, end - size - p);
  1935. memset(p, ' ', size); // clear new hole
  1936. file_modified++;
  1937. return bias;
  1938. }
  1939. // close a hole in text[]
  1940. static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
  1941. {
  1942. char *src, *dest;
  1943. int cnt, hole_size;
  1944. // move forwards, from beginning
  1945. // assume p <= q
  1946. src = q + 1;
  1947. dest = p;
  1948. if (q < p) { // they are backward- swap them
  1949. src = p + 1;
  1950. dest = q;
  1951. }
  1952. hole_size = q - p + 1;
  1953. cnt = end - src;
  1954. if (src < text || src > end)
  1955. goto thd0;
  1956. if (dest < text || dest >= end)
  1957. goto thd0;
  1958. if (src >= end)
  1959. goto thd_atend; // just delete the end of the buffer
  1960. memmove(dest, src, cnt);
  1961. thd_atend:
  1962. end = end - hole_size; // adjust the new END
  1963. if (dest >= end)
  1964. dest = end - 1; // make sure dest in below end-1
  1965. if (end <= text)
  1966. dest = end = text; // keep pointers valid
  1967. file_modified++;
  1968. thd0:
  1969. return dest;
  1970. }
  1971. // copy text into register, then delete text.
  1972. // if dist <= 0, do not include, or go past, a NewLine
  1973. //
  1974. static char *yank_delete(char *start, char *stop, int dist, int yf)
  1975. {
  1976. char *p;
  1977. // make sure start <= stop
  1978. if (start > stop) {
  1979. // they are backwards, reverse them
  1980. p = start;
  1981. start = stop;
  1982. stop = p;
  1983. }
  1984. if (dist <= 0) {
  1985. // we cannot cross NL boundaries
  1986. p = start;
  1987. if (*p == '\n')
  1988. return p;
  1989. // dont go past a NewLine
  1990. for (; p + 1 <= stop; p++) {
  1991. if (p[1] == '\n') {
  1992. stop = p; // "stop" just before NewLine
  1993. break;
  1994. }
  1995. }
  1996. }
  1997. p = start;
  1998. #if ENABLE_FEATURE_VI_YANKMARK
  1999. text_yank(start, stop, YDreg);
  2000. #endif
  2001. if (yf == YANKDEL) {
  2002. p = text_hole_delete(start, stop);
  2003. } // delete lines
  2004. return p;
  2005. }
  2006. static void show_help(void)
  2007. {
  2008. puts("These features are available:"
  2009. #if ENABLE_FEATURE_VI_SEARCH
  2010. "\n\tPattern searches with / and ?"
  2011. #endif
  2012. #if ENABLE_FEATURE_VI_DOT_CMD
  2013. "\n\tLast command repeat with ."
  2014. #endif
  2015. #if ENABLE_FEATURE_VI_YANKMARK
  2016. "\n\tLine marking with 'x"
  2017. "\n\tNamed buffers with \"x"
  2018. #endif
  2019. #if ENABLE_FEATURE_VI_READONLY
  2020. //not implemented: "\n\tReadonly if vi is called as \"view\""
  2021. //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
  2022. #endif
  2023. #if ENABLE_FEATURE_VI_SET
  2024. "\n\tSome colon mode commands with :"
  2025. #endif
  2026. #if ENABLE_FEATURE_VI_SETOPTS
  2027. "\n\tSettable options with \":set\""
  2028. #endif
  2029. #if ENABLE_FEATURE_VI_USE_SIGNALS
  2030. "\n\tSignal catching- ^C"
  2031. "\n\tJob suspend and resume with ^Z"
  2032. #endif
  2033. #if ENABLE_FEATURE_VI_WIN_RESIZE
  2034. "\n\tAdapt to window re-sizes"
  2035. #endif
  2036. );
  2037. }
  2038. #if ENABLE_FEATURE_VI_DOT_CMD
  2039. static void start_new_cmd_q(char c)
  2040. {
  2041. // get buffer for new cmd
  2042. // if there is a current cmd count put it in the buffer first
  2043. if (cmdcnt > 0) {
  2044. lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
  2045. } else { // just save char c onto queue
  2046. last_modifying_cmd[0] = c;
  2047. lmc_len = 1;
  2048. }
  2049. adding2q = 1;
  2050. }
  2051. static void end_cmd_q(void)
  2052. {
  2053. #if ENABLE_FEATURE_VI_YANKMARK
  2054. YDreg = 26; // go back to default Yank/Delete reg
  2055. #endif
  2056. adding2q = 0;
  2057. }
  2058. #endif /* FEATURE_VI_DOT_CMD */
  2059. #if ENABLE_FEATURE_VI_YANKMARK \
  2060. || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
  2061. || ENABLE_FEATURE_VI_CRASHME
  2062. // might reallocate text[]! use p += string_insert(p, ...),
  2063. // and be careful to not use pointers into potentially freed text[]!
  2064. static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
  2065. {
  2066. uintptr_t bias;
  2067. int i;
  2068. i = strlen(s);
  2069. bias = text_hole_make(p, i);
  2070. p += bias;
  2071. memcpy(p, s, i);
  2072. #if ENABLE_FEATURE_VI_YANKMARK
  2073. {
  2074. int cnt;
  2075. for (cnt = 0; *s != '\0'; s++) {
  2076. if (*s == '\n')
  2077. cnt++;
  2078. }
  2079. status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
  2080. }
  2081. #endif
  2082. return bias;
  2083. }
  2084. #endif
  2085. #if ENABLE_FEATURE_VI_YANKMARK
  2086. static char *text_yank(char *p, char *q, int dest) // copy text into a register
  2087. {
  2088. int cnt = q - p;
  2089. if (cnt < 0) { // they are backwards- reverse them
  2090. p = q;
  2091. cnt = -cnt;
  2092. }
  2093. free(reg[dest]); // if already a yank register, free it
  2094. reg[dest] = xstrndup(p, cnt + 1);
  2095. return p;
  2096. }
  2097. static char what_reg(void)
  2098. {
  2099. char c;
  2100. c = 'D'; // default to D-reg
  2101. if (0 <= YDreg && YDreg <= 25)
  2102. c = 'a' + (char) YDreg;
  2103. if (YDreg == 26)
  2104. c = 'D';
  2105. if (YDreg == 27)
  2106. c = 'U';
  2107. return c;
  2108. }
  2109. static void check_context(char cmd)
  2110. {
  2111. // A context is defined to be "modifying text"
  2112. // Any modifying command establishes a new context.
  2113. if (dot < context_start || dot > context_end) {
  2114. if (strchr(modifying_cmds, cmd) != NULL) {
  2115. // we are trying to modify text[]- make this the current context
  2116. mark[27] = mark[26]; // move cur to prev
  2117. mark[26] = dot; // move local to cur
  2118. context_start = prev_line(prev_line(dot));
  2119. context_end = next_line(next_line(dot));
  2120. //loiter= start_loiter= now;
  2121. }
  2122. }
  2123. }
  2124. static char *swap_context(char *p) // goto new context for '' command make this the current context
  2125. {
  2126. char *tmp;
  2127. // the current context is in mark[26]
  2128. // the previous context is in mark[27]
  2129. // only swap context if other context is valid
  2130. if (text <= mark[27] && mark[27] <= end - 1) {
  2131. tmp = mark[27];
  2132. mark[27] = mark[26];
  2133. mark[26] = tmp;
  2134. p = mark[26]; // where we are going- previous context
  2135. context_start = prev_line(prev_line(prev_line(p)));
  2136. context_end = next_line(next_line(next_line(p)));
  2137. }
  2138. return p;
  2139. }
  2140. #endif /* FEATURE_VI_YANKMARK */
  2141. //----- Set terminal attributes --------------------------------
  2142. static void rawmode(void)
  2143. {
  2144. tcgetattr(0, &term_orig);
  2145. term_vi = term_orig;
  2146. term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
  2147. term_vi.c_iflag &= (~IXON & ~ICRNL);
  2148. term_vi.c_oflag &= (~ONLCR);
  2149. term_vi.c_cc[VMIN] = 1;
  2150. term_vi.c_cc[VTIME] = 0;
  2151. erase_char = term_vi.c_cc[VERASE];
  2152. tcsetattr_stdin_TCSANOW(&term_vi);
  2153. }
  2154. static void cookmode(void)
  2155. {
  2156. fflush_all();
  2157. tcsetattr_stdin_TCSANOW(&term_orig);
  2158. }
  2159. #if ENABLE_FEATURE_VI_USE_SIGNALS
  2160. //----- Come here when we get a window resize signal ---------
  2161. static void winch_sig(int sig UNUSED_PARAM)
  2162. {
  2163. int save_errno = errno;
  2164. // FIXME: do it in main loop!!!
  2165. signal(SIGWINCH, winch_sig);
  2166. query_screen_dimensions();
  2167. new_screen(rows, columns); // get memory for virtual screen
  2168. redraw(TRUE); // re-draw the screen
  2169. errno = save_errno;
  2170. }
  2171. //----- Come here when we get a continue signal -------------------
  2172. static void cont_sig(int sig UNUSED_PARAM)
  2173. {
  2174. int save_errno = errno;
  2175. rawmode(); // terminal to "raw"
  2176. last_status_cksum = 0; // force status update
  2177. redraw(TRUE); // re-draw the screen
  2178. signal(SIGTSTP, suspend_sig);
  2179. signal(SIGCONT, SIG_DFL);
  2180. //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
  2181. errno = save_errno;
  2182. }
  2183. //----- Come here when we get a Suspend signal -------------------
  2184. static void suspend_sig(int sig UNUSED_PARAM)
  2185. {
  2186. int save_errno = errno;
  2187. go_bottom_and_clear_to_eol();
  2188. cookmode(); // terminal to "cooked"
  2189. signal(SIGCONT, cont_sig);
  2190. signal(SIGTSTP, SIG_DFL);
  2191. kill(my_pid, SIGTSTP);
  2192. errno = save_errno;
  2193. }
  2194. //----- Come here when we get a signal ---------------------------
  2195. static void catch_sig(int sig)
  2196. {
  2197. signal(SIGINT, catch_sig);
  2198. siglongjmp(restart, sig);
  2199. }
  2200. #endif /* FEATURE_VI_USE_SIGNALS */
  2201. static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
  2202. {
  2203. struct pollfd pfd[1];
  2204. pfd[0].fd = STDIN_FILENO;
  2205. pfd[0].events = POLLIN;
  2206. return safe_poll(pfd, 1, hund*10) > 0;
  2207. }
  2208. //----- IO Routines --------------------------------------------
  2209. static int readit(void) // read (maybe cursor) key from stdin
  2210. {
  2211. int c;
  2212. fflush_all();
  2213. c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
  2214. if (c == -1) { // EOF/error
  2215. go_bottom_and_clear_to_eol();
  2216. cookmode(); // terminal to "cooked"
  2217. bb_error_msg_and_die("can't read user input");
  2218. }
  2219. return c;
  2220. }
  2221. //----- IO Routines --------------------------------------------
  2222. static int get_one_char(void)
  2223. {
  2224. int c;
  2225. #if ENABLE_FEATURE_VI_DOT_CMD
  2226. if (!adding2q) {
  2227. // we are not adding to the q.
  2228. // but, we may be reading from a q
  2229. if (ioq == 0) {
  2230. // there is no current q, read from STDIN
  2231. c = readit(); // get the users input
  2232. } else {
  2233. // there is a queue to get chars from first
  2234. // careful with correct sign expansion!
  2235. c = (unsigned char)*ioq++;
  2236. if (c == '\0') {
  2237. // the end of the q, read from STDIN
  2238. free(ioq_start);
  2239. ioq_start = ioq = 0;
  2240. c = readit(); // get the users input
  2241. }
  2242. }
  2243. } else {
  2244. // adding STDIN chars to q
  2245. c = readit(); // get the users input
  2246. if (lmc_len >= MAX_INPUT_LEN - 1) {
  2247. status_line_bold("last_modifying_cmd overrun");
  2248. } else {
  2249. // add new char to q
  2250. last_modifying_cmd[lmc_len++] = c;
  2251. }
  2252. }
  2253. #else
  2254. c = readit(); // get the users input
  2255. #endif /* FEATURE_VI_DOT_CMD */
  2256. return c;
  2257. }
  2258. // Get input line (uses "status line" area)
  2259. static char *get_input_line(const char *prompt)
  2260. {
  2261. // char [MAX_INPUT_LEN]
  2262. #define buf get_input_line__buf
  2263. int c;
  2264. int i;
  2265. strcpy(buf, prompt);
  2266. last_status_cksum = 0; // force status update
  2267. go_bottom_and_clear_to_eol();
  2268. write1(prompt); // write out the :, /, or ? prompt
  2269. i = strlen(buf);
  2270. while (i < MAX_INPUT_LEN) {
  2271. c = get_one_char();
  2272. if (c == '\n' || c == '\r' || c == 27)
  2273. break; // this is end of input
  2274. if (c == erase_char || c == 8 || c == 127) {
  2275. // user wants to erase prev char
  2276. buf[--i] = '\0';
  2277. write1("\b \b"); // erase char on screen
  2278. if (i <= 0) // user backs up before b-o-l, exit
  2279. break;
  2280. } else if (c > 0 && c < 256) { // exclude Unicode
  2281. // (TODO: need to handle Unicode)
  2282. buf[i] = c;
  2283. buf[++i] = '\0';
  2284. bb_putchar(c);
  2285. }
  2286. }
  2287. refresh(FALSE);
  2288. return buf;
  2289. #undef buf
  2290. }
  2291. static int file_size(const char *fn) // what is the byte size of "fn"
  2292. {
  2293. struct stat st_buf;
  2294. int cnt;
  2295. cnt = -1;
  2296. if (fn && stat(fn, &st_buf) == 0) // see if file exists
  2297. cnt = (int) st_buf.st_size;
  2298. return cnt;
  2299. }
  2300. // might reallocate text[]!
  2301. static int file_insert(const char *fn, char *p, int update_ro_status)
  2302. {
  2303. int cnt = -1;
  2304. int fd, size;
  2305. struct stat statbuf;
  2306. /* Validate file */
  2307. if (stat(fn, &statbuf) < 0) {
  2308. status_line_bold_errno(fn);
  2309. goto fi0;
  2310. }
  2311. if (!S_ISREG(statbuf.st_mode)) {
  2312. // This is not a regular file
  2313. status_line_bold("'%s' is not a regular file", fn);
  2314. goto fi0;
  2315. }
  2316. if (p < text || p > end) {
  2317. status_line_bold("Trying to insert file outside of memory");
  2318. goto fi0;
  2319. }
  2320. // read file to buffer
  2321. fd = open(fn, O_RDONLY);
  2322. if (fd < 0) {
  2323. status_line_bold_errno(fn);
  2324. goto fi0;
  2325. }
  2326. size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
  2327. p += text_hole_make(p, size);
  2328. cnt = safe_read(fd, p, size);
  2329. if (cnt < 0) {
  2330. status_line_bold_errno(fn);
  2331. p = text_hole_delete(p, p + size - 1); // un-do buffer insert
  2332. } else if (cnt < size) {
  2333. // There was a partial read, shrink unused space text[]
  2334. p = text_hole_delete(p + cnt, p + size - 1); // un-do buffer insert
  2335. status_line_bold("can't read '%s'", fn);
  2336. }
  2337. if (cnt >= size)
  2338. file_modified++;
  2339. close(fd);
  2340. fi0:
  2341. #if ENABLE_FEATURE_VI_READONLY
  2342. if (update_ro_status
  2343. && ((access(fn, W_OK) < 0) ||
  2344. /* root will always have access()
  2345. * so we check fileperms too */
  2346. !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
  2347. )
  2348. ) {
  2349. SET_READONLY_FILE(readonly_mode);
  2350. }
  2351. #endif
  2352. return cnt;
  2353. }
  2354. static int file_write(char *fn, char *first, char *last)
  2355. {
  2356. int fd, cnt, charcnt;
  2357. if (fn == 0) {
  2358. status_line_bold("No current filename");
  2359. return -2;
  2360. }
  2361. /* By popular request we do not open file with O_TRUNC,
  2362. * but instead ftruncate() it _after_ successful write.
  2363. * Might reduce amount of data lost on power fail etc.
  2364. */
  2365. fd = open(fn, (O_WRONLY | O_CREAT), 0666);
  2366. if (fd < 0)
  2367. return -1;
  2368. cnt = last - first + 1;
  2369. charcnt = full_write(fd, first, cnt);
  2370. ftruncate(fd, charcnt);
  2371. if (charcnt == cnt) {
  2372. // good write
  2373. //file_modified = FALSE;
  2374. } else {
  2375. charcnt = 0;
  2376. }
  2377. close(fd);
  2378. return charcnt;
  2379. }
  2380. //----- Terminal Drawing ---------------------------------------
  2381. // The terminal is made up of 'rows' line of 'columns' columns.
  2382. // classically this would be 24 x 80.
  2383. // screen coordinates
  2384. // 0,0 ... 0,79
  2385. // 1,0 ... 1,79
  2386. // . ... .
  2387. // . ... .
  2388. // 22,0 ... 22,79
  2389. // 23,0 ... 23,79 <- status line
  2390. //----- Move the cursor to row x col (count from 0, not 1) -------
  2391. static void place_cursor(int row, int col)
  2392. {
  2393. char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
  2394. if (row < 0) row = 0;
  2395. if (row >= rows) row = rows - 1;
  2396. if (col < 0) col = 0;
  2397. if (col >= columns) col = columns - 1;
  2398. sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
  2399. write1(cm1);
  2400. }
  2401. //----- Erase from cursor to end of line -----------------------
  2402. static void clear_to_eol(void)
  2403. {
  2404. write1(ESC_CLEAR2EOL);
  2405. }
  2406. static void go_bottom_and_clear_to_eol(void)
  2407. {
  2408. place_cursor(rows - 1, 0);
  2409. clear_to_eol();
  2410. }
  2411. //----- Erase from cursor to end of screen -----------------------
  2412. static void clear_to_eos(void)
  2413. {
  2414. write1(ESC_CLEAR2EOS);
  2415. }
  2416. //----- Start standout mode ------------------------------------
  2417. static void standout_start(void)
  2418. {
  2419. write1(ESC_BOLD_TEXT);
  2420. }
  2421. //----- End standout mode --------------------------------------
  2422. static void standout_end(void)
  2423. {
  2424. write1(ESC_NORM_TEXT);
  2425. }
  2426. //----- Flash the screen --------------------------------------
  2427. static void flash(int h)
  2428. {
  2429. standout_start();
  2430. redraw(TRUE);
  2431. mysleep(h);
  2432. standout_end();
  2433. redraw(TRUE);
  2434. }
  2435. static void Indicate_Error(void)
  2436. {
  2437. #if ENABLE_FEATURE_VI_CRASHME
  2438. if (crashme > 0)
  2439. return; // generate a random command
  2440. #endif
  2441. if (!err_method) {
  2442. write1(ESC_BELL);
  2443. } else {
  2444. flash(10);
  2445. }
  2446. }
  2447. //----- Screen[] Routines --------------------------------------
  2448. //----- Erase the Screen[] memory ------------------------------
  2449. static void screen_erase(void)
  2450. {
  2451. memset(screen, ' ', screensize); // clear new screen
  2452. }
  2453. static int bufsum(char *buf, int count)
  2454. {
  2455. int sum = 0;
  2456. char *e = buf + count;
  2457. while (buf < e)
  2458. sum += (unsigned char) *buf++;
  2459. return sum;
  2460. }
  2461. //----- Draw the status line at bottom of the screen -------------
  2462. static void show_status_line(void)
  2463. {
  2464. int cnt = 0, cksum = 0;
  2465. // either we already have an error or status message, or we
  2466. // create one.
  2467. if (!have_status_msg) {
  2468. cnt = format_edit_status();
  2469. cksum = bufsum(status_buffer, cnt);
  2470. }
  2471. if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
  2472. last_status_cksum = cksum; // remember if we have seen this line
  2473. go_bottom_and_clear_to_eol();
  2474. write1(status_buffer);
  2475. if (have_status_msg) {
  2476. if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
  2477. (columns - 1) ) {
  2478. have_status_msg = 0;
  2479. Hit_Return();
  2480. }
  2481. have_status_msg = 0;
  2482. }
  2483. place_cursor(crow, ccol); // put cursor back in correct place
  2484. }
  2485. fflush_all();
  2486. }
  2487. //----- format the status buffer, the bottom line of screen ------
  2488. // format status buffer, with STANDOUT mode
  2489. static void status_line_bold(const char *format, ...)
  2490. {
  2491. va_list args;
  2492. va_start(args, format);
  2493. strcpy(status_buffer, ESC_BOLD_TEXT);
  2494. vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
  2495. strcat(status_buffer, ESC_NORM_TEXT);
  2496. va_end(args);
  2497. have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
  2498. }
  2499. static void status_line_bold_errno(const char *fn)
  2500. {
  2501. status_line_bold("'%s' %s", fn, strerror(errno));
  2502. }
  2503. // format status buffer
  2504. static void status_line(const char *format, ...)
  2505. {
  2506. va_list args;
  2507. va_start(args, format);
  2508. vsprintf(status_buffer, format, args);
  2509. va_end(args);
  2510. have_status_msg = 1;
  2511. }
  2512. // copy s to buf, convert unprintable
  2513. static void print_literal(char *buf, const char *s)
  2514. {
  2515. char *d;
  2516. unsigned char c;
  2517. buf[0] = '\0';
  2518. if (!s[0])
  2519. s = "(NULL)";
  2520. d = buf;
  2521. for (; *s; s++) {
  2522. int c_is_no_print;
  2523. c = *s;
  2524. c_is_no_print = (c & 0x80) && !Isprint(c);
  2525. if (c_is_no_print) {
  2526. strcpy(d, ESC_NORM_TEXT);
  2527. d += sizeof(ESC_NORM_TEXT)-1;
  2528. c = '.';
  2529. }
  2530. if (c < ' ' || c == 0x7f) {
  2531. *d++ = '^';
  2532. c |= '@'; /* 0x40 */
  2533. if (c == 0x7f)
  2534. c = '?';
  2535. }
  2536. *d++ = c;
  2537. *d = '\0';
  2538. if (c_is_no_print) {
  2539. strcpy(d, ESC_BOLD_TEXT);
  2540. d += sizeof(ESC_BOLD_TEXT)-1;
  2541. }
  2542. if (*s == '\n') {
  2543. *d++ = '$';
  2544. *d = '\0';
  2545. }
  2546. if (d - buf > MAX_INPUT_LEN - 10) // paranoia
  2547. break;
  2548. }
  2549. }
  2550. static void not_implemented(const char *s)
  2551. {
  2552. char buf[MAX_INPUT_LEN];
  2553. print_literal(buf, s);
  2554. status_line_bold("\'%s\' is not implemented", buf);
  2555. }
  2556. // show file status on status line
  2557. static int format_edit_status(void)
  2558. {
  2559. static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
  2560. #define tot format_edit_status__tot
  2561. int cur, percent, ret, trunc_at;
  2562. // file_modified is now a counter rather than a flag. this
  2563. // helps reduce the amount of line counting we need to do.
  2564. // (this will cause a mis-reporting of modified status
  2565. // once every MAXINT editing operations.)
  2566. // it would be nice to do a similar optimization here -- if
  2567. // we haven't done a motion that could have changed which line
  2568. // we're on, then we shouldn't have to do this count_lines()
  2569. cur = count_lines(text, dot);
  2570. // reduce counting -- the total lines can't have
  2571. // changed if we haven't done any edits.
  2572. if (file_modified != last_file_modified) {
  2573. tot = cur + count_lines(dot, end - 1) - 1;
  2574. last_file_modified = file_modified;
  2575. }
  2576. // current line percent
  2577. // ------------- ~~ ----------
  2578. // total lines 100
  2579. if (tot > 0) {
  2580. percent = (100 * cur) / tot;
  2581. } else {
  2582. cur = tot = 0;
  2583. percent = 100;
  2584. }
  2585. trunc_at = columns < STATUS_BUFFER_LEN-1 ?
  2586. columns : STATUS_BUFFER_LEN-1;
  2587. ret = snprintf(status_buffer, trunc_at+1,
  2588. #if ENABLE_FEATURE_VI_READONLY
  2589. "%c %s%s%s %d/%d %d%%",
  2590. #else
  2591. "%c %s%s %d/%d %d%%",
  2592. #endif
  2593. cmd_mode_indicator[cmd_mode & 3],
  2594. (current_filename != NULL ? current_filename : "No file"),
  2595. #if ENABLE_FEATURE_VI_READONLY
  2596. (readonly_mode ? " [Readonly]" : ""),
  2597. #endif
  2598. (file_modified ? " [Modified]" : ""),
  2599. cur, tot, percent);
  2600. if (ret >= 0 && ret < trunc_at)
  2601. return ret; /* it all fit */
  2602. return trunc_at; /* had to truncate */
  2603. #undef tot
  2604. }
  2605. //----- Force refresh of all Lines -----------------------------
  2606. static void redraw(int full_screen)
  2607. {
  2608. place_cursor(0, 0);
  2609. clear_to_eos();
  2610. screen_erase(); // erase the internal screen buffer
  2611. last_status_cksum = 0; // force status update
  2612. refresh(full_screen); // this will redraw the entire display
  2613. show_status_line();
  2614. }
  2615. //----- Format a text[] line into a buffer ---------------------
  2616. static char* format_line(char *src /*, int li*/)
  2617. {
  2618. unsigned char c;
  2619. int co;
  2620. int ofs = offset;
  2621. char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
  2622. c = '~'; // char in col 0 in non-existent lines is '~'
  2623. co = 0;
  2624. while (co < columns + tabstop) {
  2625. // have we gone past the end?
  2626. if (src < end) {
  2627. c = *src++;
  2628. if (c == '\n')
  2629. break;
  2630. if ((c & 0x80) && !Isprint(c)) {
  2631. c = '.';
  2632. }
  2633. if (c < ' ' || c == 0x7f) {
  2634. if (c == '\t') {
  2635. c = ' ';
  2636. // co % 8 != 7
  2637. while ((co % tabstop) != (tabstop - 1)) {
  2638. dest[co++] = c;
  2639. }
  2640. } else {
  2641. dest[co++] = '^';
  2642. if (c == 0x7f)
  2643. c = '?';
  2644. else
  2645. c += '@'; // Ctrl-X -> 'X'
  2646. }
  2647. }
  2648. }
  2649. dest[co++] = c;
  2650. // discard scrolled-off-to-the-left portion,
  2651. // in tabstop-sized pieces
  2652. if (ofs >= tabstop && co >= tabstop) {
  2653. memmove(dest, dest + tabstop, co);
  2654. co -= tabstop;
  2655. ofs -= tabstop;
  2656. }
  2657. if (src >= end)
  2658. break;
  2659. }
  2660. // check "short line, gigantic offset" case
  2661. if (co < ofs)
  2662. ofs = co;
  2663. // discard last scrolled off part
  2664. co -= ofs;
  2665. dest += ofs;
  2666. // fill the rest with spaces
  2667. if (co < columns)
  2668. memset(&dest[co], ' ', columns - co);
  2669. return dest;
  2670. }
  2671. //----- Refresh the changed screen lines -----------------------
  2672. // Copy the source line from text[] into the buffer and note
  2673. // if the current screenline is different from the new buffer.
  2674. // If they differ then that line needs redrawing on the terminal.
  2675. //
  2676. static void refresh(int full_screen)
  2677. {
  2678. #define old_offset refresh__old_offset
  2679. int li, changed;
  2680. char *tp, *sp; // pointer into text[] and screen[]
  2681. if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
  2682. unsigned c = columns, r = rows;
  2683. query_screen_dimensions();
  2684. full_screen |= (c - columns) | (r - rows);
  2685. }
  2686. sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
  2687. tp = screenbegin; // index into text[] of top line
  2688. // compare text[] to screen[] and mark screen[] lines that need updating
  2689. for (li = 0; li < rows - 1; li++) {
  2690. int cs, ce; // column start & end
  2691. char *out_buf;
  2692. // format current text line
  2693. out_buf = format_line(tp /*, li*/);
  2694. // skip to the end of the current text[] line
  2695. if (tp < end) {
  2696. char *t = memchr(tp, '\n', end - tp);
  2697. if (!t) t = end - 1;
  2698. tp = t + 1;
  2699. }
  2700. // see if there are any changes between vitual screen and out_buf
  2701. changed = FALSE; // assume no change
  2702. cs = 0;
  2703. ce = columns - 1;
  2704. sp = &screen[li * columns]; // start of screen line
  2705. if (full_screen) {
  2706. // force re-draw of every single column from 0 - columns-1
  2707. goto re0;
  2708. }
  2709. // compare newly formatted buffer with virtual screen
  2710. // look forward for first difference between buf and screen
  2711. for (; cs <= ce; cs++) {
  2712. if (out_buf[cs] != sp[cs]) {
  2713. changed = TRUE; // mark for redraw
  2714. break;
  2715. }
  2716. }
  2717. // look backward for last difference between out_buf and screen
  2718. for (; ce >= cs; ce--) {
  2719. if (out_buf[ce] != sp[ce]) {
  2720. changed = TRUE; // mark for redraw
  2721. break;
  2722. }
  2723. }
  2724. // now, cs is index of first diff, and ce is index of last diff
  2725. // if horz offset has changed, force a redraw
  2726. if (offset != old_offset) {
  2727. re0:
  2728. changed = TRUE;
  2729. }
  2730. // make a sanity check of columns indexes
  2731. if (cs < 0) cs = 0;
  2732. if (ce > columns - 1) ce = columns - 1;
  2733. if (cs > ce) { cs = 0; ce = columns - 1; }
  2734. // is there a change between vitual screen and out_buf
  2735. if (changed) {
  2736. // copy changed part of buffer to virtual screen
  2737. memcpy(sp+cs, out_buf+cs, ce-cs+1);
  2738. place_cursor(li, cs);
  2739. // write line out to terminal
  2740. fwrite(&sp[cs], ce - cs + 1, 1, stdout);
  2741. }
  2742. }
  2743. place_cursor(crow, ccol);
  2744. old_offset = offset;
  2745. #undef old_offset
  2746. }
  2747. //---------------------------------------------------------------------
  2748. //----- the Ascii Chart -----------------------------------------------
  2749. //
  2750. // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
  2751. // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
  2752. // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
  2753. // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
  2754. // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
  2755. // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
  2756. // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
  2757. // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
  2758. // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
  2759. // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
  2760. // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
  2761. // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
  2762. // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
  2763. // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
  2764. // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
  2765. // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
  2766. //---------------------------------------------------------------------
  2767. //----- Execute a Vi Command -----------------------------------
  2768. static void do_cmd(int c)
  2769. {
  2770. char *p, *q, *save_dot;
  2771. char buf[12];
  2772. int dir;
  2773. int cnt, i, j;
  2774. int c1;
  2775. // c1 = c; // quiet the compiler
  2776. // cnt = yf = 0; // quiet the compiler
  2777. // p = q = save_dot = buf; // quiet the compiler
  2778. memset(buf, '\0', sizeof(buf));
  2779. show_status_line();
  2780. /* if this is a cursor key, skip these checks */
  2781. switch (c) {
  2782. case KEYCODE_UP:
  2783. case KEYCODE_DOWN:
  2784. case KEYCODE_LEFT:
  2785. case KEYCODE_RIGHT:
  2786. case KEYCODE_HOME:
  2787. case KEYCODE_END:
  2788. case KEYCODE_PAGEUP:
  2789. case KEYCODE_PAGEDOWN:
  2790. case KEYCODE_DELETE:
  2791. goto key_cmd_mode;
  2792. }
  2793. if (cmd_mode == 2) {
  2794. // flip-flop Insert/Replace mode
  2795. if (c == KEYCODE_INSERT)
  2796. goto dc_i;
  2797. // we are 'R'eplacing the current *dot with new char
  2798. if (*dot == '\n') {
  2799. // don't Replace past E-o-l
  2800. cmd_mode = 1; // convert to insert
  2801. } else {
  2802. if (1 <= c || Isprint(c)) {
  2803. if (c != 27)
  2804. dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
  2805. dot = char_insert(dot, c); // insert new char
  2806. }
  2807. goto dc1;
  2808. }
  2809. }
  2810. if (cmd_mode == 1) {
  2811. // hitting "Insert" twice means "R" replace mode
  2812. if (c == KEYCODE_INSERT) goto dc5;
  2813. // insert the char c at "dot"
  2814. if (1 <= c || Isprint(c)) {
  2815. dot = char_insert(dot, c);
  2816. }
  2817. goto dc1;
  2818. }
  2819. key_cmd_mode:
  2820. switch (c) {
  2821. //case 0x01: // soh
  2822. //case 0x09: // ht
  2823. //case 0x0b: // vt
  2824. //case 0x0e: // so
  2825. //case 0x0f: // si
  2826. //case 0x10: // dle
  2827. //case 0x11: // dc1
  2828. //case 0x13: // dc3
  2829. #if ENABLE_FEATURE_VI_CRASHME
  2830. case 0x14: // dc4 ctrl-T
  2831. crashme = (crashme == 0) ? 1 : 0;
  2832. break;
  2833. #endif
  2834. //case 0x16: // syn
  2835. //case 0x17: // etb
  2836. //case 0x18: // can
  2837. //case 0x1c: // fs
  2838. //case 0x1d: // gs
  2839. //case 0x1e: // rs
  2840. //case 0x1f: // us
  2841. //case '!': // !-
  2842. //case '#': // #-
  2843. //case '&': // &-
  2844. //case '(': // (-
  2845. //case ')': // )-
  2846. //case '*': // *-
  2847. //case '=': // =-
  2848. //case '@': // @-
  2849. //case 'F': // F-
  2850. //case 'K': // K-
  2851. //case 'Q': // Q-
  2852. //case 'S': // S-
  2853. //case 'T': // T-
  2854. //case 'V': // V-
  2855. //case '[': // [-
  2856. //case '\\': // \-
  2857. //case ']': // ]-
  2858. //case '_': // _-
  2859. //case '`': // `-
  2860. //case 'u': // u- FIXME- there is no undo
  2861. //case 'v': // v-
  2862. default: // unrecognized command
  2863. buf[0] = c;
  2864. buf[1] = '\0';
  2865. not_implemented(buf);
  2866. end_cmd_q(); // stop adding to q
  2867. case 0x00: // nul- ignore
  2868. break;
  2869. case 2: // ctrl-B scroll up full screen
  2870. case KEYCODE_PAGEUP: // Cursor Key Page Up
  2871. dot_scroll(rows - 2, -1);
  2872. break;
  2873. case 4: // ctrl-D scroll down half screen
  2874. dot_scroll((rows - 2) / 2, 1);
  2875. break;
  2876. case 5: // ctrl-E scroll down one line
  2877. dot_scroll(1, 1);
  2878. break;
  2879. case 6: // ctrl-F scroll down full screen
  2880. case KEYCODE_PAGEDOWN: // Cursor Key Page Down
  2881. dot_scroll(rows - 2, 1);
  2882. break;
  2883. case 7: // ctrl-G show current status
  2884. last_status_cksum = 0; // force status update
  2885. break;
  2886. case 'h': // h- move left
  2887. case KEYCODE_LEFT: // cursor key Left
  2888. case 8: // ctrl-H- move left (This may be ERASE char)
  2889. case 0x7f: // DEL- move left (This may be ERASE char)
  2890. do {
  2891. dot_left();
  2892. } while (--cmdcnt > 0);
  2893. break;
  2894. case 10: // Newline ^J
  2895. case 'j': // j- goto next line, same col
  2896. case KEYCODE_DOWN: // cursor key Down
  2897. do {
  2898. dot_next(); // go to next B-o-l
  2899. // try stay in same col
  2900. dot = move_to_col(dot, ccol + offset);
  2901. } while (--cmdcnt > 0);
  2902. break;
  2903. case 12: // ctrl-L force redraw whole screen
  2904. case 18: // ctrl-R force redraw
  2905. place_cursor(0, 0);
  2906. clear_to_eos();
  2907. //mysleep(10); // why???
  2908. screen_erase(); // erase the internal screen buffer
  2909. last_status_cksum = 0; // force status update
  2910. refresh(TRUE); // this will redraw the entire display
  2911. break;
  2912. case 13: // Carriage Return ^M
  2913. case '+': // +- goto next line
  2914. do {
  2915. dot_next();
  2916. dot_skip_over_ws();
  2917. } while (--cmdcnt > 0);
  2918. break;
  2919. case 21: // ctrl-U scroll up half screen
  2920. dot_scroll((rows - 2) / 2, -1);
  2921. break;
  2922. case 25: // ctrl-Y scroll up one line
  2923. dot_scroll(1, -1);
  2924. break;
  2925. case 27: // esc
  2926. if (cmd_mode == 0)
  2927. indicate_error(c);
  2928. cmd_mode = 0; // stop insrting
  2929. end_cmd_q();
  2930. last_status_cksum = 0; // force status update
  2931. break;
  2932. case ' ': // move right
  2933. case 'l': // move right
  2934. case KEYCODE_RIGHT: // Cursor Key Right
  2935. do {
  2936. dot_right();
  2937. } while (--cmdcnt > 0);
  2938. break;
  2939. #if ENABLE_FEATURE_VI_YANKMARK
  2940. case '"': // "- name a register to use for Delete/Yank
  2941. c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
  2942. if ((unsigned)c1 <= 25) { // a-z?
  2943. YDreg = c1;
  2944. } else {
  2945. indicate_error(c);
  2946. }
  2947. break;
  2948. case '\'': // '- goto a specific mark
  2949. c1 = (get_one_char() | 0x20) - 'a';
  2950. if ((unsigned)c1 <= 25) { // a-z?
  2951. // get the b-o-l
  2952. q = mark[c1];
  2953. if (text <= q && q < end) {
  2954. dot = q;
  2955. dot_begin(); // go to B-o-l
  2956. dot_skip_over_ws();
  2957. }
  2958. } else if (c1 == '\'') { // goto previous context
  2959. dot = swap_context(dot); // swap current and previous context
  2960. dot_begin(); // go to B-o-l
  2961. dot_skip_over_ws();
  2962. } else {
  2963. indicate_error(c);
  2964. }
  2965. break;
  2966. case 'm': // m- Mark a line
  2967. // this is really stupid. If there are any inserts or deletes
  2968. // between text[0] and dot then this mark will not point to the
  2969. // correct location! It could be off by many lines!
  2970. // Well..., at least its quick and dirty.
  2971. c1 = (get_one_char() | 0x20) - 'a';
  2972. if ((unsigned)c1 <= 25) { // a-z?
  2973. // remember the line
  2974. mark[c1] = dot;
  2975. } else {
  2976. indicate_error(c);
  2977. }
  2978. break;
  2979. case 'P': // P- Put register before
  2980. case 'p': // p- put register after
  2981. p = reg[YDreg];
  2982. if (p == NULL) {
  2983. status_line_bold("Nothing in register %c", what_reg());
  2984. break;
  2985. }
  2986. // are we putting whole lines or strings
  2987. if (strchr(p, '\n') != NULL) {
  2988. if (c == 'P') {
  2989. dot_begin(); // putting lines- Put above
  2990. }
  2991. if (c == 'p') {
  2992. // are we putting after very last line?
  2993. if (end_line(dot) == (end - 1)) {
  2994. dot = end; // force dot to end of text[]
  2995. } else {
  2996. dot_next(); // next line, then put before
  2997. }
  2998. }
  2999. } else {
  3000. if (c == 'p')
  3001. dot_right(); // move to right, can move to NL
  3002. }
  3003. string_insert(dot, p); // insert the string
  3004. end_cmd_q(); // stop adding to q
  3005. break;
  3006. case 'U': // U- Undo; replace current line with original version
  3007. if (reg[Ureg] != NULL) {
  3008. p = begin_line(dot);
  3009. q = end_line(dot);
  3010. p = text_hole_delete(p, q); // delete cur line
  3011. p += string_insert(p, reg[Ureg]); // insert orig line
  3012. dot = p;
  3013. dot_skip_over_ws();
  3014. }
  3015. break;
  3016. #endif /* FEATURE_VI_YANKMARK */
  3017. case '$': // $- goto end of line
  3018. case KEYCODE_END: // Cursor Key End
  3019. for (;;) {
  3020. dot = end_line(dot);
  3021. if (--cmdcnt <= 0)
  3022. break;
  3023. dot_next();
  3024. }
  3025. break;
  3026. case '%': // %- find matching char of pair () [] {}
  3027. for (q = dot; q < end && *q != '\n'; q++) {
  3028. if (strchr("()[]{}", *q) != NULL) {
  3029. // we found half of a pair
  3030. p = find_pair(q, *q);
  3031. if (p == NULL) {
  3032. indicate_error(c);
  3033. } else {
  3034. dot = p;
  3035. }
  3036. break;
  3037. }
  3038. }
  3039. if (*q == '\n')
  3040. indicate_error(c);
  3041. break;
  3042. case 'f': // f- forward to a user specified char
  3043. last_forward_char = get_one_char(); // get the search char
  3044. //
  3045. // dont separate these two commands. 'f' depends on ';'
  3046. //
  3047. //**** fall through to ... ';'
  3048. case ';': // ;- look at rest of line for last forward char
  3049. do {
  3050. if (last_forward_char == 0)
  3051. break;
  3052. q = dot + 1;
  3053. while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
  3054. q++;
  3055. }
  3056. if (*q == last_forward_char)
  3057. dot = q;
  3058. } while (--cmdcnt > 0);
  3059. break;
  3060. case ',': // repeat latest 'f' in opposite direction
  3061. if (last_forward_char == 0)
  3062. break;
  3063. do {
  3064. q = dot - 1;
  3065. while (q >= text && *q != '\n' && *q != last_forward_char) {
  3066. q--;
  3067. }
  3068. if (q >= text && *q == last_forward_char)
  3069. dot = q;
  3070. } while (--cmdcnt > 0);
  3071. break;
  3072. case '-': // -- goto prev line
  3073. do {
  3074. dot_prev();
  3075. dot_skip_over_ws();
  3076. } while (--cmdcnt > 0);
  3077. break;
  3078. #if ENABLE_FEATURE_VI_DOT_CMD
  3079. case '.': // .- repeat the last modifying command
  3080. // Stuff the last_modifying_cmd back into stdin
  3081. // and let it be re-executed.
  3082. if (lmc_len > 0) {
  3083. last_modifying_cmd[lmc_len] = 0;
  3084. ioq = ioq_start = xstrdup(last_modifying_cmd);
  3085. }
  3086. break;
  3087. #endif
  3088. #if ENABLE_FEATURE_VI_SEARCH
  3089. case '?': // /- search for a pattern
  3090. case '/': // /- search for a pattern
  3091. buf[0] = c;
  3092. buf[1] = '\0';
  3093. q = get_input_line(buf); // get input line- use "status line"
  3094. if (q[0] && !q[1]) {
  3095. if (last_search_pattern[0])
  3096. last_search_pattern[0] = c;
  3097. goto dc3; // if no pat re-use old pat
  3098. }
  3099. if (q[0]) { // strlen(q) > 1: new pat- save it and find
  3100. // there is a new pat
  3101. free(last_search_pattern);
  3102. last_search_pattern = xstrdup(q);
  3103. goto dc3; // now find the pattern
  3104. }
  3105. // user changed mind and erased the "/"- do nothing
  3106. break;
  3107. case 'N': // N- backward search for last pattern
  3108. dir = BACK; // assume BACKWARD search
  3109. p = dot - 1;
  3110. if (last_search_pattern[0] == '?') {
  3111. dir = FORWARD;
  3112. p = dot + 1;
  3113. }
  3114. goto dc4; // now search for pattern
  3115. break;
  3116. case 'n': // n- repeat search for last pattern
  3117. // search rest of text[] starting at next char
  3118. // if search fails return orignal "p" not the "p+1" address
  3119. do {
  3120. const char *msg;
  3121. dc3:
  3122. dir = FORWARD; // assume FORWARD search
  3123. p = dot + 1;
  3124. if (last_search_pattern[0] == '?') {
  3125. dir = BACK;
  3126. p = dot - 1;
  3127. }
  3128. dc4:
  3129. q = char_search(p, last_search_pattern + 1, dir, FULL);
  3130. if (q != NULL) {
  3131. dot = q; // good search, update "dot"
  3132. msg = NULL;
  3133. goto dc2;
  3134. }
  3135. // no pattern found between "dot" and "end"- continue at top
  3136. p = text;
  3137. if (dir == BACK) {
  3138. p = end - 1;
  3139. }
  3140. q = char_search(p, last_search_pattern + 1, dir, FULL);
  3141. if (q != NULL) { // found something
  3142. dot = q; // found new pattern- goto it
  3143. msg = "search hit BOTTOM, continuing at TOP";
  3144. if (dir == BACK) {
  3145. msg = "search hit TOP, continuing at BOTTOM";
  3146. }
  3147. } else {
  3148. msg = "Pattern not found";
  3149. }
  3150. dc2:
  3151. if (msg)
  3152. status_line_bold("%s", msg);
  3153. } while (--cmdcnt > 0);
  3154. break;
  3155. case '{': // {- move backward paragraph
  3156. q = char_search(dot, "\n\n", BACK, FULL);
  3157. if (q != NULL) { // found blank line
  3158. dot = next_line(q); // move to next blank line
  3159. }
  3160. break;
  3161. case '}': // }- move forward paragraph
  3162. q = char_search(dot, "\n\n", FORWARD, FULL);
  3163. if (q != NULL) { // found blank line
  3164. dot = next_line(q); // move to next blank line
  3165. }
  3166. break;
  3167. #endif /* FEATURE_VI_SEARCH */
  3168. case '0': // 0- goto begining of line
  3169. case '1': // 1-
  3170. case '2': // 2-
  3171. case '3': // 3-
  3172. case '4': // 4-
  3173. case '5': // 5-
  3174. case '6': // 6-
  3175. case '7': // 7-
  3176. case '8': // 8-
  3177. case '9': // 9-
  3178. if (c == '0' && cmdcnt < 1) {
  3179. dot_begin(); // this was a standalone zero
  3180. } else {
  3181. cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
  3182. }
  3183. break;
  3184. case ':': // :- the colon mode commands
  3185. p = get_input_line(":"); // get input line- use "status line"
  3186. #if ENABLE_FEATURE_VI_COLON
  3187. colon(p); // execute the command
  3188. #else
  3189. if (*p == ':')
  3190. p++; // move past the ':'
  3191. cnt = strlen(p);
  3192. if (cnt <= 0)
  3193. break;
  3194. if (strncmp(p, "quit", cnt) == 0
  3195. || strncmp(p, "q!", cnt) == 0 // delete lines
  3196. ) {
  3197. if (file_modified && p[1] != '!') {
  3198. status_line_bold("No write since last change (:%s! overrides)", p);
  3199. } else {
  3200. editing = 0;
  3201. }
  3202. } else if (strncmp(p, "write", cnt) == 0
  3203. || strncmp(p, "wq", cnt) == 0
  3204. || strncmp(p, "wn", cnt) == 0
  3205. || (p[0] == 'x' && !p[1])
  3206. ) {
  3207. cnt = file_write(current_filename, text, end - 1);
  3208. if (cnt < 0) {
  3209. if (cnt == -1)
  3210. status_line_bold("Write error: %s", strerror(errno));
  3211. } else {
  3212. file_modified = 0;
  3213. last_file_modified = -1;
  3214. status_line("'%s' %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
  3215. if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
  3216. || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
  3217. ) {
  3218. editing = 0;
  3219. }
  3220. }
  3221. } else if (strncmp(p, "file", cnt) == 0) {
  3222. last_status_cksum = 0; // force status update
  3223. } else if (sscanf(p, "%d", &j) > 0) {
  3224. dot = find_line(j); // go to line # j
  3225. dot_skip_over_ws();
  3226. } else { // unrecognized cmd
  3227. not_implemented(p);
  3228. }
  3229. #endif /* !FEATURE_VI_COLON */
  3230. break;
  3231. case '<': // <- Left shift something
  3232. case '>': // >- Right shift something
  3233. cnt = count_lines(text, dot); // remember what line we are on
  3234. c1 = get_one_char(); // get the type of thing to delete
  3235. find_range(&p, &q, c1);
  3236. yank_delete(p, q, 1, YANKONLY); // save copy before change
  3237. p = begin_line(p);
  3238. q = end_line(q);
  3239. i = count_lines(p, q); // # of lines we are shifting
  3240. for ( ; i > 0; i--, p = next_line(p)) {
  3241. if (c == '<') {
  3242. // shift left- remove tab or 8 spaces
  3243. if (*p == '\t') {
  3244. // shrink buffer 1 char
  3245. text_hole_delete(p, p);
  3246. } else if (*p == ' ') {
  3247. // we should be calculating columns, not just SPACE
  3248. for (j = 0; *p == ' ' && j < tabstop; j++) {
  3249. text_hole_delete(p, p);
  3250. }
  3251. }
  3252. } else if (c == '>') {
  3253. // shift right -- add tab or 8 spaces
  3254. char_insert(p, '\t');
  3255. }
  3256. }
  3257. dot = find_line(cnt); // what line were we on
  3258. dot_skip_over_ws();
  3259. end_cmd_q(); // stop adding to q
  3260. break;
  3261. case 'A': // A- append at e-o-l
  3262. dot_end(); // go to e-o-l
  3263. //**** fall through to ... 'a'
  3264. case 'a': // a- append after current char
  3265. if (*dot != '\n')
  3266. dot++;
  3267. goto dc_i;
  3268. break;
  3269. case 'B': // B- back a blank-delimited Word
  3270. case 'E': // E- end of a blank-delimited word
  3271. case 'W': // W- forward a blank-delimited word
  3272. dir = FORWARD;
  3273. if (c == 'B')
  3274. dir = BACK;
  3275. do {
  3276. if (c == 'W' || isspace(dot[dir])) {
  3277. dot = skip_thing(dot, 1, dir, S_TO_WS);
  3278. dot = skip_thing(dot, 2, dir, S_OVER_WS);
  3279. }
  3280. if (c != 'W')
  3281. dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
  3282. } while (--cmdcnt > 0);
  3283. break;
  3284. case 'C': // C- Change to e-o-l
  3285. case 'D': // D- delete to e-o-l
  3286. save_dot = dot;
  3287. dot = dollar_line(dot); // move to before NL
  3288. // copy text into a register and delete
  3289. dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
  3290. if (c == 'C')
  3291. goto dc_i; // start inserting
  3292. #if ENABLE_FEATURE_VI_DOT_CMD
  3293. if (c == 'D')
  3294. end_cmd_q(); // stop adding to q
  3295. #endif
  3296. break;
  3297. case 'g': // 'gg' goto a line number (vim) (default: very first line)
  3298. c1 = get_one_char();
  3299. if (c1 != 'g') {
  3300. buf[0] = 'g';
  3301. buf[1] = c1; // TODO: if Unicode?
  3302. buf[2] = '\0';
  3303. not_implemented(buf);
  3304. break;
  3305. }
  3306. if (cmdcnt == 0)
  3307. cmdcnt = 1;
  3308. /* fall through */
  3309. case 'G': // G- goto to a line number (default= E-O-F)
  3310. dot = end - 1; // assume E-O-F
  3311. if (cmdcnt > 0) {
  3312. dot = find_line(cmdcnt); // what line is #cmdcnt
  3313. }
  3314. dot_skip_over_ws();
  3315. break;
  3316. case 'H': // H- goto top line on screen
  3317. dot = screenbegin;
  3318. if (cmdcnt > (rows - 1)) {
  3319. cmdcnt = (rows - 1);
  3320. }
  3321. if (--cmdcnt > 0) {
  3322. do_cmd('+');
  3323. }
  3324. dot_skip_over_ws();
  3325. break;
  3326. case 'I': // I- insert before first non-blank
  3327. dot_begin(); // 0
  3328. dot_skip_over_ws();
  3329. //**** fall through to ... 'i'
  3330. case 'i': // i- insert before current char
  3331. case KEYCODE_INSERT: // Cursor Key Insert
  3332. dc_i:
  3333. cmd_mode = 1; // start inserting
  3334. break;
  3335. case 'J': // J- join current and next lines together
  3336. do {
  3337. dot_end(); // move to NL
  3338. if (dot < end - 1) { // make sure not last char in text[]
  3339. *dot++ = ' '; // replace NL with space
  3340. file_modified++;
  3341. while (isblank(*dot)) { // delete leading WS
  3342. dot_delete();
  3343. }
  3344. }
  3345. } while (--cmdcnt > 0);
  3346. end_cmd_q(); // stop adding to q
  3347. break;
  3348. case 'L': // L- goto bottom line on screen
  3349. dot = end_screen();
  3350. if (cmdcnt > (rows - 1)) {
  3351. cmdcnt = (rows - 1);
  3352. }
  3353. if (--cmdcnt > 0) {
  3354. do_cmd('-');
  3355. }
  3356. dot_begin();
  3357. dot_skip_over_ws();
  3358. break;
  3359. case 'M': // M- goto middle line on screen
  3360. dot = screenbegin;
  3361. for (cnt = 0; cnt < (rows-1) / 2; cnt++)
  3362. dot = next_line(dot);
  3363. break;
  3364. case 'O': // O- open a empty line above
  3365. // 0i\n ESC -i
  3366. p = begin_line(dot);
  3367. if (p[-1] == '\n') {
  3368. dot_prev();
  3369. case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
  3370. dot_end();
  3371. dot = char_insert(dot, '\n');
  3372. } else {
  3373. dot_begin(); // 0
  3374. dot = char_insert(dot, '\n'); // i\n ESC
  3375. dot_prev(); // -
  3376. }
  3377. goto dc_i;
  3378. break;
  3379. case 'R': // R- continuous Replace char
  3380. dc5:
  3381. cmd_mode = 2;
  3382. break;
  3383. case KEYCODE_DELETE:
  3384. c = 'x';
  3385. // fall through
  3386. case 'X': // X- delete char before dot
  3387. case 'x': // x- delete the current char
  3388. case 's': // s- substitute the current char
  3389. dir = 0;
  3390. if (c == 'X')
  3391. dir = -1;
  3392. do {
  3393. if (dot[dir] != '\n') {
  3394. if (c == 'X')
  3395. dot--; // delete prev char
  3396. dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
  3397. }
  3398. } while (--cmdcnt > 0);
  3399. end_cmd_q(); // stop adding to q
  3400. if (c == 's')
  3401. goto dc_i; // start inserting
  3402. break;
  3403. case 'Z': // Z- if modified, {write}; exit
  3404. // ZZ means to save file (if necessary), then exit
  3405. c1 = get_one_char();
  3406. if (c1 != 'Z') {
  3407. indicate_error(c);
  3408. break;
  3409. }
  3410. if (file_modified) {
  3411. if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
  3412. status_line_bold("'%s' is read only", current_filename);
  3413. break;
  3414. }
  3415. cnt = file_write(current_filename, text, end - 1);
  3416. if (cnt < 0) {
  3417. if (cnt == -1)
  3418. status_line_bold("Write error: %s", strerror(errno));
  3419. } else if (cnt == (end - 1 - text + 1)) {
  3420. editing = 0;
  3421. }
  3422. } else {
  3423. editing = 0;
  3424. }
  3425. break;
  3426. case '^': // ^- move to first non-blank on line
  3427. dot_begin();
  3428. dot_skip_over_ws();
  3429. break;
  3430. case 'b': // b- back a word
  3431. case 'e': // e- end of word
  3432. dir = FORWARD;
  3433. if (c == 'b')
  3434. dir = BACK;
  3435. do {
  3436. if ((dot + dir) < text || (dot + dir) > end - 1)
  3437. break;
  3438. dot += dir;
  3439. if (isspace(*dot)) {
  3440. dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
  3441. }
  3442. if (isalnum(*dot) || *dot == '_') {
  3443. dot = skip_thing(dot, 1, dir, S_END_ALNUM);
  3444. } else if (ispunct(*dot)) {
  3445. dot = skip_thing(dot, 1, dir, S_END_PUNCT);
  3446. }
  3447. } while (--cmdcnt > 0);
  3448. break;
  3449. case 'c': // c- change something
  3450. case 'd': // d- delete something
  3451. #if ENABLE_FEATURE_VI_YANKMARK
  3452. case 'y': // y- yank something
  3453. case 'Y': // Y- Yank a line
  3454. #endif
  3455. {
  3456. int yf, ml, whole = 0;
  3457. yf = YANKDEL; // assume either "c" or "d"
  3458. #if ENABLE_FEATURE_VI_YANKMARK
  3459. if (c == 'y' || c == 'Y')
  3460. yf = YANKONLY;
  3461. #endif
  3462. c1 = 'y';
  3463. if (c != 'Y')
  3464. c1 = get_one_char(); // get the type of thing to delete
  3465. // determine range, and whether it spans lines
  3466. ml = find_range(&p, &q, c1);
  3467. if (c1 == 27) { // ESC- user changed mind and wants out
  3468. c = c1 = 27; // Escape- do nothing
  3469. } else if (strchr("wW", c1)) {
  3470. if (c == 'c') {
  3471. // don't include trailing WS as part of word
  3472. while (isblank(*q)) {
  3473. if (q <= text || q[-1] == '\n')
  3474. break;
  3475. q--;
  3476. }
  3477. }
  3478. dot = yank_delete(p, q, ml, yf); // delete word
  3479. } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
  3480. // partial line copy text into a register and delete
  3481. dot = yank_delete(p, q, ml, yf); // delete word
  3482. } else if (strchr("cdykjHL+-{}\r\n", c1)) {
  3483. // whole line copy text into a register and delete
  3484. dot = yank_delete(p, q, ml, yf); // delete lines
  3485. whole = 1;
  3486. } else {
  3487. // could not recognize object
  3488. c = c1 = 27; // error-
  3489. ml = 0;
  3490. indicate_error(c);
  3491. }
  3492. if (ml && whole) {
  3493. if (c == 'c') {
  3494. dot = char_insert(dot, '\n');
  3495. // on the last line of file don't move to prev line
  3496. if (whole && dot != (end-1)) {
  3497. dot_prev();
  3498. }
  3499. } else if (c == 'd') {
  3500. dot_begin();
  3501. dot_skip_over_ws();
  3502. }
  3503. }
  3504. if (c1 != 27) {
  3505. // if CHANGING, not deleting, start inserting after the delete
  3506. if (c == 'c') {
  3507. strcpy(buf, "Change");
  3508. goto dc_i; // start inserting
  3509. }
  3510. if (c == 'd') {
  3511. strcpy(buf, "Delete");
  3512. }
  3513. #if ENABLE_FEATURE_VI_YANKMARK
  3514. if (c == 'y' || c == 'Y') {
  3515. strcpy(buf, "Yank");
  3516. }
  3517. p = reg[YDreg];
  3518. q = p + strlen(p);
  3519. for (cnt = 0; p <= q; p++) {
  3520. if (*p == '\n')
  3521. cnt++;
  3522. }
  3523. status_line("%s %d lines (%d chars) using [%c]",
  3524. buf, cnt, strlen(reg[YDreg]), what_reg());
  3525. #endif
  3526. end_cmd_q(); // stop adding to q
  3527. }
  3528. break;
  3529. }
  3530. case 'k': // k- goto prev line, same col
  3531. case KEYCODE_UP: // cursor key Up
  3532. do {
  3533. dot_prev();
  3534. dot = move_to_col(dot, ccol + offset); // try stay in same col
  3535. } while (--cmdcnt > 0);
  3536. break;
  3537. case 'r': // r- replace the current char with user input
  3538. c1 = get_one_char(); // get the replacement char
  3539. if (*dot != '\n') {
  3540. *dot = c1;
  3541. file_modified++;
  3542. }
  3543. end_cmd_q(); // stop adding to q
  3544. break;
  3545. case 't': // t- move to char prior to next x
  3546. last_forward_char = get_one_char();
  3547. do_cmd(';');
  3548. if (*dot == last_forward_char)
  3549. dot_left();
  3550. last_forward_char = 0;
  3551. break;
  3552. case 'w': // w- forward a word
  3553. do {
  3554. if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
  3555. dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
  3556. } else if (ispunct(*dot)) { // we are on PUNCT
  3557. dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
  3558. }
  3559. if (dot < end - 1)
  3560. dot++; // move over word
  3561. if (isspace(*dot)) {
  3562. dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
  3563. }
  3564. } while (--cmdcnt > 0);
  3565. break;
  3566. case 'z': // z-
  3567. c1 = get_one_char(); // get the replacement char
  3568. cnt = 0;
  3569. if (c1 == '.')
  3570. cnt = (rows - 2) / 2; // put dot at center
  3571. if (c1 == '-')
  3572. cnt = rows - 2; // put dot at bottom
  3573. screenbegin = begin_line(dot); // start dot at top
  3574. dot_scroll(cnt, -1);
  3575. break;
  3576. case '|': // |- move to column "cmdcnt"
  3577. dot = move_to_col(dot, cmdcnt - 1); // try to move to column
  3578. break;
  3579. case '~': // ~- flip the case of letters a-z -> A-Z
  3580. do {
  3581. if (islower(*dot)) {
  3582. *dot = toupper(*dot);
  3583. file_modified++;
  3584. } else if (isupper(*dot)) {
  3585. *dot = tolower(*dot);
  3586. file_modified++;
  3587. }
  3588. dot_right();
  3589. } while (--cmdcnt > 0);
  3590. end_cmd_q(); // stop adding to q
  3591. break;
  3592. //----- The Cursor and Function Keys -----------------------------
  3593. case KEYCODE_HOME: // Cursor Key Home
  3594. dot_begin();
  3595. break;
  3596. // The Fn keys could point to do_macro which could translate them
  3597. #if 0
  3598. case KEYCODE_FUN1: // Function Key F1
  3599. case KEYCODE_FUN2: // Function Key F2
  3600. case KEYCODE_FUN3: // Function Key F3
  3601. case KEYCODE_FUN4: // Function Key F4
  3602. case KEYCODE_FUN5: // Function Key F5
  3603. case KEYCODE_FUN6: // Function Key F6
  3604. case KEYCODE_FUN7: // Function Key F7
  3605. case KEYCODE_FUN8: // Function Key F8
  3606. case KEYCODE_FUN9: // Function Key F9
  3607. case KEYCODE_FUN10: // Function Key F10
  3608. case KEYCODE_FUN11: // Function Key F11
  3609. case KEYCODE_FUN12: // Function Key F12
  3610. break;
  3611. #endif
  3612. }
  3613. dc1:
  3614. // if text[] just became empty, add back an empty line
  3615. if (end == text) {
  3616. char_insert(text, '\n'); // start empty buf with dummy line
  3617. dot = text;
  3618. }
  3619. // it is OK for dot to exactly equal to end, otherwise check dot validity
  3620. if (dot != end) {
  3621. dot = bound_dot(dot); // make sure "dot" is valid
  3622. }
  3623. #if ENABLE_FEATURE_VI_YANKMARK
  3624. check_context(c); // update the current context
  3625. #endif
  3626. if (!isdigit(c))
  3627. cmdcnt = 0; // cmd was not a number, reset cmdcnt
  3628. cnt = dot - begin_line(dot);
  3629. // Try to stay off of the Newline
  3630. if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
  3631. dot--;
  3632. }
  3633. /* NB! the CRASHME code is unmaintained, and doesn't currently build */
  3634. #if ENABLE_FEATURE_VI_CRASHME
  3635. static int totalcmds = 0;
  3636. static int Mp = 85; // Movement command Probability
  3637. static int Np = 90; // Non-movement command Probability
  3638. static int Dp = 96; // Delete command Probability
  3639. static int Ip = 97; // Insert command Probability
  3640. static int Yp = 98; // Yank command Probability
  3641. static int Pp = 99; // Put command Probability
  3642. static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
  3643. static const char chars[20] = "\t012345 abcdABCD-=.$";
  3644. static const char *const words[20] = {
  3645. "this", "is", "a", "test",
  3646. "broadcast", "the", "emergency", "of",
  3647. "system", "quick", "brown", "fox",
  3648. "jumped", "over", "lazy", "dogs",
  3649. "back", "January", "Febuary", "March"
  3650. };
  3651. static const char *const lines[20] = {
  3652. "You should have received a copy of the GNU General Public License\n",
  3653. "char c, cm, *cmd, *cmd1;\n",
  3654. "generate a command by percentages\n",
  3655. "Numbers may be typed as a prefix to some commands.\n",
  3656. "Quit, discarding changes!\n",
  3657. "Forced write, if permission originally not valid.\n",
  3658. "In general, any ex or ed command (such as substitute or delete).\n",
  3659. "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
  3660. "Please get w/ me and I will go over it with you.\n",
  3661. "The following is a list of scheduled, committed changes.\n",
  3662. "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
  3663. "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
  3664. "Any question about transactions please contact Sterling Huxley.\n",
  3665. "I will try to get back to you by Friday, December 31.\n",
  3666. "This Change will be implemented on Friday.\n",
  3667. "Let me know if you have problems accessing this;\n",
  3668. "Sterling Huxley recently added you to the access list.\n",
  3669. "Would you like to go to lunch?\n",
  3670. "The last command will be automatically run.\n",
  3671. "This is too much english for a computer geek.\n",
  3672. };
  3673. static char *multilines[20] = {
  3674. "You should have received a copy of the GNU General Public License\n",
  3675. "char c, cm, *cmd, *cmd1;\n",
  3676. "generate a command by percentages\n",
  3677. "Numbers may be typed as a prefix to some commands.\n",
  3678. "Quit, discarding changes!\n",
  3679. "Forced write, if permission originally not valid.\n",
  3680. "In general, any ex or ed command (such as substitute or delete).\n",
  3681. "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
  3682. "Please get w/ me and I will go over it with you.\n",
  3683. "The following is a list of scheduled, committed changes.\n",
  3684. "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
  3685. "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
  3686. "Any question about transactions please contact Sterling Huxley.\n",
  3687. "I will try to get back to you by Friday, December 31.\n",
  3688. "This Change will be implemented on Friday.\n",
  3689. "Let me know if you have problems accessing this;\n",
  3690. "Sterling Huxley recently added you to the access list.\n",
  3691. "Would you like to go to lunch?\n",
  3692. "The last command will be automatically run.\n",
  3693. "This is too much english for a computer geek.\n",
  3694. };
  3695. // create a random command to execute
  3696. static void crash_dummy()
  3697. {
  3698. static int sleeptime; // how long to pause between commands
  3699. char c, cm, *cmd, *cmd1;
  3700. int i, cnt, thing, rbi, startrbi, percent;
  3701. // "dot" movement commands
  3702. cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
  3703. // is there already a command running?
  3704. if (readbuffer[0] > 0)
  3705. goto cd1;
  3706. cd0:
  3707. readbuffer[0] = 'X';
  3708. startrbi = rbi = 1;
  3709. sleeptime = 0; // how long to pause between commands
  3710. memset(readbuffer, '\0', sizeof(readbuffer));
  3711. // generate a command by percentages
  3712. percent = (int) lrand48() % 100; // get a number from 0-99
  3713. if (percent < Mp) { // Movement commands
  3714. // available commands
  3715. cmd = cmd1;
  3716. M++;
  3717. } else if (percent < Np) { // non-movement commands
  3718. cmd = "mz<>\'\""; // available commands
  3719. N++;
  3720. } else if (percent < Dp) { // Delete commands
  3721. cmd = "dx"; // available commands
  3722. D++;
  3723. } else if (percent < Ip) { // Inset commands
  3724. cmd = "iIaAsrJ"; // available commands
  3725. I++;
  3726. } else if (percent < Yp) { // Yank commands
  3727. cmd = "yY"; // available commands
  3728. Y++;
  3729. } else if (percent < Pp) { // Put commands
  3730. cmd = "pP"; // available commands
  3731. P++;
  3732. } else {
  3733. // We do not know how to handle this command, try again
  3734. U++;
  3735. goto cd0;
  3736. }
  3737. // randomly pick one of the available cmds from "cmd[]"
  3738. i = (int) lrand48() % strlen(cmd);
  3739. cm = cmd[i];
  3740. if (strchr(":\024", cm))
  3741. goto cd0; // dont allow colon or ctrl-T commands
  3742. readbuffer[rbi++] = cm; // put cmd into input buffer
  3743. // now we have the command-
  3744. // there are 1, 2, and multi char commands
  3745. // find out which and generate the rest of command as necessary
  3746. if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
  3747. cmd1 = " \n\r0$^-+wWeEbBhjklHL";
  3748. if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
  3749. cmd1 = "abcdefghijklmnopqrstuvwxyz";
  3750. }
  3751. thing = (int) lrand48() % strlen(cmd1); // pick a movement command
  3752. c = cmd1[thing];
  3753. readbuffer[rbi++] = c; // add movement to input buffer
  3754. }
  3755. if (strchr("iIaAsc", cm)) { // multi-char commands
  3756. if (cm == 'c') {
  3757. // change some thing
  3758. thing = (int) lrand48() % strlen(cmd1); // pick a movement command
  3759. c = cmd1[thing];
  3760. readbuffer[rbi++] = c; // add movement to input buffer
  3761. }
  3762. thing = (int) lrand48() % 4; // what thing to insert
  3763. cnt = (int) lrand48() % 10; // how many to insert
  3764. for (i = 0; i < cnt; i++) {
  3765. if (thing == 0) { // insert chars
  3766. readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
  3767. } else if (thing == 1) { // insert words
  3768. strcat(readbuffer, words[(int) lrand48() % 20]);
  3769. strcat(readbuffer, " ");
  3770. sleeptime = 0; // how fast to type
  3771. } else if (thing == 2) { // insert lines
  3772. strcat(readbuffer, lines[(int) lrand48() % 20]);
  3773. sleeptime = 0; // how fast to type
  3774. } else { // insert multi-lines
  3775. strcat(readbuffer, multilines[(int) lrand48() % 20]);
  3776. sleeptime = 0; // how fast to type
  3777. }
  3778. }
  3779. strcat(readbuffer, "\033");
  3780. }
  3781. readbuffer[0] = strlen(readbuffer + 1);
  3782. cd1:
  3783. totalcmds++;
  3784. if (sleeptime > 0)
  3785. mysleep(sleeptime); // sleep 1/100 sec
  3786. }
  3787. // test to see if there are any errors
  3788. static void crash_test()
  3789. {
  3790. static time_t oldtim;
  3791. time_t tim;
  3792. char d[2], msg[80];
  3793. msg[0] = '\0';
  3794. if (end < text) {
  3795. strcat(msg, "end<text ");
  3796. }
  3797. if (end > textend) {
  3798. strcat(msg, "end>textend ");
  3799. }
  3800. if (dot < text) {
  3801. strcat(msg, "dot<text ");
  3802. }
  3803. if (dot > end) {
  3804. strcat(msg, "dot>end ");
  3805. }
  3806. if (screenbegin < text) {
  3807. strcat(msg, "screenbegin<text ");
  3808. }
  3809. if (screenbegin > end - 1) {
  3810. strcat(msg, "screenbegin>end-1 ");
  3811. }
  3812. if (msg[0]) {
  3813. printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
  3814. totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
  3815. fflush_all();
  3816. while (safe_read(STDIN_FILENO, d, 1) > 0) {
  3817. if (d[0] == '\n' || d[0] == '\r')
  3818. break;
  3819. }
  3820. }
  3821. tim = time(NULL);
  3822. if (tim >= (oldtim + 3)) {
  3823. sprintf(status_buffer,
  3824. "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
  3825. totalcmds, M, N, I, D, Y, P, U, end - text + 1);
  3826. oldtim = tim;
  3827. }
  3828. }
  3829. #endif