vi.c 111 KB

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