vi.c 131 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052
  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. // ./.exrc
  11. // add magic to search /foo.*bar
  12. // add :help command
  13. // :map macros
  14. // if mark[] values were line numbers rather than pointers
  15. // it would be easier to change the mark when add/delete lines
  16. // More intelligence in refresh()
  17. // ":r !cmd" and "!cmd" to filter text through an external command
  18. // An "ex" line oriented mode- maybe using "cmdedit"
  19. //config:config VI
  20. //config: bool "vi (23 kb)"
  21. //config: default y
  22. //config: help
  23. //config: 'vi' is a text editor. More specifically, it is the One True
  24. //config: text editor <grin>. It does, however, have a rather steep
  25. //config: learning curve. If you are not already comfortable with 'vi'
  26. //config: you may wish to use something else.
  27. //config:
  28. //config:config FEATURE_VI_MAX_LEN
  29. //config: int "Maximum screen width"
  30. //config: range 256 16384
  31. //config: default 4096
  32. //config: depends on VI
  33. //config: help
  34. //config: Contrary to what you may think, this is not eating much.
  35. //config: Make it smaller than 4k only if you are very limited on memory.
  36. //config:
  37. //config:config FEATURE_VI_8BIT
  38. //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
  39. //config: default n
  40. //config: depends on VI
  41. //config: help
  42. //config: If your terminal can display characters with high bit set,
  43. //config: you may want to enable this. Note: vi is not Unicode-capable.
  44. //config: If your terminal combines several 8-bit bytes into one character
  45. //config: (as in Unicode mode), this will not work properly.
  46. //config:
  47. //config:config FEATURE_VI_COLON
  48. //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
  49. //config: default y
  50. //config: depends on VI
  51. //config: help
  52. //config: Enable a limited set of colon commands. This does not
  53. //config: provide an "ex" mode.
  54. //config:
  55. //config:config FEATURE_VI_COLON_EXPAND
  56. //config: bool "Expand \"%\" and \"#\" in colon commands"
  57. //config: default y
  58. //config: depends on FEATURE_VI_COLON
  59. //config: help
  60. //config: Expand the special characters \"%\" (current filename)
  61. //config: and \"#\" (alternate filename) in colon commands.
  62. //config:
  63. //config:config FEATURE_VI_YANKMARK
  64. //config: bool "Enable yank/put commands and mark cmds"
  65. //config: default y
  66. //config: depends on VI
  67. //config: help
  68. //config: This enables you to use yank and put, as well as mark.
  69. //config:
  70. //config:config FEATURE_VI_SEARCH
  71. //config: bool "Enable search and replace cmds"
  72. //config: default y
  73. //config: depends on VI
  74. //config: help
  75. //config: Select this if you wish to be able to do search and replace.
  76. //config:
  77. //config:config FEATURE_VI_REGEX_SEARCH
  78. //config: bool "Enable regex in search and replace"
  79. //config: default n # Uses GNU regex, which may be unavailable. FIXME
  80. //config: depends on FEATURE_VI_SEARCH
  81. //config: help
  82. //config: Use extended regex search.
  83. //config:
  84. //config:config FEATURE_VI_USE_SIGNALS
  85. //config: bool "Catch signals"
  86. //config: default y
  87. //config: depends on VI
  88. //config: help
  89. //config: Selecting this option will make vi signal aware. This will support
  90. //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
  91. //config:
  92. //config:config FEATURE_VI_DOT_CMD
  93. //config: bool "Remember previous cmd and \".\" cmd"
  94. //config: default y
  95. //config: depends on VI
  96. //config: help
  97. //config: Make vi remember the last command and be able to repeat it.
  98. //config:
  99. //config:config FEATURE_VI_READONLY
  100. //config: bool "Enable -R option and \"view\" mode"
  101. //config: default y
  102. //config: depends on VI
  103. //config: help
  104. //config: Enable the read-only command line option, which allows the user to
  105. //config: open a file in read-only mode.
  106. //config:
  107. //config:config FEATURE_VI_SETOPTS
  108. //config: bool "Enable settable options, ai ic showmatch"
  109. //config: default y
  110. //config: depends on VI
  111. //config: help
  112. //config: Enable the editor to set some (ai, ic, showmatch) options.
  113. //config:
  114. //config:config FEATURE_VI_SET
  115. //config: bool "Support :set"
  116. //config: default y
  117. //config: depends on VI
  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: 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: This is not clean but helps a lot on serial lines and such.
  136. //config:
  137. //config:config FEATURE_VI_UNDO
  138. //config: bool "Support undo command \"u\""
  139. //config: default y
  140. //config: depends on VI
  141. //config: help
  142. //config: Support the 'u' command to undo insertion, deletion, and replacement
  143. //config: of text.
  144. //config:
  145. //config:config FEATURE_VI_UNDO_QUEUE
  146. //config: bool "Enable undo operation queuing"
  147. //config: default y
  148. //config: depends on FEATURE_VI_UNDO
  149. //config: help
  150. //config: The vi undo functions can use an intermediate queue to greatly lower
  151. //config: malloc() calls and overhead. When the maximum size of this queue is
  152. //config: reached, the contents of the queue are committed to the undo stack.
  153. //config: This increases the size of the undo code and allows some undo
  154. //config: operations (especially un-typing/backspacing) to be far more useful.
  155. //config:
  156. //config:config FEATURE_VI_UNDO_QUEUE_MAX
  157. //config: int "Maximum undo character queue size"
  158. //config: default 256
  159. //config: range 32 65536
  160. //config: depends on FEATURE_VI_UNDO_QUEUE
  161. //config: help
  162. //config: This option sets the number of bytes used at runtime for the queue.
  163. //config: Smaller values will create more undo objects and reduce the amount
  164. //config: of typed or backspaced characters that are grouped into one undo
  165. //config: operation; larger values increase the potential size of each undo
  166. //config: and will generally malloc() larger objects and less frequently.
  167. //config: Unless you want more (or less) frequent "undo points" while typing,
  168. //config: you should probably leave this unchanged.
  169. //config:
  170. //config:config FEATURE_VI_VERBOSE_STATUS
  171. //config: bool "Enable verbose status reporting"
  172. //config: default y
  173. //config: depends on VI
  174. //config: help
  175. //config: Enable more verbose reporting of the results of yank, change,
  176. //config: delete, undo and substitution commands.
  177. //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
  178. //kbuild:lib-$(CONFIG_VI) += vi.o
  179. //usage:#define vi_trivial_usage
  180. //usage: IF_FEATURE_VI_COLON("[-c CMD] ")IF_FEATURE_VI_READONLY("[-R] ")"[-H] [FILE]..."
  181. //usage:#define vi_full_usage "\n\n"
  182. //usage: "Edit FILE\n"
  183. //usage: IF_FEATURE_VI_COLON(
  184. //usage: "\n -c CMD Initial command to run ($EXINIT and ~/.exrc also available)"
  185. //usage: )
  186. //usage: IF_FEATURE_VI_READONLY(
  187. //usage: "\n -R Read-only"
  188. //usage: )
  189. //usage: "\n -H List available features"
  190. // note: non-standard, "vim -H" is Hebrew mode (bidi support)
  191. #include "libbb.h"
  192. // Should be after libbb.h: on some systems regex.h needs sys/types.h:
  193. #if ENABLE_FEATURE_VI_REGEX_SEARCH
  194. # include <regex.h>
  195. #endif
  196. // the CRASHME code is unmaintained, and doesn't currently build
  197. #define ENABLE_FEATURE_VI_CRASHME 0
  198. #define IF_FEATURE_VI_CRASHME(...)
  199. #if ENABLE_LOCALE_SUPPORT
  200. #if ENABLE_FEATURE_VI_8BIT
  201. //FIXME: this does not work properly for Unicode anyway
  202. # define Isprint(c) (isprint)(c)
  203. #else
  204. # define Isprint(c) isprint_asciionly(c)
  205. #endif
  206. #else
  207. // 0x9b is Meta-ESC
  208. #if ENABLE_FEATURE_VI_8BIT
  209. # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
  210. #else
  211. # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
  212. #endif
  213. #endif
  214. #define isbackspace(c) ((c) == term_orig.c_cc[VERASE] || (c) == 8 || (c) == 127)
  215. enum {
  216. MAX_TABSTOP = 32, // sanity limit
  217. // User input len. Need not be extra big.
  218. // Lines in file being edited *can* be bigger than this.
  219. MAX_INPUT_LEN = 128,
  220. // Sanity limits. We have only one buffer of this size.
  221. MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
  222. MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
  223. };
  224. // VT102 ESC sequences.
  225. // See "Xterm Control Sequences"
  226. // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
  227. #define ESC "\033"
  228. // Inverse/Normal text
  229. #define ESC_BOLD_TEXT ESC"[7m"
  230. #define ESC_NORM_TEXT ESC"[m"
  231. // Bell
  232. #define ESC_BELL "\007"
  233. // Clear-to-end-of-line
  234. #define ESC_CLEAR2EOL ESC"[K"
  235. // Clear-to-end-of-screen.
  236. // (We use default param here.
  237. // Full sequence is "ESC [ <num> J",
  238. // <num> is 0/1/2 = "erase below/above/all".)
  239. #define ESC_CLEAR2EOS ESC"[J"
  240. // Cursor to given coordinate (1,1: top left)
  241. #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
  242. #define ESC_SET_CURSOR_TOPLEFT ESC"[H"
  243. //UNUSED
  244. //// Cursor up and down
  245. //#define ESC_CURSOR_UP ESC"[A"
  246. //#define ESC_CURSOR_DOWN "\n"
  247. #if ENABLE_FEATURE_VI_DOT_CMD
  248. // cmds modifying text[]
  249. static const char modifying_cmds[] ALIGN1 = "aAcCdDiIJoOpPrRs""xX<>~";
  250. #endif
  251. enum {
  252. YANKONLY = FALSE,
  253. YANKDEL = TRUE,
  254. FORWARD = 1, // code depends on "1" for array index
  255. BACK = -1, // code depends on "-1" for array index
  256. LIMITED = 0, // char_search() only current line
  257. FULL = 1, // char_search() to the end/beginning of entire text
  258. PARTIAL = 0, // buffer contains partial line
  259. WHOLE = 1, // buffer contains whole lines
  260. MULTI = 2, // buffer may include newlines
  261. S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
  262. S_TO_WS = 2, // used in skip_thing() for moving "dot"
  263. S_OVER_WS = 3, // used in skip_thing() for moving "dot"
  264. S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
  265. S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
  266. C_END = -1, // cursor is at end of line due to '$' command
  267. };
  268. // vi.c expects chars to be unsigned.
  269. // busybox build system provides that, but it's better
  270. // to audit and fix the source
  271. struct globals {
  272. // many references - keep near the top of globals
  273. char *text, *end; // pointers to the user data in memory
  274. char *dot; // where all the action takes place
  275. int text_size; // size of the allocated buffer
  276. // the rest
  277. #if ENABLE_FEATURE_VI_SETOPTS
  278. smallint vi_setops; // set by setops()
  279. #define VI_AUTOINDENT (1 << 0)
  280. #define VI_EXPANDTAB (1 << 1)
  281. #define VI_ERR_METHOD (1 << 2)
  282. #define VI_IGNORECASE (1 << 3)
  283. #define VI_SHOWMATCH (1 << 4)
  284. #define VI_TABSTOP (1 << 5)
  285. #define autoindent (vi_setops & VI_AUTOINDENT)
  286. #define expandtab (vi_setops & VI_EXPANDTAB )
  287. #define err_method (vi_setops & VI_ERR_METHOD) // indicate error with beep or flash
  288. #define ignorecase (vi_setops & VI_IGNORECASE)
  289. #define showmatch (vi_setops & VI_SHOWMATCH )
  290. // order of constants and strings must match
  291. #define OPTS_STR \
  292. "ai\0""autoindent\0" \
  293. "et\0""expandtab\0" \
  294. "fl\0""flash\0" \
  295. "ic\0""ignorecase\0" \
  296. "sm\0""showmatch\0" \
  297. "ts\0""tabstop\0"
  298. #else
  299. #define autoindent (0)
  300. #define expandtab (0)
  301. #define err_method (0)
  302. #define ignorecase (0)
  303. #endif
  304. #if ENABLE_FEATURE_VI_READONLY
  305. smallint readonly_mode;
  306. #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
  307. #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
  308. #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
  309. #else
  310. #define SET_READONLY_FILE(flags) ((void)0)
  311. #define SET_READONLY_MODE(flags) ((void)0)
  312. #define UNSET_READONLY_FILE(flags) ((void)0)
  313. #endif
  314. smallint editing; // >0 while we are editing a file
  315. // [code audit says "can be 0, 1 or 2 only"]
  316. smallint cmd_mode; // 0=command 1=insert 2=replace
  317. int modified_count; // buffer contents changed if !0
  318. int last_modified_count; // = -1;
  319. int cmdline_filecnt; // how many file names on cmd line
  320. int cmdcnt; // repetition count
  321. char *rstart; // start of text in Replace mode
  322. unsigned rows, columns; // the terminal screen is this size
  323. #if ENABLE_FEATURE_VI_ASK_TERMINAL
  324. int get_rowcol_error;
  325. #endif
  326. int crow, ccol; // cursor is on Crow x Ccol
  327. int offset; // chars scrolled off the screen to the left
  328. int have_status_msg; // is default edit status needed?
  329. // [don't make smallint!]
  330. int last_status_cksum; // hash of current status line
  331. char *current_filename;
  332. #if ENABLE_FEATURE_VI_COLON_EXPAND
  333. char *alt_filename;
  334. #endif
  335. char *screenbegin; // index into text[], of top line on the screen
  336. char *screen; // pointer to the virtual screen buffer
  337. int screensize; // and its size
  338. int tabstop;
  339. int last_search_char; // last char searched for (int because of Unicode)
  340. smallint last_search_cmd; // command used to invoke last char search
  341. #if ENABLE_FEATURE_VI_CRASHME
  342. char last_input_char; // last char read from user
  343. #endif
  344. #if ENABLE_FEATURE_VI_UNDO_QUEUE
  345. char undo_queue_state; // One of UNDO_INS, UNDO_DEL, UNDO_EMPTY
  346. #endif
  347. #if ENABLE_FEATURE_VI_DOT_CMD
  348. smallint adding2q; // are we currently adding user input to q
  349. int lmc_len; // length of last_modifying_cmd
  350. char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
  351. int dotcnt; // number of times to repeat '.' command
  352. #endif
  353. #if ENABLE_FEATURE_VI_SEARCH
  354. char *last_search_pattern; // last pattern from a '/' or '?' search
  355. #endif
  356. #if ENABLE_FEATURE_VI_SETOPTS
  357. int char_insert__indentcol; // column of recent autoindent or 0
  358. int newindent; // autoindent value for 'O'/'cc' commands
  359. // or -1 to use indent from previous line
  360. #endif
  361. smallint cmd_error;
  362. // former statics
  363. #if ENABLE_FEATURE_VI_YANKMARK
  364. char *edit_file__cur_line;
  365. #endif
  366. int refresh__old_offset;
  367. int format_edit_status__tot;
  368. // a few references only
  369. #if ENABLE_FEATURE_VI_YANKMARK
  370. smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
  371. #define Ureg 27
  372. char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
  373. char regtype[28]; // buffer type: WHOLE, MULTI or PARTIAL
  374. char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
  375. #endif
  376. #if ENABLE_FEATURE_VI_USE_SIGNALS
  377. sigjmp_buf restart; // int_handler() jumps to location remembered here
  378. #endif
  379. struct termios term_orig; // remember what the cooked mode was
  380. int cindex; // saved character index for up/down motion
  381. smallint keep_index; // retain saved character index
  382. #if ENABLE_FEATURE_VI_COLON
  383. llist_t *initial_cmds;
  384. #endif
  385. // Should be just enough to hold a key sequence,
  386. // but CRASHME mode uses it as generated command buffer too
  387. #if ENABLE_FEATURE_VI_CRASHME
  388. char readbuffer[128];
  389. #else
  390. char readbuffer[KEYCODE_BUFFER_SIZE];
  391. #endif
  392. #define STATUS_BUFFER_LEN 200
  393. char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
  394. #if ENABLE_FEATURE_VI_DOT_CMD
  395. char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
  396. #endif
  397. char get_input_line__buf[MAX_INPUT_LEN]; // former static
  398. char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
  399. #if ENABLE_FEATURE_VI_UNDO
  400. // undo_push() operations
  401. #define UNDO_INS 0
  402. #define UNDO_DEL 1
  403. #define UNDO_INS_CHAIN 2
  404. #define UNDO_DEL_CHAIN 3
  405. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  406. #define UNDO_INS_QUEUED 4
  407. #define UNDO_DEL_QUEUED 5
  408. # endif
  409. // Pass-through flags for functions that can be undone
  410. #define NO_UNDO 0
  411. #define ALLOW_UNDO 1
  412. #define ALLOW_UNDO_CHAIN 2
  413. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  414. #define ALLOW_UNDO_QUEUED 3
  415. # else
  416. // If undo queuing disabled, don't invoke the missing queue logic
  417. #define ALLOW_UNDO_QUEUED ALLOW_UNDO
  418. # endif
  419. struct undo_object {
  420. struct undo_object *prev; // Linking back avoids list traversal (LIFO)
  421. int start; // Offset where the data should be restored/deleted
  422. int length; // total data size
  423. uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
  424. char undo_text[1]; // text that was deleted (if deletion)
  425. } *undo_stack_tail;
  426. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  427. #define UNDO_USE_SPOS 32
  428. #define UNDO_EMPTY 64
  429. char *undo_queue_spos; // Start position of queued operation
  430. int undo_q;
  431. char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
  432. # endif
  433. #endif /* ENABLE_FEATURE_VI_UNDO */
  434. };
  435. #define G (*ptr_to_globals)
  436. #define text (G.text )
  437. #define text_size (G.text_size )
  438. #define end (G.end )
  439. #define dot (G.dot )
  440. #define reg (G.reg )
  441. #define vi_setops (G.vi_setops )
  442. #define editing (G.editing )
  443. #define cmd_mode (G.cmd_mode )
  444. #define modified_count (G.modified_count )
  445. #define last_modified_count (G.last_modified_count)
  446. #define cmdline_filecnt (G.cmdline_filecnt )
  447. #define cmdcnt (G.cmdcnt )
  448. #define rstart (G.rstart )
  449. #define rows (G.rows )
  450. #define columns (G.columns )
  451. #define crow (G.crow )
  452. #define ccol (G.ccol )
  453. #define offset (G.offset )
  454. #define status_buffer (G.status_buffer )
  455. #define have_status_msg (G.have_status_msg )
  456. #define last_status_cksum (G.last_status_cksum )
  457. #define current_filename (G.current_filename )
  458. #define alt_filename (G.alt_filename )
  459. #define screen (G.screen )
  460. #define screensize (G.screensize )
  461. #define screenbegin (G.screenbegin )
  462. #define tabstop (G.tabstop )
  463. #define last_search_char (G.last_search_char )
  464. #define last_search_cmd (G.last_search_cmd )
  465. #if ENABLE_FEATURE_VI_CRASHME
  466. #define last_input_char (G.last_input_char )
  467. #endif
  468. #if ENABLE_FEATURE_VI_READONLY
  469. #define readonly_mode (G.readonly_mode )
  470. #else
  471. #define readonly_mode 0
  472. #endif
  473. #define adding2q (G.adding2q )
  474. #define lmc_len (G.lmc_len )
  475. #define ioq (G.ioq )
  476. #define ioq_start (G.ioq_start )
  477. #define dotcnt (G.dotcnt )
  478. #define last_search_pattern (G.last_search_pattern)
  479. #define char_insert__indentcol (G.char_insert__indentcol)
  480. #define newindent (G.newindent )
  481. #define cmd_error (G.cmd_error )
  482. #define edit_file__cur_line (G.edit_file__cur_line)
  483. #define refresh__old_offset (G.refresh__old_offset)
  484. #define format_edit_status__tot (G.format_edit_status__tot)
  485. #define YDreg (G.YDreg )
  486. //#define Ureg (G.Ureg )
  487. #define regtype (G.regtype )
  488. #define mark (G.mark )
  489. #define restart (G.restart )
  490. #define term_orig (G.term_orig )
  491. #define cindex (G.cindex )
  492. #define keep_index (G.keep_index )
  493. #define initial_cmds (G.initial_cmds )
  494. #define readbuffer (G.readbuffer )
  495. #define scr_out_buf (G.scr_out_buf )
  496. #define last_modifying_cmd (G.last_modifying_cmd )
  497. #define get_input_line__buf (G.get_input_line__buf)
  498. #if ENABLE_FEATURE_VI_UNDO
  499. #define undo_stack_tail (G.undo_stack_tail )
  500. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  501. #define undo_queue_state (G.undo_queue_state)
  502. #define undo_q (G.undo_q )
  503. #define undo_queue (G.undo_queue )
  504. #define undo_queue_spos (G.undo_queue_spos )
  505. # endif
  506. #endif
  507. #define INIT_G() do { \
  508. SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
  509. last_modified_count--; \
  510. /* "" but has space for 2 chars: */ \
  511. IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
  512. tabstop = 8; \
  513. IF_FEATURE_VI_SETOPTS(newindent--;) \
  514. } while (0)
  515. #if ENABLE_FEATURE_VI_CRASHME
  516. static int crashme = 0;
  517. #endif
  518. static void show_status_line(void); // put a message on the bottom line
  519. static void status_line_bold(const char *, ...);
  520. static void show_help(void)
  521. {
  522. puts("These features are available:"
  523. #if ENABLE_FEATURE_VI_SEARCH
  524. "\n\tPattern searches with / and ?"
  525. #endif
  526. #if ENABLE_FEATURE_VI_DOT_CMD
  527. "\n\tLast command repeat with ."
  528. #endif
  529. #if ENABLE_FEATURE_VI_YANKMARK
  530. "\n\tLine marking with 'x"
  531. "\n\tNamed buffers with \"x"
  532. #endif
  533. #if ENABLE_FEATURE_VI_READONLY
  534. //not implemented: "\n\tReadonly if vi is called as \"view\""
  535. //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
  536. #endif
  537. #if ENABLE_FEATURE_VI_SET
  538. "\n\tSome colon mode commands with :"
  539. #endif
  540. #if ENABLE_FEATURE_VI_SETOPTS
  541. "\n\tSettable options with \":set\""
  542. #endif
  543. #if ENABLE_FEATURE_VI_USE_SIGNALS
  544. "\n\tSignal catching- ^C"
  545. "\n\tJob suspend and resume with ^Z"
  546. #endif
  547. #if ENABLE_FEATURE_VI_WIN_RESIZE
  548. "\n\tAdapt to window re-sizes"
  549. #endif
  550. );
  551. }
  552. static void write1(const char *out)
  553. {
  554. fputs_stdout(out);
  555. }
  556. #if ENABLE_FEATURE_VI_WIN_RESIZE
  557. static int query_screen_dimensions(void)
  558. {
  559. int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
  560. if (rows > MAX_SCR_ROWS)
  561. rows = MAX_SCR_ROWS;
  562. if (columns > MAX_SCR_COLS)
  563. columns = MAX_SCR_COLS;
  564. return err;
  565. }
  566. #else
  567. static ALWAYS_INLINE int query_screen_dimensions(void)
  568. {
  569. return 0;
  570. }
  571. #endif
  572. // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
  573. static int mysleep(int hund)
  574. {
  575. struct pollfd pfd[1];
  576. if (hund != 0)
  577. fflush_all();
  578. pfd[0].fd = STDIN_FILENO;
  579. pfd[0].events = POLLIN;
  580. return safe_poll(pfd, 1, hund*10) > 0;
  581. }
  582. //----- Set terminal attributes --------------------------------
  583. static void rawmode(void)
  584. {
  585. // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
  586. set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
  587. }
  588. static void cookmode(void)
  589. {
  590. fflush_all();
  591. tcsetattr_stdin_TCSANOW(&term_orig);
  592. }
  593. //----- Terminal Drawing ---------------------------------------
  594. // The terminal is made up of 'rows' line of 'columns' columns.
  595. // classically this would be 24 x 80.
  596. // screen coordinates
  597. // 0,0 ... 0,79
  598. // 1,0 ... 1,79
  599. // . ... .
  600. // . ... .
  601. // 22,0 ... 22,79
  602. // 23,0 ... 23,79 <- status line
  603. //----- Move the cursor to row x col (count from 0, not 1) -------
  604. static void place_cursor(int row, int col)
  605. {
  606. char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
  607. if (row < 0) row = 0;
  608. if (row >= rows) row = rows - 1;
  609. if (col < 0) col = 0;
  610. if (col >= columns) col = columns - 1;
  611. sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
  612. write1(cm1);
  613. }
  614. //----- Erase from cursor to end of line -----------------------
  615. static void clear_to_eol(void)
  616. {
  617. write1(ESC_CLEAR2EOL);
  618. }
  619. static void go_bottom_and_clear_to_eol(void)
  620. {
  621. place_cursor(rows - 1, 0);
  622. clear_to_eol();
  623. }
  624. //----- Start standout mode ------------------------------------
  625. static void standout_start(void)
  626. {
  627. write1(ESC_BOLD_TEXT);
  628. }
  629. //----- End standout mode --------------------------------------
  630. static void standout_end(void)
  631. {
  632. write1(ESC_NORM_TEXT);
  633. }
  634. //----- Text Movement Routines ---------------------------------
  635. static char *begin_line(char *p) // return pointer to first char cur line
  636. {
  637. if (p > text) {
  638. p = memrchr(text, '\n', p - text);
  639. if (!p)
  640. return text;
  641. return p + 1;
  642. }
  643. return p;
  644. }
  645. static char *end_line(char *p) // return pointer to NL of cur line
  646. {
  647. if (p < end - 1) {
  648. p = memchr(p, '\n', end - p - 1);
  649. if (!p)
  650. return end - 1;
  651. }
  652. return p;
  653. }
  654. static char *dollar_line(char *p) // return pointer to just before NL line
  655. {
  656. p = end_line(p);
  657. // Try to stay off of the Newline
  658. if (*p == '\n' && (p - begin_line(p)) > 0)
  659. p--;
  660. return p;
  661. }
  662. static char *prev_line(char *p) // return pointer first char prev line
  663. {
  664. p = begin_line(p); // goto beginning of cur line
  665. if (p > text && p[-1] == '\n')
  666. p--; // step to prev line
  667. p = begin_line(p); // goto beginning of prev line
  668. return p;
  669. }
  670. static char *next_line(char *p) // return pointer first char next line
  671. {
  672. p = end_line(p);
  673. if (p < end - 1 && *p == '\n')
  674. p++; // step to next line
  675. return p;
  676. }
  677. //----- Text Information Routines ------------------------------
  678. static char *end_screen(void)
  679. {
  680. char *q;
  681. int cnt;
  682. // find new bottom line
  683. q = screenbegin;
  684. for (cnt = 0; cnt < rows - 2; cnt++)
  685. q = next_line(q);
  686. q = end_line(q);
  687. return q;
  688. }
  689. // count line from start to stop
  690. static int count_lines(char *start, char *stop)
  691. {
  692. char *q;
  693. int cnt;
  694. if (stop < start) { // start and stop are backwards- reverse them
  695. q = start;
  696. start = stop;
  697. stop = q;
  698. }
  699. cnt = 0;
  700. stop = end_line(stop);
  701. while (start <= stop && start <= end - 1) {
  702. start = end_line(start);
  703. if (*start == '\n')
  704. cnt++;
  705. start++;
  706. }
  707. return cnt;
  708. }
  709. static char *find_line(int li) // find beginning of line #li
  710. {
  711. char *q;
  712. for (q = text; li > 1; li--) {
  713. q = next_line(q);
  714. }
  715. return q;
  716. }
  717. static int next_tabstop(int col)
  718. {
  719. return col + ((tabstop - 1) - (col % tabstop));
  720. }
  721. static int prev_tabstop(int col)
  722. {
  723. return col - ((col % tabstop) ?: tabstop);
  724. }
  725. static int next_column(char c, int co)
  726. {
  727. if (c == '\t')
  728. co = next_tabstop(co);
  729. else if ((unsigned char)c < ' ' || c == 0x7f)
  730. co++; // display as ^X, use 2 columns
  731. return co + 1;
  732. }
  733. static int get_column(char *p)
  734. {
  735. const char *r;
  736. int co = 0;
  737. for (r = begin_line(p); r < p; r++)
  738. co = next_column(*r, co);
  739. return co;
  740. }
  741. //----- Erase the Screen[] memory ------------------------------
  742. static void screen_erase(void)
  743. {
  744. memset(screen, ' ', screensize); // clear new screen
  745. }
  746. static void new_screen(int ro, int co)
  747. {
  748. char *s;
  749. free(screen);
  750. screensize = ro * co + 8;
  751. s = screen = xmalloc(screensize);
  752. // initialize the new screen. assume this will be a empty file.
  753. screen_erase();
  754. // non-existent text[] lines start with a tilde (~).
  755. //screen[(1 * co) + 0] = '~';
  756. //screen[(2 * co) + 0] = '~';
  757. //..
  758. //screen[((ro-2) * co) + 0] = '~';
  759. ro -= 2;
  760. while (--ro >= 0) {
  761. s += co;
  762. *s = '~';
  763. }
  764. }
  765. //----- Synchronize the cursor to Dot --------------------------
  766. static void sync_cursor(char *d, int *row, int *col)
  767. {
  768. char *beg_cur; // begin and end of "d" line
  769. char *tp;
  770. int cnt, ro, co;
  771. beg_cur = begin_line(d); // first char of cur line
  772. if (beg_cur < screenbegin) {
  773. // "d" is before top line on screen
  774. // how many lines do we have to move
  775. cnt = count_lines(beg_cur, screenbegin);
  776. sc1:
  777. screenbegin = beg_cur;
  778. if (cnt > (rows - 1) / 2) {
  779. // we moved too many lines. put "dot" in middle of screen
  780. for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
  781. screenbegin = prev_line(screenbegin);
  782. }
  783. }
  784. } else {
  785. char *end_scr; // begin and end of screen
  786. end_scr = end_screen(); // last char of screen
  787. if (beg_cur > end_scr) {
  788. // "d" is after bottom line on screen
  789. // how many lines do we have to move
  790. cnt = count_lines(end_scr, beg_cur);
  791. if (cnt > (rows - 1) / 2)
  792. goto sc1; // too many lines
  793. for (ro = 0; ro < cnt - 1; ro++) {
  794. // move screen begin the same amount
  795. screenbegin = next_line(screenbegin);
  796. // now, move the end of screen
  797. end_scr = next_line(end_scr);
  798. end_scr = end_line(end_scr);
  799. }
  800. }
  801. }
  802. // "d" is on screen- find out which row
  803. tp = screenbegin;
  804. for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
  805. if (tp == beg_cur)
  806. break;
  807. tp = next_line(tp);
  808. }
  809. // find out what col "d" is on
  810. co = 0;
  811. do { // drive "co" to correct column
  812. if (*tp == '\n') //vda || *tp == '\0')
  813. break;
  814. co = next_column(*tp, co) - 1;
  815. // inserting text before a tab, don't include its position
  816. if (cmd_mode && tp == d - 1 && *d == '\t') {
  817. co++;
  818. break;
  819. }
  820. } while (tp++ < d && ++co);
  821. // "co" is the column where "dot" is.
  822. // The screen has "columns" columns.
  823. // The currently displayed columns are 0+offset -- columns+ofset
  824. // |-------------------------------------------------------------|
  825. // ^ ^ ^
  826. // offset | |------- columns ----------------|
  827. //
  828. // If "co" is already in this range then we do not have to adjust offset
  829. // but, we do have to subtract the "offset" bias from "co".
  830. // If "co" is outside this range then we have to change "offset".
  831. // If the first char of a line is a tab the cursor will try to stay
  832. // in column 7, but we have to set offset to 0.
  833. if (co < 0 + offset) {
  834. offset = co;
  835. }
  836. if (co >= columns + offset) {
  837. offset = co - columns + 1;
  838. }
  839. // if the first char of the line is a tab, and "dot" is sitting on it
  840. // force offset to 0.
  841. if (d == beg_cur && *d == '\t') {
  842. offset = 0;
  843. }
  844. co -= offset;
  845. *row = ro;
  846. *col = co;
  847. }
  848. //----- Format a text[] line into a buffer ---------------------
  849. static char* format_line(char *src /*, int li*/)
  850. {
  851. unsigned char c;
  852. int co;
  853. int ofs = offset;
  854. char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
  855. c = '~'; // char in col 0 in non-existent lines is '~'
  856. co = 0;
  857. while (co < columns + tabstop) {
  858. // have we gone past the end?
  859. if (src < end) {
  860. c = *src++;
  861. if (c == '\n')
  862. break;
  863. if ((c & 0x80) && !Isprint(c)) {
  864. c = '.';
  865. }
  866. if (c < ' ' || c == 0x7f) {
  867. if (c == '\t') {
  868. c = ' ';
  869. // co % 8 != 7
  870. while ((co % tabstop) != (tabstop - 1)) {
  871. dest[co++] = c;
  872. }
  873. } else {
  874. dest[co++] = '^';
  875. if (c == 0x7f)
  876. c = '?';
  877. else
  878. c += '@'; // Ctrl-X -> 'X'
  879. }
  880. }
  881. }
  882. dest[co++] = c;
  883. // discard scrolled-off-to-the-left portion,
  884. // in tabstop-sized pieces
  885. if (ofs >= tabstop && co >= tabstop) {
  886. memmove(dest, dest + tabstop, co);
  887. co -= tabstop;
  888. ofs -= tabstop;
  889. }
  890. if (src >= end)
  891. break;
  892. }
  893. // check "short line, gigantic offset" case
  894. if (co < ofs)
  895. ofs = co;
  896. // discard last scrolled off part
  897. co -= ofs;
  898. dest += ofs;
  899. // fill the rest with spaces
  900. if (co < columns)
  901. memset(&dest[co], ' ', columns - co);
  902. return dest;
  903. }
  904. //----- Refresh the changed screen lines -----------------------
  905. // Copy the source line from text[] into the buffer and note
  906. // if the current screenline is different from the new buffer.
  907. // If they differ then that line needs redrawing on the terminal.
  908. //
  909. static void refresh(int full_screen)
  910. {
  911. #define old_offset refresh__old_offset
  912. int li, changed;
  913. char *tp, *sp; // pointer into text[] and screen[]
  914. if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
  915. unsigned c = columns, r = rows;
  916. query_screen_dimensions();
  917. #if ENABLE_FEATURE_VI_USE_SIGNALS
  918. full_screen |= (c - columns) | (r - rows);
  919. #else
  920. if (c != columns || r != rows) {
  921. full_screen = TRUE;
  922. // update screen memory since SIGWINCH won't have done it
  923. new_screen(rows, columns);
  924. }
  925. #endif
  926. }
  927. sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
  928. tp = screenbegin; // index into text[] of top line
  929. // compare text[] to screen[] and mark screen[] lines that need updating
  930. for (li = 0; li < rows - 1; li++) {
  931. int cs, ce; // column start & end
  932. char *out_buf;
  933. // format current text line
  934. out_buf = format_line(tp /*, li*/);
  935. // skip to the end of the current text[] line
  936. if (tp < end) {
  937. char *t = memchr(tp, '\n', end - tp);
  938. if (!t) t = end - 1;
  939. tp = t + 1;
  940. }
  941. // see if there are any changes between virtual screen and out_buf
  942. changed = FALSE; // assume no change
  943. cs = 0;
  944. ce = columns - 1;
  945. sp = &screen[li * columns]; // start of screen line
  946. if (full_screen) {
  947. // force re-draw of every single column from 0 - columns-1
  948. goto re0;
  949. }
  950. // compare newly formatted buffer with virtual screen
  951. // look forward for first difference between buf and screen
  952. for (; cs <= ce; cs++) {
  953. if (out_buf[cs] != sp[cs]) {
  954. changed = TRUE; // mark for redraw
  955. break;
  956. }
  957. }
  958. // look backward for last difference between out_buf and screen
  959. for (; ce >= cs; ce--) {
  960. if (out_buf[ce] != sp[ce]) {
  961. changed = TRUE; // mark for redraw
  962. break;
  963. }
  964. }
  965. // now, cs is index of first diff, and ce is index of last diff
  966. // if horz offset has changed, force a redraw
  967. if (offset != old_offset) {
  968. re0:
  969. changed = TRUE;
  970. }
  971. // make a sanity check of columns indexes
  972. if (cs < 0) cs = 0;
  973. if (ce > columns - 1) ce = columns - 1;
  974. if (cs > ce) { cs = 0; ce = columns - 1; }
  975. // is there a change between virtual screen and out_buf
  976. if (changed) {
  977. // copy changed part of buffer to virtual screen
  978. memcpy(sp+cs, out_buf+cs, ce-cs+1);
  979. place_cursor(li, cs);
  980. // write line out to terminal
  981. fwrite(&sp[cs], ce - cs + 1, 1, stdout);
  982. }
  983. }
  984. place_cursor(crow, ccol);
  985. if (!keep_index)
  986. cindex = ccol + offset;
  987. old_offset = offset;
  988. #undef old_offset
  989. }
  990. //----- Force refresh of all Lines -----------------------------
  991. static void redraw(int full_screen)
  992. {
  993. // cursor to top,left; clear to the end of screen
  994. write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
  995. screen_erase(); // erase the internal screen buffer
  996. last_status_cksum = 0; // force status update
  997. refresh(full_screen); // this will redraw the entire display
  998. show_status_line();
  999. }
  1000. //----- Flash the screen --------------------------------------
  1001. static void flash(int h)
  1002. {
  1003. standout_start();
  1004. redraw(TRUE);
  1005. mysleep(h);
  1006. standout_end();
  1007. redraw(TRUE);
  1008. }
  1009. static void indicate_error(void)
  1010. {
  1011. #if ENABLE_FEATURE_VI_CRASHME
  1012. if (crashme > 0)
  1013. return;
  1014. #endif
  1015. cmd_error = TRUE;
  1016. if (!err_method) {
  1017. write1(ESC_BELL);
  1018. } else {
  1019. flash(10);
  1020. }
  1021. }
  1022. //----- IO Routines --------------------------------------------
  1023. static int readit(void) // read (maybe cursor) key from stdin
  1024. {
  1025. int c;
  1026. fflush_all();
  1027. // Wait for input. TIMEOUT = -1 makes read_key wait even
  1028. // on nonblocking stdin.
  1029. // Note: read_key sets errno to 0 on success.
  1030. again:
  1031. c = safe_read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
  1032. if (c == -1) { // EOF/error
  1033. if (errno == EAGAIN) // paranoia
  1034. goto again;
  1035. go_bottom_and_clear_to_eol();
  1036. cookmode(); // terminal to "cooked"
  1037. bb_simple_error_msg_and_die("can't read user input");
  1038. }
  1039. return c;
  1040. }
  1041. #if ENABLE_FEATURE_VI_DOT_CMD
  1042. static int get_one_char(void)
  1043. {
  1044. int c;
  1045. if (!adding2q) {
  1046. // we are not adding to the q.
  1047. // but, we may be reading from a saved q.
  1048. // (checking "ioq" for NULL is wrong, it's not reset to NULL
  1049. // when done - "ioq_start" is reset instead).
  1050. if (ioq_start != NULL) {
  1051. // there is a queue to get chars from.
  1052. // careful with correct sign expansion!
  1053. c = (unsigned char)*ioq++;
  1054. if (c != '\0')
  1055. return c;
  1056. // the end of the q
  1057. free(ioq_start);
  1058. ioq_start = NULL;
  1059. // read from STDIN:
  1060. }
  1061. return readit();
  1062. }
  1063. // we are adding STDIN chars to q.
  1064. c = readit();
  1065. if (lmc_len >= ARRAY_SIZE(last_modifying_cmd) - 2) {
  1066. // last_modifying_cmd[] is too small, can't remember the cmd
  1067. // - drop it
  1068. adding2q = 0;
  1069. lmc_len = 0;
  1070. } else {
  1071. last_modifying_cmd[lmc_len++] = c;
  1072. }
  1073. return c;
  1074. }
  1075. #else
  1076. # define get_one_char() readit()
  1077. #endif
  1078. // Get type of thing to operate on and adjust count
  1079. static int get_motion_char(void)
  1080. {
  1081. int c, cnt;
  1082. c = get_one_char();
  1083. if (isdigit(c)) {
  1084. if (c != '0') {
  1085. // get any non-zero motion count
  1086. for (cnt = 0; isdigit(c); c = get_one_char())
  1087. cnt = cnt * 10 + (c - '0');
  1088. cmdcnt = (cmdcnt ?: 1) * cnt;
  1089. } else {
  1090. // ensure standalone '0' works
  1091. cmdcnt = 0;
  1092. }
  1093. }
  1094. return c;
  1095. }
  1096. // Get input line (uses "status line" area)
  1097. static char *get_input_line(const char *prompt)
  1098. {
  1099. // char [MAX_INPUT_LEN]
  1100. #define buf get_input_line__buf
  1101. int c;
  1102. int i;
  1103. strcpy(buf, prompt);
  1104. last_status_cksum = 0; // force status update
  1105. go_bottom_and_clear_to_eol();
  1106. write1(buf); // write out the :, /, or ? prompt
  1107. i = strlen(buf);
  1108. while (i < MAX_INPUT_LEN - 1) {
  1109. c = get_one_char();
  1110. if (c == '\n' || c == '\r' || c == 27)
  1111. break; // this is end of input
  1112. if (isbackspace(c)) {
  1113. // user wants to erase prev char
  1114. buf[--i] = '\0';
  1115. go_bottom_and_clear_to_eol();
  1116. if (i <= 0) // user backs up before b-o-l, exit
  1117. break;
  1118. write1(buf);
  1119. } else if (c > 0 && c < 256) { // exclude Unicode
  1120. // (TODO: need to handle Unicode)
  1121. buf[i] = c;
  1122. buf[++i] = '\0';
  1123. bb_putchar(c);
  1124. }
  1125. }
  1126. refresh(FALSE);
  1127. return buf;
  1128. #undef buf
  1129. }
  1130. static void Hit_Return(void)
  1131. {
  1132. int c;
  1133. standout_start();
  1134. write1("[Hit return to continue]");
  1135. standout_end();
  1136. while ((c = get_one_char()) != '\n' && c != '\r')
  1137. continue;
  1138. redraw(TRUE); // force redraw all
  1139. }
  1140. //----- Draw the status line at bottom of the screen -------------
  1141. // show file status on status line
  1142. static int format_edit_status(void)
  1143. {
  1144. static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
  1145. #define tot format_edit_status__tot
  1146. int cur, percent, ret, trunc_at;
  1147. // modified_count is now a counter rather than a flag. this
  1148. // helps reduce the amount of line counting we need to do.
  1149. // (this will cause a mis-reporting of modified status
  1150. // once every MAXINT editing operations.)
  1151. // it would be nice to do a similar optimization here -- if
  1152. // we haven't done a motion that could have changed which line
  1153. // we're on, then we shouldn't have to do this count_lines()
  1154. cur = count_lines(text, dot);
  1155. // count_lines() is expensive.
  1156. // Call it only if something was changed since last time
  1157. // we were here:
  1158. if (modified_count != last_modified_count) {
  1159. tot = cur + count_lines(dot, end - 1) - 1;
  1160. last_modified_count = modified_count;
  1161. }
  1162. // current line percent
  1163. // ------------- ~~ ----------
  1164. // total lines 100
  1165. if (tot > 0) {
  1166. percent = (100 * cur) / tot;
  1167. } else {
  1168. cur = tot = 0;
  1169. percent = 100;
  1170. }
  1171. trunc_at = columns < STATUS_BUFFER_LEN-1 ?
  1172. columns : STATUS_BUFFER_LEN-1;
  1173. ret = snprintf(status_buffer, trunc_at+1,
  1174. #if ENABLE_FEATURE_VI_READONLY
  1175. "%c %s%s%s %d/%d %d%%",
  1176. #else
  1177. "%c %s%s %d/%d %d%%",
  1178. #endif
  1179. cmd_mode_indicator[cmd_mode & 3],
  1180. (current_filename != NULL ? current_filename : "No file"),
  1181. #if ENABLE_FEATURE_VI_READONLY
  1182. (readonly_mode ? " [Readonly]" : ""),
  1183. #endif
  1184. (modified_count ? " [Modified]" : ""),
  1185. cur, tot, percent);
  1186. if (ret >= 0 && ret < trunc_at)
  1187. return ret; // it all fit
  1188. return trunc_at; // had to truncate
  1189. #undef tot
  1190. }
  1191. static int bufsum(char *buf, int count)
  1192. {
  1193. int sum = 0;
  1194. char *e = buf + count;
  1195. while (buf < e)
  1196. sum += (unsigned char) *buf++;
  1197. return sum;
  1198. }
  1199. static void show_status_line(void)
  1200. {
  1201. int cnt = 0, cksum = 0;
  1202. // either we already have an error or status message, or we
  1203. // create one.
  1204. if (!have_status_msg) {
  1205. cnt = format_edit_status();
  1206. cksum = bufsum(status_buffer, cnt);
  1207. }
  1208. if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
  1209. last_status_cksum = cksum; // remember if we have seen this line
  1210. go_bottom_and_clear_to_eol();
  1211. write1(status_buffer);
  1212. if (have_status_msg) {
  1213. if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
  1214. (columns - 1) ) {
  1215. have_status_msg = 0;
  1216. Hit_Return();
  1217. }
  1218. have_status_msg = 0;
  1219. }
  1220. place_cursor(crow, ccol); // put cursor back in correct place
  1221. }
  1222. fflush_all();
  1223. }
  1224. //----- format the status buffer, the bottom line of screen ------
  1225. static void status_line(const char *format, ...)
  1226. {
  1227. va_list args;
  1228. va_start(args, format);
  1229. vsnprintf(status_buffer, STATUS_BUFFER_LEN, format, args);
  1230. va_end(args);
  1231. have_status_msg = 1;
  1232. }
  1233. static void status_line_bold(const char *format, ...)
  1234. {
  1235. va_list args;
  1236. va_start(args, format);
  1237. strcpy(status_buffer, ESC_BOLD_TEXT);
  1238. vsnprintf(status_buffer + (sizeof(ESC_BOLD_TEXT)-1),
  1239. STATUS_BUFFER_LEN - sizeof(ESC_BOLD_TEXT) - sizeof(ESC_NORM_TEXT),
  1240. format, args
  1241. );
  1242. strcat(status_buffer, ESC_NORM_TEXT);
  1243. va_end(args);
  1244. have_status_msg = 1 + (sizeof(ESC_BOLD_TEXT)-1) + (sizeof(ESC_NORM_TEXT)-1);
  1245. }
  1246. static void status_line_bold_errno(const char *fn)
  1247. {
  1248. status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
  1249. }
  1250. // copy s to buf, convert unprintable
  1251. static void print_literal(char *buf, const char *s)
  1252. {
  1253. char *d;
  1254. unsigned char c;
  1255. if (!s[0])
  1256. s = "(NULL)";
  1257. d = buf;
  1258. for (; *s; s++) {
  1259. c = *s;
  1260. if ((c & 0x80) && !Isprint(c))
  1261. c = '?';
  1262. if (c < ' ' || c == 0x7f) {
  1263. *d++ = '^';
  1264. c |= '@'; // 0x40
  1265. if (c == 0x7f)
  1266. c = '?';
  1267. }
  1268. *d++ = c;
  1269. *d = '\0';
  1270. if (d - buf > MAX_INPUT_LEN - 10) // paranoia
  1271. break;
  1272. }
  1273. }
  1274. static void not_implemented(const char *s)
  1275. {
  1276. char buf[MAX_INPUT_LEN];
  1277. print_literal(buf, s);
  1278. status_line_bold("'%s' is not implemented", buf);
  1279. }
  1280. //----- Block insert/delete, undo ops --------------------------
  1281. #if ENABLE_FEATURE_VI_YANKMARK
  1282. // copy text into a register
  1283. static char *text_yank(char *p, char *q, int dest, int buftype)
  1284. {
  1285. char *oldreg = reg[dest];
  1286. int cnt = q - p;
  1287. if (cnt < 0) { // they are backwards- reverse them
  1288. p = q;
  1289. cnt = -cnt;
  1290. }
  1291. // Don't free register yet. This prevents the memory allocator
  1292. // from reusing the free block so we can detect if it's changed.
  1293. reg[dest] = xstrndup(p, cnt + 1);
  1294. regtype[dest] = buftype;
  1295. free(oldreg);
  1296. return p;
  1297. }
  1298. static char what_reg(void)
  1299. {
  1300. char c;
  1301. c = 'D'; // default to D-reg
  1302. if (YDreg <= 25)
  1303. c = 'a' + (char) YDreg;
  1304. if (YDreg == 26)
  1305. c = 'D';
  1306. if (YDreg == 27)
  1307. c = 'U';
  1308. return c;
  1309. }
  1310. static void check_context(char cmd)
  1311. {
  1312. // Certain movement commands update the context.
  1313. if (strchr(":%{}'GHLMz/?Nn", cmd) != NULL) {
  1314. mark[27] = mark[26]; // move cur to prev
  1315. mark[26] = dot; // move local to cur
  1316. }
  1317. }
  1318. static char *swap_context(char *p) // goto new context for '' command make this the current context
  1319. {
  1320. char *tmp;
  1321. // the current context is in mark[26]
  1322. // the previous context is in mark[27]
  1323. // only swap context if other context is valid
  1324. if (text <= mark[27] && mark[27] <= end - 1) {
  1325. tmp = mark[27];
  1326. mark[27] = p;
  1327. mark[26] = p = tmp;
  1328. }
  1329. return p;
  1330. }
  1331. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  1332. static void yank_status(const char *op, const char *p, int cnt)
  1333. {
  1334. int lines, chars;
  1335. lines = chars = 0;
  1336. while (*p) {
  1337. ++chars;
  1338. if (*p++ == '\n')
  1339. ++lines;
  1340. }
  1341. status_line("%s %d lines (%d chars) from [%c]",
  1342. op, lines * cnt, chars * cnt, what_reg());
  1343. }
  1344. # endif
  1345. #endif /* FEATURE_VI_YANKMARK */
  1346. #if ENABLE_FEATURE_VI_UNDO
  1347. static void undo_push(char *, unsigned, int);
  1348. #endif
  1349. // open a hole in text[]
  1350. // might reallocate text[]! use p += text_hole_make(p, ...),
  1351. // and be careful to not use pointers into potentially freed text[]!
  1352. static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
  1353. {
  1354. uintptr_t bias = 0;
  1355. if (size <= 0)
  1356. return bias;
  1357. end += size; // adjust the new END
  1358. if (end >= (text + text_size)) {
  1359. char *new_text;
  1360. text_size += end - (text + text_size) + 10240;
  1361. new_text = xrealloc(text, text_size);
  1362. bias = (new_text - text);
  1363. screenbegin += bias;
  1364. dot += bias;
  1365. end += bias;
  1366. p += bias;
  1367. #if ENABLE_FEATURE_VI_YANKMARK
  1368. {
  1369. int i;
  1370. for (i = 0; i < ARRAY_SIZE(mark); i++)
  1371. if (mark[i])
  1372. mark[i] += bias;
  1373. }
  1374. #endif
  1375. text = new_text;
  1376. }
  1377. memmove(p + size, p, end - size - p);
  1378. memset(p, ' ', size); // clear new hole
  1379. return bias;
  1380. }
  1381. // close a hole in text[] - delete "p" through "q", inclusive
  1382. // "undo" value indicates if this operation should be undo-able
  1383. #if !ENABLE_FEATURE_VI_UNDO
  1384. #define text_hole_delete(a,b,c) text_hole_delete(a,b)
  1385. #endif
  1386. static char *text_hole_delete(char *p, char *q, int undo)
  1387. {
  1388. char *src, *dest;
  1389. int cnt, hole_size;
  1390. // move forwards, from beginning
  1391. // assume p <= q
  1392. src = q + 1;
  1393. dest = p;
  1394. if (q < p) { // they are backward- swap them
  1395. src = p + 1;
  1396. dest = q;
  1397. }
  1398. hole_size = q - p + 1;
  1399. cnt = end - src;
  1400. #if ENABLE_FEATURE_VI_UNDO
  1401. switch (undo) {
  1402. case NO_UNDO:
  1403. break;
  1404. case ALLOW_UNDO:
  1405. undo_push(p, hole_size, UNDO_DEL);
  1406. break;
  1407. case ALLOW_UNDO_CHAIN:
  1408. undo_push(p, hole_size, UNDO_DEL_CHAIN);
  1409. break;
  1410. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  1411. case ALLOW_UNDO_QUEUED:
  1412. undo_push(p, hole_size, UNDO_DEL_QUEUED);
  1413. break;
  1414. # endif
  1415. }
  1416. modified_count--;
  1417. #endif
  1418. if (src < text || src > end)
  1419. goto thd0;
  1420. if (dest < text || dest >= end)
  1421. goto thd0;
  1422. modified_count++;
  1423. if (src >= end)
  1424. goto thd_atend; // just delete the end of the buffer
  1425. memmove(dest, src, cnt);
  1426. thd_atend:
  1427. end = end - hole_size; // adjust the new END
  1428. if (dest >= end)
  1429. dest = end - 1; // make sure dest in below end-1
  1430. if (end <= text)
  1431. dest = end = text; // keep pointers valid
  1432. thd0:
  1433. return dest;
  1434. }
  1435. #if ENABLE_FEATURE_VI_UNDO
  1436. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  1437. // Flush any queued objects to the undo stack
  1438. static void undo_queue_commit(void)
  1439. {
  1440. // Pushes the queue object onto the undo stack
  1441. if (undo_q > 0) {
  1442. // Deleted character undo events grow from the end
  1443. undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
  1444. undo_q,
  1445. (undo_queue_state | UNDO_USE_SPOS)
  1446. );
  1447. undo_queue_state = UNDO_EMPTY;
  1448. undo_q = 0;
  1449. }
  1450. }
  1451. # else
  1452. # define undo_queue_commit() ((void)0)
  1453. # endif
  1454. static void flush_undo_data(void)
  1455. {
  1456. struct undo_object *undo_entry;
  1457. while (undo_stack_tail) {
  1458. undo_entry = undo_stack_tail;
  1459. undo_stack_tail = undo_entry->prev;
  1460. free(undo_entry);
  1461. }
  1462. }
  1463. // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
  1464. // Add to the undo stack
  1465. static void undo_push(char *src, unsigned length, int u_type)
  1466. {
  1467. struct undo_object *undo_entry;
  1468. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  1469. int use_spos = u_type & UNDO_USE_SPOS;
  1470. # endif
  1471. // "u_type" values
  1472. // UNDO_INS: insertion, undo will remove from buffer
  1473. // UNDO_DEL: deleted text, undo will restore to buffer
  1474. // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
  1475. // The CHAIN operations are for handling multiple operations that the user
  1476. // performs with a single action, i.e. REPLACE mode or find-and-replace commands
  1477. // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
  1478. // for the INS/DEL operation.
  1479. // UNDO_{INS,DEL} ORed with UNDO_USE_SPOS: commit the undo queue
  1480. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  1481. // This undo queuing functionality groups multiple character typing or backspaces
  1482. // into a single large undo object. This greatly reduces calls to malloc() for
  1483. // single-character operations while typing and has the side benefit of letting
  1484. // an undo operation remove chunks of text rather than a single character.
  1485. switch (u_type) {
  1486. case UNDO_EMPTY: // Just in case this ever happens...
  1487. return;
  1488. case UNDO_DEL_QUEUED:
  1489. if (length != 1)
  1490. return; // Only queue single characters
  1491. switch (undo_queue_state) {
  1492. case UNDO_EMPTY:
  1493. undo_queue_state = UNDO_DEL;
  1494. case UNDO_DEL:
  1495. undo_queue_spos = src;
  1496. undo_q++;
  1497. undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
  1498. // If queue is full, dump it into an object
  1499. if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
  1500. undo_queue_commit();
  1501. return;
  1502. case UNDO_INS:
  1503. // Switch from storing inserted text to deleted text
  1504. undo_queue_commit();
  1505. undo_push(src, length, UNDO_DEL_QUEUED);
  1506. return;
  1507. }
  1508. break;
  1509. case UNDO_INS_QUEUED:
  1510. if (length < 1)
  1511. return;
  1512. switch (undo_queue_state) {
  1513. case UNDO_EMPTY:
  1514. undo_queue_state = UNDO_INS;
  1515. undo_queue_spos = src;
  1516. case UNDO_INS:
  1517. while (length--) {
  1518. undo_q++; // Don't need to save any data for insertions
  1519. if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
  1520. undo_queue_commit();
  1521. }
  1522. return;
  1523. case UNDO_DEL:
  1524. // Switch from storing deleted text to inserted text
  1525. undo_queue_commit();
  1526. undo_push(src, length, UNDO_INS_QUEUED);
  1527. return;
  1528. }
  1529. break;
  1530. }
  1531. u_type &= ~UNDO_USE_SPOS;
  1532. # endif
  1533. // Allocate a new undo object
  1534. if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
  1535. // For UNDO_DEL objects, save deleted text
  1536. if ((text + length) == end)
  1537. length--;
  1538. // If this deletion empties text[], strip the newline. When the buffer becomes
  1539. // zero-length, a newline is added back, which requires this to compensate.
  1540. undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
  1541. memcpy(undo_entry->undo_text, src, length);
  1542. } else {
  1543. undo_entry = xzalloc(sizeof(*undo_entry));
  1544. }
  1545. undo_entry->length = length;
  1546. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  1547. if (use_spos) {
  1548. undo_entry->start = undo_queue_spos - text; // use start position from queue
  1549. } else {
  1550. undo_entry->start = src - text; // use offset from start of text buffer
  1551. }
  1552. # else
  1553. undo_entry->start = src - text;
  1554. # endif
  1555. undo_entry->u_type = u_type;
  1556. // Push it on undo stack
  1557. undo_entry->prev = undo_stack_tail;
  1558. undo_stack_tail = undo_entry;
  1559. modified_count++;
  1560. }
  1561. static void undo_push_insert(char *p, int len, int undo)
  1562. {
  1563. switch (undo) {
  1564. case ALLOW_UNDO:
  1565. undo_push(p, len, UNDO_INS);
  1566. break;
  1567. case ALLOW_UNDO_CHAIN:
  1568. undo_push(p, len, UNDO_INS_CHAIN);
  1569. break;
  1570. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  1571. case ALLOW_UNDO_QUEUED:
  1572. undo_push(p, len, UNDO_INS_QUEUED);
  1573. break;
  1574. # endif
  1575. }
  1576. }
  1577. // Undo the last operation
  1578. static void undo_pop(void)
  1579. {
  1580. int repeat;
  1581. char *u_start, *u_end;
  1582. struct undo_object *undo_entry;
  1583. // Commit pending undo queue before popping (should be unnecessary)
  1584. undo_queue_commit();
  1585. undo_entry = undo_stack_tail;
  1586. // Check for an empty undo stack
  1587. if (!undo_entry) {
  1588. status_line("Already at oldest change");
  1589. return;
  1590. }
  1591. switch (undo_entry->u_type) {
  1592. case UNDO_DEL:
  1593. case UNDO_DEL_CHAIN:
  1594. // make hole and put in text that was deleted; deallocate text
  1595. u_start = text + undo_entry->start;
  1596. text_hole_make(u_start, undo_entry->length);
  1597. memcpy(u_start, undo_entry->undo_text, undo_entry->length);
  1598. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  1599. status_line("Undo [%d] %s %d chars at position %d",
  1600. modified_count, "restored",
  1601. undo_entry->length, undo_entry->start
  1602. );
  1603. # endif
  1604. break;
  1605. case UNDO_INS:
  1606. case UNDO_INS_CHAIN:
  1607. // delete what was inserted
  1608. u_start = undo_entry->start + text;
  1609. u_end = u_start - 1 + undo_entry->length;
  1610. text_hole_delete(u_start, u_end, NO_UNDO);
  1611. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  1612. status_line("Undo [%d] %s %d chars at position %d",
  1613. modified_count, "deleted",
  1614. undo_entry->length, undo_entry->start
  1615. );
  1616. # endif
  1617. break;
  1618. }
  1619. repeat = 0;
  1620. switch (undo_entry->u_type) {
  1621. // If this is the end of a chain, lower modification count and refresh display
  1622. case UNDO_DEL:
  1623. case UNDO_INS:
  1624. dot = (text + undo_entry->start);
  1625. refresh(FALSE);
  1626. break;
  1627. case UNDO_DEL_CHAIN:
  1628. case UNDO_INS_CHAIN:
  1629. repeat = 1;
  1630. break;
  1631. }
  1632. // Deallocate the undo object we just processed
  1633. undo_stack_tail = undo_entry->prev;
  1634. free(undo_entry);
  1635. modified_count--;
  1636. // For chained operations, continue popping all the way down the chain.
  1637. if (repeat) {
  1638. undo_pop(); // Follow the undo chain if one exists
  1639. }
  1640. }
  1641. #else
  1642. # define flush_undo_data() ((void)0)
  1643. # define undo_queue_commit() ((void)0)
  1644. #endif /* ENABLE_FEATURE_VI_UNDO */
  1645. //----- Dot Movement Routines ----------------------------------
  1646. static void dot_left(void)
  1647. {
  1648. undo_queue_commit();
  1649. if (dot > text && dot[-1] != '\n')
  1650. dot--;
  1651. }
  1652. static void dot_right(void)
  1653. {
  1654. undo_queue_commit();
  1655. if (dot < end - 1 && *dot != '\n')
  1656. dot++;
  1657. }
  1658. static void dot_begin(void)
  1659. {
  1660. undo_queue_commit();
  1661. dot = begin_line(dot); // return pointer to first char cur line
  1662. }
  1663. static void dot_end(void)
  1664. {
  1665. undo_queue_commit();
  1666. dot = end_line(dot); // return pointer to last char cur line
  1667. }
  1668. static char *move_to_col(char *p, int l)
  1669. {
  1670. int co;
  1671. p = begin_line(p);
  1672. co = 0;
  1673. do {
  1674. if (*p == '\n') //vda || *p == '\0')
  1675. break;
  1676. co = next_column(*p, co);
  1677. } while (co <= l && p++ < end);
  1678. return p;
  1679. }
  1680. static void dot_next(void)
  1681. {
  1682. undo_queue_commit();
  1683. dot = next_line(dot);
  1684. }
  1685. static void dot_prev(void)
  1686. {
  1687. undo_queue_commit();
  1688. dot = prev_line(dot);
  1689. }
  1690. static void dot_skip_over_ws(void)
  1691. {
  1692. // skip WS
  1693. while (isspace(*dot) && *dot != '\n' && dot < end - 1)
  1694. dot++;
  1695. }
  1696. static void dot_to_char(int cmd)
  1697. {
  1698. char *q = dot;
  1699. int dir = islower(cmd) ? FORWARD : BACK;
  1700. if (last_search_char == 0)
  1701. return;
  1702. do {
  1703. do {
  1704. q += dir;
  1705. if ((dir == FORWARD ? q > end - 1 : q < text) || *q == '\n') {
  1706. indicate_error();
  1707. return;
  1708. }
  1709. } while (*q != last_search_char);
  1710. } while (--cmdcnt > 0);
  1711. dot = q;
  1712. // place cursor before/after char as required
  1713. if (cmd == 't')
  1714. dot_left();
  1715. else if (cmd == 'T')
  1716. dot_right();
  1717. }
  1718. static void dot_scroll(int cnt, int dir)
  1719. {
  1720. char *q;
  1721. undo_queue_commit();
  1722. for (; cnt > 0; cnt--) {
  1723. if (dir < 0) {
  1724. // scroll Backwards
  1725. // ctrl-Y scroll up one line
  1726. screenbegin = prev_line(screenbegin);
  1727. } else {
  1728. // scroll Forwards
  1729. // ctrl-E scroll down one line
  1730. screenbegin = next_line(screenbegin);
  1731. }
  1732. }
  1733. // make sure "dot" stays on the screen so we dont scroll off
  1734. if (dot < screenbegin)
  1735. dot = screenbegin;
  1736. q = end_screen(); // find new bottom line
  1737. if (dot > q)
  1738. dot = begin_line(q); // is dot is below bottom line?
  1739. dot_skip_over_ws();
  1740. }
  1741. static char *bound_dot(char *p) // make sure text[0] <= P < "end"
  1742. {
  1743. if (p >= end && end > text) {
  1744. p = end - 1;
  1745. indicate_error();
  1746. }
  1747. if (p < text) {
  1748. p = text;
  1749. indicate_error();
  1750. }
  1751. return p;
  1752. }
  1753. #if ENABLE_FEATURE_VI_DOT_CMD
  1754. static void start_new_cmd_q(char c)
  1755. {
  1756. // get buffer for new cmd
  1757. dotcnt = cmdcnt ?: 1;
  1758. last_modifying_cmd[0] = c;
  1759. lmc_len = 1;
  1760. adding2q = 1;
  1761. }
  1762. static void end_cmd_q(void)
  1763. {
  1764. # if ENABLE_FEATURE_VI_YANKMARK
  1765. YDreg = 26; // go back to default Yank/Delete reg
  1766. # endif
  1767. adding2q = 0;
  1768. }
  1769. #else
  1770. # define end_cmd_q() ((void)0)
  1771. #endif /* FEATURE_VI_DOT_CMD */
  1772. // copy text into register, then delete text.
  1773. //
  1774. #if !ENABLE_FEATURE_VI_UNDO
  1775. #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
  1776. #endif
  1777. static char *yank_delete(char *start, char *stop, int buftype, int yf, int undo)
  1778. {
  1779. char *p;
  1780. // make sure start <= stop
  1781. if (start > stop) {
  1782. // they are backwards, reverse them
  1783. p = start;
  1784. start = stop;
  1785. stop = p;
  1786. }
  1787. if (buftype == PARTIAL && *start == '\n')
  1788. return start;
  1789. p = start;
  1790. #if ENABLE_FEATURE_VI_YANKMARK
  1791. text_yank(start, stop, YDreg, buftype);
  1792. #endif
  1793. if (yf == YANKDEL) {
  1794. p = text_hole_delete(start, stop, undo);
  1795. } // delete lines
  1796. return p;
  1797. }
  1798. // might reallocate text[]!
  1799. static int file_insert(const char *fn, char *p, int initial)
  1800. {
  1801. int cnt = -1;
  1802. int fd, size;
  1803. struct stat statbuf;
  1804. if (p < text)
  1805. p = text;
  1806. if (p > end)
  1807. p = end;
  1808. fd = open(fn, O_RDONLY);
  1809. if (fd < 0) {
  1810. if (!initial)
  1811. status_line_bold_errno(fn);
  1812. return cnt;
  1813. }
  1814. // Validate file
  1815. if (fstat(fd, &statbuf) < 0) {
  1816. status_line_bold_errno(fn);
  1817. goto fi;
  1818. }
  1819. if (!S_ISREG(statbuf.st_mode)) {
  1820. status_line_bold("'%s' is not a regular file", fn);
  1821. goto fi;
  1822. }
  1823. size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
  1824. p += text_hole_make(p, size);
  1825. cnt = full_read(fd, p, size);
  1826. if (cnt < 0) {
  1827. status_line_bold_errno(fn);
  1828. p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
  1829. } else if (cnt < size) {
  1830. // There was a partial read, shrink unused space
  1831. p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
  1832. status_line_bold("can't read '%s'", fn);
  1833. }
  1834. # if ENABLE_FEATURE_VI_UNDO
  1835. else {
  1836. undo_push_insert(p, size, ALLOW_UNDO);
  1837. }
  1838. # endif
  1839. fi:
  1840. close(fd);
  1841. #if ENABLE_FEATURE_VI_READONLY
  1842. if (initial
  1843. && ((access(fn, W_OK) < 0) ||
  1844. // root will always have access()
  1845. // so we check fileperms too
  1846. !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
  1847. )
  1848. ) {
  1849. SET_READONLY_FILE(readonly_mode);
  1850. }
  1851. #endif
  1852. return cnt;
  1853. }
  1854. // find matching char of pair () [] {}
  1855. // will crash if c is not one of these
  1856. static char *find_pair(char *p, const char c)
  1857. {
  1858. const char *braces = "()[]{}";
  1859. char match;
  1860. int dir, level;
  1861. dir = strchr(braces, c) - braces;
  1862. dir ^= 1;
  1863. match = braces[dir];
  1864. dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
  1865. // look for match, count levels of pairs (( ))
  1866. level = 1;
  1867. for (;;) {
  1868. p += dir;
  1869. if (p < text || p >= end)
  1870. return NULL;
  1871. if (*p == c)
  1872. level++; // increase pair levels
  1873. if (*p == match) {
  1874. level--; // reduce pair level
  1875. if (level == 0)
  1876. return p; // found matching pair
  1877. }
  1878. }
  1879. }
  1880. #if ENABLE_FEATURE_VI_SETOPTS
  1881. // show the matching char of a pair, () [] {}
  1882. static void showmatching(char *p)
  1883. {
  1884. char *q, *save_dot;
  1885. // we found half of a pair
  1886. q = find_pair(p, *p); // get loc of matching char
  1887. if (q == NULL) {
  1888. indicate_error(); // no matching char
  1889. } else {
  1890. // "q" now points to matching pair
  1891. save_dot = dot; // remember where we are
  1892. dot = q; // go to new loc
  1893. refresh(FALSE); // let the user see it
  1894. mysleep(40); // give user some time
  1895. dot = save_dot; // go back to old loc
  1896. refresh(FALSE);
  1897. }
  1898. }
  1899. #endif /* FEATURE_VI_SETOPTS */
  1900. // might reallocate text[]! use p += stupid_insert(p, ...),
  1901. // and be careful to not use pointers into potentially freed text[]!
  1902. static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
  1903. {
  1904. uintptr_t bias;
  1905. bias = text_hole_make(p, 1);
  1906. p += bias;
  1907. *p = c;
  1908. return bias;
  1909. }
  1910. // find number of characters in indent, p must be at beginning of line
  1911. static size_t indent_len(char *p)
  1912. {
  1913. char *r = p;
  1914. while (r < (end - 1) && isblank(*r))
  1915. r++;
  1916. return r - p;
  1917. }
  1918. #if !ENABLE_FEATURE_VI_UNDO
  1919. #define char_insert(a,b,c) char_insert(a,b)
  1920. #endif
  1921. static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
  1922. {
  1923. #if ENABLE_FEATURE_VI_SETOPTS
  1924. # define indentcol char_insert__indentcol
  1925. size_t len;
  1926. int col, ntab, nspc;
  1927. #endif
  1928. char *bol = begin_line(p);
  1929. if (c == 22) { // Is this an ctrl-V?
  1930. p += stupid_insert(p, '^'); // use ^ to indicate literal next
  1931. refresh(FALSE); // show the ^
  1932. c = get_one_char();
  1933. *p = c;
  1934. #if ENABLE_FEATURE_VI_UNDO
  1935. undo_push_insert(p, 1, undo);
  1936. #else
  1937. modified_count++;
  1938. #endif
  1939. p++;
  1940. } else if (c == 27) { // Is this an ESC?
  1941. cmd_mode = 0;
  1942. undo_queue_commit();
  1943. cmdcnt = 0;
  1944. end_cmd_q(); // stop adding to q
  1945. last_status_cksum = 0; // force status update
  1946. if ((dot > text) && (p[-1] != '\n')) {
  1947. p--;
  1948. }
  1949. #if ENABLE_FEATURE_VI_SETOPTS
  1950. if (autoindent) {
  1951. len = indent_len(bol);
  1952. col = get_column(bol + len);
  1953. if (len && col == indentcol && bol[len] == '\n') {
  1954. // remove autoindent from otherwise empty line
  1955. text_hole_delete(bol, bol + len - 1, undo);
  1956. p = bol;
  1957. }
  1958. }
  1959. #endif
  1960. } else if (c == 4) { // ctrl-D reduces indentation
  1961. char *r = bol + indent_len(bol);
  1962. int prev = prev_tabstop(get_column(r));
  1963. while (r > bol && get_column(r) > prev) {
  1964. if (p > bol)
  1965. p--;
  1966. r--;
  1967. r = text_hole_delete(r, r, ALLOW_UNDO_QUEUED);
  1968. }
  1969. #if ENABLE_FEATURE_VI_SETOPTS
  1970. if (autoindent && indentcol && r == end_line(p)) {
  1971. // record changed size of autoindent
  1972. indentcol = get_column(p);
  1973. return p;
  1974. }
  1975. #endif
  1976. #if ENABLE_FEATURE_VI_SETOPTS
  1977. } else if (c == '\t' && expandtab) { // expand tab
  1978. col = get_column(p);
  1979. col = next_tabstop(col) - col + 1;
  1980. while (col--) {
  1981. # if ENABLE_FEATURE_VI_UNDO
  1982. undo_push_insert(p, 1, undo);
  1983. # else
  1984. modified_count++;
  1985. # endif
  1986. p += 1 + stupid_insert(p, ' ');
  1987. }
  1988. #endif
  1989. } else if (isbackspace(c)) {
  1990. if (cmd_mode == 2) {
  1991. // special treatment for backspace in Replace mode
  1992. if (p > rstart) {
  1993. p--;
  1994. #if ENABLE_FEATURE_VI_UNDO
  1995. undo_pop();
  1996. #endif
  1997. }
  1998. } else if (p > text) {
  1999. p--;
  2000. p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
  2001. }
  2002. } else {
  2003. // insert a char into text[]
  2004. if (c == 13)
  2005. c = '\n'; // translate \r to \n
  2006. #if ENABLE_FEATURE_VI_UNDO
  2007. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  2008. if (c == '\n')
  2009. undo_queue_commit();
  2010. # endif
  2011. undo_push_insert(p, 1, undo);
  2012. #else
  2013. modified_count++;
  2014. #endif
  2015. p += 1 + stupid_insert(p, c); // insert the char
  2016. #if ENABLE_FEATURE_VI_SETOPTS
  2017. if (showmatch && strchr(")]}", c) != NULL) {
  2018. showmatching(p - 1);
  2019. }
  2020. if (autoindent && c == '\n') { // auto indent the new line
  2021. if (newindent < 0) {
  2022. // use indent of previous line
  2023. bol = prev_line(p);
  2024. len = indent_len(bol);
  2025. col = get_column(bol + len);
  2026. if (len && col == indentcol) {
  2027. // previous line was empty except for autoindent
  2028. // move the indent to the current line
  2029. memmove(bol + 1, bol, len);
  2030. *bol = '\n';
  2031. return p;
  2032. }
  2033. } else {
  2034. // for 'O'/'cc' commands add indent before newly inserted NL
  2035. if (p != end - 1) // but not for 'cc' at EOF
  2036. p--;
  2037. col = newindent;
  2038. }
  2039. if (col) {
  2040. // only record indent if in insert/replace mode or for
  2041. // the 'o'/'O'/'cc' commands, which are switched to
  2042. // insert mode early.
  2043. indentcol = cmd_mode != 0 ? col : 0;
  2044. if (expandtab) {
  2045. ntab = 0;
  2046. nspc = col;
  2047. } else {
  2048. ntab = col / tabstop;
  2049. nspc = col % tabstop;
  2050. }
  2051. p += text_hole_make(p, ntab + nspc);
  2052. # if ENABLE_FEATURE_VI_UNDO
  2053. undo_push_insert(p, ntab + nspc, undo);
  2054. # endif
  2055. memset(p, '\t', ntab);
  2056. p += ntab;
  2057. memset(p, ' ', nspc);
  2058. return p + nspc;
  2059. }
  2060. }
  2061. #endif
  2062. }
  2063. #if ENABLE_FEATURE_VI_SETOPTS
  2064. indentcol = 0;
  2065. # undef indentcol
  2066. #endif
  2067. return p;
  2068. }
  2069. #if ENABLE_FEATURE_VI_COLON_EXPAND
  2070. static void init_filename(char *fn)
  2071. {
  2072. char *copy = xstrdup(fn);
  2073. if (current_filename == NULL) {
  2074. current_filename = copy;
  2075. } else {
  2076. free(alt_filename);
  2077. alt_filename = copy;
  2078. }
  2079. }
  2080. #else
  2081. # define init_filename(f) ((void)(0))
  2082. #endif
  2083. static void update_filename(char *fn)
  2084. {
  2085. #if ENABLE_FEATURE_VI_COLON_EXPAND
  2086. if (fn == NULL)
  2087. return;
  2088. if (current_filename == NULL || strcmp(fn, current_filename) != 0) {
  2089. free(alt_filename);
  2090. alt_filename = current_filename;
  2091. current_filename = xstrdup(fn);
  2092. }
  2093. #else
  2094. if (fn != current_filename) {
  2095. free(current_filename);
  2096. current_filename = xstrdup(fn);
  2097. }
  2098. #endif
  2099. }
  2100. // read text from file or create an empty buf
  2101. // will also update current_filename
  2102. static int init_text_buffer(char *fn)
  2103. {
  2104. int rc;
  2105. // allocate/reallocate text buffer
  2106. free(text);
  2107. text_size = 10240;
  2108. screenbegin = dot = end = text = xzalloc(text_size);
  2109. update_filename(fn);
  2110. rc = file_insert(fn, text, 1);
  2111. if (rc < 0) {
  2112. // file doesnt exist. Start empty buf with dummy line
  2113. char_insert(text, '\n', NO_UNDO);
  2114. }
  2115. flush_undo_data();
  2116. modified_count = 0;
  2117. last_modified_count = -1;
  2118. #if ENABLE_FEATURE_VI_YANKMARK
  2119. // init the marks
  2120. memset(mark, 0, sizeof(mark));
  2121. #endif
  2122. return rc;
  2123. }
  2124. #if ENABLE_FEATURE_VI_YANKMARK \
  2125. || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
  2126. || ENABLE_FEATURE_VI_CRASHME
  2127. // might reallocate text[]! use p += string_insert(p, ...),
  2128. // and be careful to not use pointers into potentially freed text[]!
  2129. # if !ENABLE_FEATURE_VI_UNDO
  2130. # define string_insert(a,b,c) string_insert(a,b)
  2131. # endif
  2132. static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
  2133. {
  2134. uintptr_t bias;
  2135. int i;
  2136. i = strlen(s);
  2137. #if ENABLE_FEATURE_VI_UNDO
  2138. undo_push_insert(p, i, undo);
  2139. #endif
  2140. bias = text_hole_make(p, i);
  2141. p += bias;
  2142. memcpy(p, s, i);
  2143. return bias;
  2144. }
  2145. #endif
  2146. static int file_write(char *fn, char *first, char *last)
  2147. {
  2148. int fd, cnt, charcnt;
  2149. if (fn == 0) {
  2150. status_line_bold("No current filename");
  2151. return -2;
  2152. }
  2153. // By popular request we do not open file with O_TRUNC,
  2154. // but instead ftruncate() it _after_ successful write.
  2155. // Might reduce amount of data lost on power fail etc.
  2156. fd = open(fn, (O_WRONLY | O_CREAT), 0666);
  2157. if (fd < 0)
  2158. return -1;
  2159. cnt = last - first + 1;
  2160. charcnt = full_write(fd, first, cnt);
  2161. ftruncate(fd, charcnt);
  2162. if (charcnt == cnt) {
  2163. // good write
  2164. //modified_count = FALSE;
  2165. } else {
  2166. charcnt = 0;
  2167. }
  2168. close(fd);
  2169. return charcnt;
  2170. }
  2171. #if ENABLE_FEATURE_VI_SEARCH
  2172. # if ENABLE_FEATURE_VI_REGEX_SEARCH
  2173. // search for pattern starting at p
  2174. static char *char_search(char *p, const char *pat, int dir_and_range)
  2175. {
  2176. struct re_pattern_buffer preg;
  2177. const char *err;
  2178. char *q;
  2179. int i, size, range, start;
  2180. re_syntax_options = RE_SYNTAX_POSIX_BASIC & (~RE_DOT_NEWLINE);
  2181. if (ignorecase)
  2182. re_syntax_options |= RE_ICASE;
  2183. memset(&preg, 0, sizeof(preg));
  2184. err = re_compile_pattern(pat, strlen(pat), &preg);
  2185. preg.not_bol = p != text;
  2186. preg.not_eol = p != end - 1;
  2187. if (err != NULL) {
  2188. status_line_bold("bad search pattern '%s': %s", pat, err);
  2189. return p;
  2190. }
  2191. range = (dir_and_range & 1);
  2192. q = end - 1; // if FULL
  2193. if (range == LIMITED)
  2194. q = next_line(p);
  2195. if (dir_and_range < 0) { // BACK?
  2196. q = text;
  2197. if (range == LIMITED)
  2198. q = prev_line(p);
  2199. }
  2200. // RANGE could be negative if we are searching backwards
  2201. range = q - p;
  2202. if (range < 0) {
  2203. size = -range;
  2204. start = size;
  2205. } else {
  2206. size = range;
  2207. start = 0;
  2208. }
  2209. q = p - start;
  2210. if (q < text)
  2211. q = text;
  2212. // search for the compiled pattern, preg, in p[]
  2213. // range < 0, start == size: search backward
  2214. // range > 0, start == 0: search forward
  2215. // re_search() < 0: not found or error
  2216. // re_search() >= 0: index of found pattern
  2217. // struct pattern char int int int struct reg
  2218. // re_search(*pattern_buffer, *string, size, start, range, *regs)
  2219. i = re_search(&preg, q, size, start, range, /*struct re_registers*:*/ NULL);
  2220. regfree(&preg);
  2221. return i < 0 ? NULL : q + i;
  2222. }
  2223. # else
  2224. # if ENABLE_FEATURE_VI_SETOPTS
  2225. static int mycmp(const char *s1, const char *s2, int len)
  2226. {
  2227. if (ignorecase) {
  2228. return strncasecmp(s1, s2, len);
  2229. }
  2230. return strncmp(s1, s2, len);
  2231. }
  2232. # else
  2233. # define mycmp strncmp
  2234. # endif
  2235. static char *char_search(char *p, const char *pat, int dir_and_range)
  2236. {
  2237. char *start, *stop;
  2238. int len;
  2239. int range;
  2240. len = strlen(pat);
  2241. range = (dir_and_range & 1);
  2242. if (dir_and_range > 0) { //FORWARD?
  2243. stop = end - 1; // assume range is p..end-1
  2244. if (range == LIMITED)
  2245. stop = next_line(p); // range is to next line
  2246. for (start = p; start < stop; start++) {
  2247. if (mycmp(start, pat, len) == 0) {
  2248. return start;
  2249. }
  2250. }
  2251. } else { //BACK
  2252. stop = text; // assume range is text..p
  2253. if (range == LIMITED)
  2254. stop = prev_line(p); // range is to prev line
  2255. for (start = p - len; start >= stop; start--) {
  2256. if (mycmp(start, pat, len) == 0) {
  2257. return start;
  2258. }
  2259. }
  2260. }
  2261. // pattern not found
  2262. return NULL;
  2263. }
  2264. # endif
  2265. #endif /* FEATURE_VI_SEARCH */
  2266. //----- The Colon commands -------------------------------------
  2267. #if ENABLE_FEATURE_VI_COLON
  2268. // Evaluate colon address expression. Returns a pointer to the
  2269. // next character or NULL on error. If 'result' contains a valid
  2270. // address 'valid' is TRUE.
  2271. static char *get_one_address(char *p, int *result, int *valid)
  2272. {
  2273. int num, sign, addr, got_addr;
  2274. # if ENABLE_FEATURE_VI_YANKMARK || ENABLE_FEATURE_VI_SEARCH
  2275. char *q, c;
  2276. # endif
  2277. IF_FEATURE_VI_SEARCH(int dir;)
  2278. got_addr = FALSE;
  2279. addr = count_lines(text, dot); // default to current line
  2280. sign = 0;
  2281. for (;;) {
  2282. if (isblank(*p)) {
  2283. if (got_addr) {
  2284. addr += sign;
  2285. sign = 0;
  2286. }
  2287. p++;
  2288. } else if (!got_addr && *p == '.') { // the current line
  2289. p++;
  2290. //addr = count_lines(text, dot);
  2291. got_addr = TRUE;
  2292. } else if (!got_addr && *p == '$') { // the last line in file
  2293. p++;
  2294. addr = count_lines(text, end - 1);
  2295. got_addr = TRUE;
  2296. }
  2297. # if ENABLE_FEATURE_VI_YANKMARK
  2298. else if (!got_addr && *p == '\'') { // is this a mark addr
  2299. p++;
  2300. c = tolower(*p);
  2301. p++;
  2302. q = NULL;
  2303. if (c >= 'a' && c <= 'z') {
  2304. // we have a mark
  2305. c = c - 'a';
  2306. q = mark[(unsigned char) c];
  2307. }
  2308. if (q == NULL) { // is mark valid
  2309. status_line_bold("Mark not set");
  2310. return NULL;
  2311. }
  2312. addr = count_lines(text, q);
  2313. got_addr = TRUE;
  2314. }
  2315. # endif
  2316. # if ENABLE_FEATURE_VI_SEARCH
  2317. else if (!got_addr && (*p == '/' || *p == '?')) { // a search pattern
  2318. c = *p;
  2319. q = strchrnul(p + 1, c);
  2320. if (p + 1 != q) {
  2321. // save copy of new pattern
  2322. free(last_search_pattern);
  2323. last_search_pattern = xstrndup(p, q - p);
  2324. }
  2325. p = q;
  2326. if (*p == c)
  2327. p++;
  2328. if (c == '/') {
  2329. q = next_line(dot);
  2330. dir = (FORWARD << 1) | FULL;
  2331. } else {
  2332. q = begin_line(dot);
  2333. dir = ((unsigned)BACK << 1) | FULL;
  2334. }
  2335. q = char_search(q, last_search_pattern + 1, dir);
  2336. if (q == NULL) {
  2337. // no match, continue from other end of file
  2338. q = char_search(dir > 0 ? text : end - 1,
  2339. last_search_pattern + 1, dir);
  2340. if (q == NULL) {
  2341. status_line_bold("Pattern not found");
  2342. return NULL;
  2343. }
  2344. }
  2345. addr = count_lines(text, q);
  2346. got_addr = TRUE;
  2347. }
  2348. # endif
  2349. else if (isdigit(*p)) {
  2350. num = 0;
  2351. while (isdigit(*p))
  2352. num = num * 10 + *p++ -'0';
  2353. if (!got_addr) { // specific line number
  2354. addr = num;
  2355. got_addr = TRUE;
  2356. } else { // offset from current addr
  2357. addr += sign >= 0 ? num : -num;
  2358. }
  2359. sign = 0;
  2360. } else if (*p == '-' || *p == '+') {
  2361. if (!got_addr) { // default address is dot
  2362. //addr = count_lines(text, dot);
  2363. got_addr = TRUE;
  2364. } else {
  2365. addr += sign;
  2366. }
  2367. sign = *p++ == '-' ? -1 : 1;
  2368. } else {
  2369. addr += sign; // consume unused trailing sign
  2370. break;
  2371. }
  2372. }
  2373. *result = addr;
  2374. *valid = got_addr;
  2375. return p;
  2376. }
  2377. # define GET_ADDRESS 0
  2378. # define GET_SEPARATOR 1
  2379. // Read line addresses for a colon command. The user can enter as
  2380. // many as they like but only the last two will be used.
  2381. static char *get_address(char *p, int *b, int *e, unsigned int *got)
  2382. {
  2383. int state = GET_ADDRESS;
  2384. int valid;
  2385. int addr;
  2386. char *save_dot = dot;
  2387. //----- get the address' i.e., 1,3 'a,'b -----
  2388. for (;;) {
  2389. if (isblank(*p)) {
  2390. p++;
  2391. } else if (state == GET_ADDRESS && *p == '%') { // alias for 1,$
  2392. p++;
  2393. *b = 1;
  2394. *e = count_lines(text, end-1);
  2395. *got = 3;
  2396. state = GET_SEPARATOR;
  2397. } else if (state == GET_ADDRESS) {
  2398. valid = FALSE;
  2399. p = get_one_address(p, &addr, &valid);
  2400. // Quit on error or if the address is invalid and isn't of
  2401. // the form ',$' or '1,' (in which case it defaults to dot).
  2402. if (p == NULL || !(valid || *p == ',' || *p == ';' || *got & 1))
  2403. break;
  2404. *b = *e;
  2405. *e = addr;
  2406. *got = (*got << 1) | 1;
  2407. state = GET_SEPARATOR;
  2408. } else if (state == GET_SEPARATOR && (*p == ',' || *p == ';')) {
  2409. if (*p == ';')
  2410. dot = find_line(*e);
  2411. p++;
  2412. state = GET_ADDRESS;
  2413. } else {
  2414. break;
  2415. }
  2416. }
  2417. dot = save_dot;
  2418. return p;
  2419. }
  2420. # if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
  2421. static void setops(char *args, int flg_no)
  2422. {
  2423. char *eq;
  2424. int index;
  2425. eq = strchr(args, '=');
  2426. if (eq) *eq = '\0';
  2427. index = index_in_strings(OPTS_STR, args + flg_no);
  2428. if (eq) *eq = '=';
  2429. if (index < 0) {
  2430. bad:
  2431. status_line_bold("bad option: %s", args);
  2432. return;
  2433. }
  2434. index = 1 << (index >> 1); // convert to VI_bit
  2435. if (index & VI_TABSTOP) {
  2436. int t;
  2437. if (!eq || flg_no) // no "=NNN" or it is "notabstop"?
  2438. goto bad;
  2439. t = bb_strtou(eq + 1, NULL, 10);
  2440. if (t <= 0 || t > MAX_TABSTOP)
  2441. goto bad;
  2442. tabstop = t;
  2443. return;
  2444. }
  2445. if (eq) goto bad; // boolean option has "="?
  2446. if (flg_no) {
  2447. vi_setops &= ~index;
  2448. } else {
  2449. vi_setops |= index;
  2450. }
  2451. }
  2452. # endif
  2453. # if ENABLE_FEATURE_VI_COLON_EXPAND
  2454. static char *expand_args(char *args)
  2455. {
  2456. char *s, *t;
  2457. const char *replace;
  2458. args = xstrdup(args);
  2459. for (s = args; *s; s++) {
  2460. if (*s == '%') {
  2461. replace = current_filename;
  2462. } else if (*s == '#') {
  2463. replace = alt_filename;
  2464. } else {
  2465. if (*s == '\\' && s[1] != '\0') {
  2466. for (t = s++; *t; t++)
  2467. *t = t[1];
  2468. }
  2469. continue;
  2470. }
  2471. if (replace == NULL) {
  2472. free(args);
  2473. status_line_bold("No previous filename");
  2474. return NULL;
  2475. }
  2476. *s = '\0';
  2477. t = xasprintf("%s%s%s", args, replace, s+1);
  2478. s = t + (s - args) + strlen(replace);
  2479. free(args);
  2480. args = t;
  2481. }
  2482. return args;
  2483. }
  2484. # else
  2485. # define expand_args(a) (a)
  2486. # endif
  2487. #endif /* FEATURE_VI_COLON */
  2488. #if ENABLE_FEATURE_VI_REGEX_SEARCH
  2489. # define MAX_SUBPATTERN 10 // subpatterns \0 .. \9
  2490. // Like strchr() but skipping backslash-escaped characters
  2491. static char *strchr_backslash(const char *s, int c)
  2492. {
  2493. while (*s) {
  2494. if (*s == c)
  2495. return (char *)s;
  2496. if (*s == '\\')
  2497. if (*++s == '\0')
  2498. break;
  2499. s++;
  2500. }
  2501. return NULL;
  2502. }
  2503. // If the return value is not NULL the caller should free R
  2504. static char *regex_search(char *q, regex_t *preg, const char *Rorig,
  2505. size_t *len_F, size_t *len_R, char **R)
  2506. {
  2507. regmatch_t regmatch[MAX_SUBPATTERN], *cur_match;
  2508. char *found = NULL;
  2509. const char *t;
  2510. char *r;
  2511. regmatch[0].rm_so = 0;
  2512. regmatch[0].rm_eo = end_line(q) - q;
  2513. if (regexec(preg, q, MAX_SUBPATTERN, regmatch, REG_STARTEND) != 0)
  2514. return found;
  2515. found = q + regmatch[0].rm_so;
  2516. *len_F = regmatch[0].rm_eo - regmatch[0].rm_so;
  2517. *R = NULL;
  2518. fill_result:
  2519. // first pass calculates len_R, second fills R
  2520. *len_R = 0;
  2521. for (t = Rorig, r = *R; *t; t++) {
  2522. size_t len = 1; // default is to copy one char from replace pattern
  2523. const char *from = t;
  2524. if (*t == '\\') {
  2525. from = ++t; // skip backslash
  2526. if (*t >= '0' && *t < '0' + MAX_SUBPATTERN) {
  2527. cur_match = regmatch + (*t - '0');
  2528. if (cur_match->rm_so >= 0) {
  2529. len = cur_match->rm_eo - cur_match->rm_so;
  2530. from = q + cur_match->rm_so;
  2531. }
  2532. }
  2533. }
  2534. *len_R += len;
  2535. if (*R) {
  2536. memcpy(r, from, len);
  2537. r += len;
  2538. /* *r = '\0'; - xzalloc did it */
  2539. }
  2540. }
  2541. if (*R == NULL) {
  2542. *R = xzalloc(*len_R + 1);
  2543. goto fill_result;
  2544. }
  2545. return found;
  2546. }
  2547. #else /* !ENABLE_FEATURE_VI_REGEX_SEARCH */
  2548. # define strchr_backslash(s, c) strchr(s, c)
  2549. #endif /* ENABLE_FEATURE_VI_REGEX_SEARCH */
  2550. // buf must be no longer than MAX_INPUT_LEN!
  2551. static void colon(char *buf)
  2552. {
  2553. #if !ENABLE_FEATURE_VI_COLON
  2554. // Simple ":cmd" handler with minimal set of commands
  2555. char *p = buf;
  2556. int cnt;
  2557. if (*p == ':')
  2558. p++;
  2559. cnt = strlen(p);
  2560. if (cnt == 0)
  2561. return;
  2562. if (strncmp(p, "quit", cnt) == 0
  2563. || strcmp(p, "q!") == 0
  2564. ) {
  2565. if (modified_count && p[1] != '!') {
  2566. status_line_bold("No write since last change (:%s! overrides)", p);
  2567. } else {
  2568. editing = 0;
  2569. }
  2570. return;
  2571. }
  2572. if (strncmp(p, "write", cnt) == 0
  2573. || strcmp(p, "wq") == 0
  2574. || strcmp(p, "wn") == 0
  2575. || (p[0] == 'x' && !p[1])
  2576. ) {
  2577. if (modified_count != 0 || p[0] != 'x') {
  2578. cnt = file_write(current_filename, text, end - 1);
  2579. }
  2580. if (cnt < 0) {
  2581. if (cnt == -1)
  2582. status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
  2583. } else {
  2584. modified_count = 0;
  2585. last_modified_count = -1;
  2586. status_line("'%s' %uL, %uC",
  2587. current_filename,
  2588. count_lines(text, end - 1), cnt
  2589. );
  2590. if (p[0] == 'x'
  2591. || p[1] == 'q' || p[1] == 'n'
  2592. ) {
  2593. editing = 0;
  2594. }
  2595. }
  2596. return;
  2597. }
  2598. if (strncmp(p, "file", cnt) == 0) {
  2599. last_status_cksum = 0; // force status update
  2600. return;
  2601. }
  2602. if (sscanf(p, "%d", &cnt) > 0) {
  2603. dot = find_line(cnt);
  2604. dot_skip_over_ws();
  2605. return;
  2606. }
  2607. not_implemented(p);
  2608. #else
  2609. // check how many addresses we got
  2610. # define GOT_ADDRESS (got & 1)
  2611. # define GOT_RANGE ((got & 3) == 3)
  2612. char c, *buf1, *q, *r;
  2613. char *fn, cmd[MAX_INPUT_LEN], *cmdend, *args, *exp = NULL;
  2614. int i, l, li, b, e;
  2615. unsigned int got;
  2616. int useforce;
  2617. // :3154 // if (-e line 3154) goto it else stay put
  2618. // :4,33w! foo // write a portion of buffer to file "foo"
  2619. // :w // write all of buffer to current file
  2620. // :q // quit
  2621. // :q! // quit- dont care about modified file
  2622. // :'a,'z!sort -u // filter block through sort
  2623. // :'f // goto mark "f"
  2624. // :'fl // list literal the mark "f" line
  2625. // :.r bar // read file "bar" into buffer before dot
  2626. // :/123/,/abc/d // delete lines from "123" line to "abc" line
  2627. // :/xyz/ // goto the "xyz" line
  2628. // :s/find/replace/ // substitute pattern "find" with "replace"
  2629. // :!<cmd> // run <cmd> then return
  2630. //
  2631. while (*buf == ':')
  2632. buf++; // move past leading colons
  2633. while (isblank(*buf))
  2634. buf++; // move past leading blanks
  2635. if (!buf[0] || buf[0] == '"')
  2636. goto ret; // ignore empty lines or those starting with '"'
  2637. li = i = 0;
  2638. b = e = -1;
  2639. got = 0;
  2640. li = count_lines(text, end - 1);
  2641. fn = current_filename;
  2642. // look for optional address(es) :. :1 :1,9 :'q,'a :%
  2643. buf = get_address(buf, &b, &e, &got);
  2644. if (buf == NULL) {
  2645. goto ret;
  2646. }
  2647. // get the COMMAND into cmd[]
  2648. strcpy(cmd, buf);
  2649. buf1 = cmd;
  2650. while (!isspace(*buf1) && *buf1 != '\0') {
  2651. buf1++;
  2652. }
  2653. cmdend = buf1;
  2654. // get any ARGuments
  2655. while (isblank(*buf1))
  2656. buf1++;
  2657. args = buf1;
  2658. *cmdend = '\0';
  2659. useforce = FALSE;
  2660. if (cmdend > cmd && cmdend[-1] == '!') {
  2661. useforce = TRUE;
  2662. cmdend[-1] = '\0'; // get rid of !
  2663. }
  2664. // assume the command will want a range, certain commands
  2665. // (read, substitute) need to adjust these assumptions
  2666. if (!GOT_ADDRESS) {
  2667. q = text; // no addr, use 1,$ for the range
  2668. r = end - 1;
  2669. } else {
  2670. // at least one addr was given, get its details
  2671. if (e < 0 || e > li) {
  2672. status_line_bold("Invalid range");
  2673. goto ret;
  2674. }
  2675. q = r = find_line(e);
  2676. if (!GOT_RANGE) {
  2677. // if there is only one addr, then it's the line
  2678. // number of the single line the user wants.
  2679. // Reset the end pointer to the end of that line.
  2680. r = end_line(q);
  2681. li = 1;
  2682. } else {
  2683. // we were given two addrs. change the
  2684. // start pointer to the addr given by user.
  2685. if (b < 0 || b > li || b > e) {
  2686. status_line_bold("Invalid range");
  2687. goto ret;
  2688. }
  2689. q = find_line(b); // what line is #b
  2690. r = end_line(r);
  2691. li = e - b + 1;
  2692. }
  2693. }
  2694. // ------------ now look for the command ------------
  2695. i = strlen(cmd);
  2696. if (i == 0) { // :123CR goto line #123
  2697. if (e >= 0) {
  2698. dot = find_line(e); // what line is #e
  2699. dot_skip_over_ws();
  2700. }
  2701. }
  2702. # if ENABLE_FEATURE_ALLOW_EXEC
  2703. else if (cmd[0] == '!') { // run a cmd
  2704. int retcode;
  2705. // :!ls run the <cmd>
  2706. exp = expand_args(buf + 1);
  2707. if (exp == NULL)
  2708. goto ret;
  2709. go_bottom_and_clear_to_eol();
  2710. cookmode();
  2711. retcode = system(exp); // run the cmd
  2712. if (retcode)
  2713. printf("\nshell returned %i\n\n", retcode);
  2714. rawmode();
  2715. Hit_Return(); // let user see results
  2716. }
  2717. # endif
  2718. else if (cmd[0] == '=' && !cmd[1]) { // where is the address
  2719. if (!GOT_ADDRESS) { // no addr given- use defaults
  2720. e = count_lines(text, dot);
  2721. }
  2722. status_line("%d", e);
  2723. } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
  2724. if (!GOT_ADDRESS) { // no addr given- use defaults
  2725. q = begin_line(dot); // assume .,. for the range
  2726. r = end_line(dot);
  2727. }
  2728. dot = yank_delete(q, r, WHOLE, YANKDEL, ALLOW_UNDO); // save, then delete lines
  2729. dot_skip_over_ws();
  2730. } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
  2731. int size;
  2732. // don't edit, if the current file has been modified
  2733. if (modified_count && !useforce) {
  2734. status_line_bold("No write since last change (:%s! overrides)", cmd);
  2735. goto ret;
  2736. }
  2737. if (args[0]) {
  2738. // the user supplied a file name
  2739. fn = exp = expand_args(args);
  2740. if (exp == NULL)
  2741. goto ret;
  2742. } else if (current_filename == NULL) {
  2743. // no user file name, no current name- punt
  2744. status_line_bold("No current filename");
  2745. goto ret;
  2746. }
  2747. size = init_text_buffer(fn);
  2748. # if ENABLE_FEATURE_VI_YANKMARK
  2749. if (Ureg >= 0 && Ureg < 28) {
  2750. free(reg[Ureg]); // free orig line reg- for 'U'
  2751. reg[Ureg] = NULL;
  2752. }
  2753. /*if (YDreg < 28) - always true*/ {
  2754. free(reg[YDreg]); // free default yank/delete register
  2755. reg[YDreg] = NULL;
  2756. }
  2757. # endif
  2758. // how many lines in text[]?
  2759. li = count_lines(text, end - 1);
  2760. status_line("'%s'%s"
  2761. IF_FEATURE_VI_READONLY("%s")
  2762. " %uL, %uC",
  2763. fn,
  2764. (size < 0 ? " [New file]" : ""),
  2765. IF_FEATURE_VI_READONLY(
  2766. ((readonly_mode) ? " [Readonly]" : ""),
  2767. )
  2768. li, (int)(end - text)
  2769. );
  2770. } else if (strncmp(cmd, "file", i) == 0) { // what File is this
  2771. if (e >= 0) {
  2772. status_line_bold("No address allowed on this command");
  2773. goto ret;
  2774. }
  2775. if (args[0]) {
  2776. // user wants a new filename
  2777. exp = expand_args(args);
  2778. if (exp == NULL)
  2779. goto ret;
  2780. update_filename(exp);
  2781. } else {
  2782. // user wants file status info
  2783. last_status_cksum = 0; // force status update
  2784. }
  2785. } else if (strncmp(cmd, "features", i) == 0) { // what features are available
  2786. // print out values of all features
  2787. go_bottom_and_clear_to_eol();
  2788. cookmode();
  2789. show_help();
  2790. rawmode();
  2791. Hit_Return();
  2792. } else if (strncmp(cmd, "list", i) == 0) { // literal print line
  2793. if (!GOT_ADDRESS) { // no addr given- use defaults
  2794. q = begin_line(dot); // assume .,. for the range
  2795. r = end_line(dot);
  2796. }
  2797. go_bottom_and_clear_to_eol();
  2798. puts("\r");
  2799. for (; q <= r; q++) {
  2800. int c_is_no_print;
  2801. c = *q;
  2802. c_is_no_print = (c & 0x80) && !Isprint(c);
  2803. if (c_is_no_print) {
  2804. c = '.';
  2805. standout_start();
  2806. }
  2807. if (c == '\n') {
  2808. write1("$\r");
  2809. } else if (c < ' ' || c == 127) {
  2810. bb_putchar('^');
  2811. if (c == 127)
  2812. c = '?';
  2813. else
  2814. c += '@';
  2815. }
  2816. bb_putchar(c);
  2817. if (c_is_no_print)
  2818. standout_end();
  2819. }
  2820. Hit_Return();
  2821. } else if (strncmp(cmd, "quit", i) == 0 // quit
  2822. || strncmp(cmd, "next", i) == 0 // edit next file
  2823. || strncmp(cmd, "prev", i) == 0 // edit previous file
  2824. ) {
  2825. int n;
  2826. if (useforce) {
  2827. if (*cmd == 'q') {
  2828. // force end of argv list
  2829. optind = cmdline_filecnt;
  2830. }
  2831. editing = 0;
  2832. goto ret;
  2833. }
  2834. // don't exit if the file been modified
  2835. if (modified_count) {
  2836. status_line_bold("No write since last change (:%s! overrides)", cmd);
  2837. goto ret;
  2838. }
  2839. // are there other file to edit
  2840. n = cmdline_filecnt - optind - 1;
  2841. if (*cmd == 'q' && n > 0) {
  2842. status_line_bold("%u more file(s) to edit", n);
  2843. goto ret;
  2844. }
  2845. if (*cmd == 'n' && n <= 0) {
  2846. status_line_bold("No more files to edit");
  2847. goto ret;
  2848. }
  2849. if (*cmd == 'p') {
  2850. // are there previous files to edit
  2851. if (optind < 1) {
  2852. status_line_bold("No previous files to edit");
  2853. goto ret;
  2854. }
  2855. optind -= 2;
  2856. }
  2857. editing = 0;
  2858. } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
  2859. int size, num;
  2860. if (args[0]) {
  2861. // the user supplied a file name
  2862. fn = exp = expand_args(args);
  2863. if (exp == NULL)
  2864. goto ret;
  2865. init_filename(fn);
  2866. } else if (current_filename == NULL) {
  2867. // no user file name, no current name- punt
  2868. status_line_bold("No current filename");
  2869. goto ret;
  2870. }
  2871. if (e == 0) { // user said ":0r foo"
  2872. q = text;
  2873. } else { // read after given line or current line if none given
  2874. q = next_line(GOT_ADDRESS ? find_line(e) : dot);
  2875. // read after last line
  2876. if (q == end-1)
  2877. ++q;
  2878. }
  2879. num = count_lines(text, q);
  2880. if (q == end)
  2881. num++;
  2882. { // dance around potentially-reallocated text[]
  2883. uintptr_t ofs = q - text;
  2884. size = file_insert(fn, q, 0);
  2885. q = text + ofs;
  2886. }
  2887. if (size < 0)
  2888. goto ret; // nothing was inserted
  2889. // how many lines in text[]?
  2890. li = count_lines(q, q + size - 1);
  2891. status_line("'%s'"
  2892. IF_FEATURE_VI_READONLY("%s")
  2893. " %uL, %uC",
  2894. fn,
  2895. IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
  2896. li, size
  2897. );
  2898. dot = find_line(num);
  2899. } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
  2900. if (modified_count && !useforce) {
  2901. status_line_bold("No write since last change (:%s! overrides)", cmd);
  2902. } else {
  2903. // reset the filenames to edit
  2904. optind = -1; // start from 0th file
  2905. editing = 0;
  2906. }
  2907. # if ENABLE_FEATURE_VI_SET
  2908. } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
  2909. # if ENABLE_FEATURE_VI_SETOPTS
  2910. char *argp, *argn, oldch;
  2911. # endif
  2912. // only blank is regarded as args delimiter. What about tab '\t'?
  2913. if (!args[0] || strcmp(args, "all") == 0) {
  2914. // print out values of all options
  2915. # if ENABLE_FEATURE_VI_SETOPTS
  2916. status_line_bold(
  2917. "%sautoindent "
  2918. "%sexpandtab "
  2919. "%sflash "
  2920. "%signorecase "
  2921. "%sshowmatch "
  2922. "tabstop=%u",
  2923. autoindent ? "" : "no",
  2924. expandtab ? "" : "no",
  2925. err_method ? "" : "no",
  2926. ignorecase ? "" : "no",
  2927. showmatch ? "" : "no",
  2928. tabstop
  2929. );
  2930. # endif
  2931. goto ret;
  2932. }
  2933. # if ENABLE_FEATURE_VI_SETOPTS
  2934. argp = args;
  2935. while (*argp) {
  2936. i = 0;
  2937. if (argp[0] == 'n' && argp[1] == 'o') // "noXXX"
  2938. i = 2;
  2939. argn = skip_non_whitespace(argp);
  2940. oldch = *argn;
  2941. *argn = '\0';
  2942. setops(argp, i);
  2943. *argn = oldch;
  2944. argp = skip_whitespace(argn);
  2945. }
  2946. # endif /* FEATURE_VI_SETOPTS */
  2947. # endif /* FEATURE_VI_SET */
  2948. # if ENABLE_FEATURE_VI_SEARCH
  2949. } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
  2950. char *F, *R, *flags;
  2951. size_t len_F, len_R;
  2952. int gflag = 0; // global replace flag
  2953. int subs = 0; // number of substitutions
  2954. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  2955. int last_line = 0, lines = 0;
  2956. # endif
  2957. # if ENABLE_FEATURE_VI_REGEX_SEARCH
  2958. regex_t preg;
  2959. int cflags;
  2960. char *Rorig;
  2961. # if ENABLE_FEATURE_VI_UNDO
  2962. int undo = 0;
  2963. # endif
  2964. # endif
  2965. // F points to the "find" pattern
  2966. // R points to the "replace" pattern
  2967. // replace the cmd line delimiters "/" with NULs
  2968. c = buf[1]; // what is the delimiter
  2969. F = buf + 2; // start of "find"
  2970. R = strchr_backslash(F, c); // middle delimiter
  2971. if (!R)
  2972. goto colon_s_fail;
  2973. len_F = R - F;
  2974. *R++ = '\0'; // terminate "find"
  2975. flags = strchr_backslash(R, c);
  2976. if (flags) {
  2977. *flags++ = '\0'; // terminate "replace"
  2978. gflag = *flags;
  2979. }
  2980. if (len_F) { // save "find" as last search pattern
  2981. free(last_search_pattern);
  2982. last_search_pattern = xstrdup(F - 1);
  2983. last_search_pattern[0] = '/';
  2984. } else if (last_search_pattern[1] == '\0') {
  2985. status_line_bold("No previous search");
  2986. goto ret;
  2987. } else {
  2988. F = last_search_pattern + 1;
  2989. len_F = strlen(F);
  2990. }
  2991. if (!GOT_ADDRESS) { // no addr given
  2992. q = begin_line(dot); // start with cur line
  2993. r = end_line(dot);
  2994. b = e = count_lines(text, q); // cur line number
  2995. } else if (!GOT_RANGE) { // one addr given
  2996. b = e;
  2997. }
  2998. # if ENABLE_FEATURE_VI_REGEX_SEARCH
  2999. Rorig = R;
  3000. cflags = 0;
  3001. if (ignorecase)
  3002. cflags = REG_ICASE;
  3003. memset(&preg, 0, sizeof(preg));
  3004. if (regcomp(&preg, F, cflags) != 0) {
  3005. status_line(":s bad search pattern");
  3006. goto regex_search_end;
  3007. }
  3008. # else
  3009. len_R = strlen(R);
  3010. # endif
  3011. for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
  3012. char *ls = q; // orig line start
  3013. char *found;
  3014. vc4:
  3015. # if ENABLE_FEATURE_VI_REGEX_SEARCH
  3016. found = regex_search(q, &preg, Rorig, &len_F, &len_R, &R);
  3017. # else
  3018. found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
  3019. # endif
  3020. if (found) {
  3021. uintptr_t bias;
  3022. // we found the "find" pattern - delete it
  3023. // For undo support, the first item should not be chained
  3024. // This needs to be handled differently depending on
  3025. // whether or not regex support is enabled.
  3026. # if ENABLE_FEATURE_VI_REGEX_SEARCH
  3027. # define TEST_LEN_F len_F // len_F may be zero
  3028. # define TEST_UNDO1 undo++
  3029. # define TEST_UNDO2 undo++
  3030. # else
  3031. # define TEST_LEN_F 1 // len_F is never zero
  3032. # define TEST_UNDO1 subs
  3033. # define TEST_UNDO2 1
  3034. # endif
  3035. if (TEST_LEN_F) // match can be empty, no delete needed
  3036. text_hole_delete(found, found + len_F - 1,
  3037. TEST_UNDO1 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
  3038. if (len_R != 0) { // insert the "replace" pattern, if required
  3039. bias = string_insert(found, R,
  3040. TEST_UNDO2 ? ALLOW_UNDO_CHAIN : ALLOW_UNDO);
  3041. found += bias;
  3042. ls += bias;
  3043. //q += bias; - recalculated anyway
  3044. }
  3045. # if ENABLE_FEATURE_VI_REGEX_SEARCH
  3046. free(R);
  3047. # endif
  3048. if (TEST_LEN_F || len_R != 0) {
  3049. dot = ls;
  3050. subs++;
  3051. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  3052. if (last_line != i) {
  3053. last_line = i;
  3054. ++lines;
  3055. }
  3056. # endif
  3057. }
  3058. // check for "global" :s/foo/bar/g
  3059. if (gflag == 'g') {
  3060. if ((found + len_R) < end_line(ls)) {
  3061. q = found + len_R;
  3062. goto vc4; // don't let q move past cur line
  3063. }
  3064. }
  3065. }
  3066. q = next_line(ls);
  3067. }
  3068. if (subs == 0) {
  3069. status_line_bold("No match");
  3070. } else {
  3071. dot_skip_over_ws();
  3072. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  3073. if (subs > 1)
  3074. status_line("%d substitutions on %d lines", subs, lines);
  3075. # endif
  3076. }
  3077. # if ENABLE_FEATURE_VI_REGEX_SEARCH
  3078. regex_search_end:
  3079. regfree(&preg);
  3080. # endif
  3081. # endif /* FEATURE_VI_SEARCH */
  3082. } else if (strncmp(cmd, "version", i) == 0) { // show software version
  3083. status_line(BB_VER);
  3084. } else if (strncmp(cmd, "write", i) == 0 // write text to file
  3085. || strcmp(cmd, "wq") == 0
  3086. || strcmp(cmd, "wn") == 0
  3087. || (cmd[0] == 'x' && !cmd[1])
  3088. ) {
  3089. int size;
  3090. //int forced = FALSE;
  3091. // is there a file name to write to?
  3092. if (args[0]) {
  3093. struct stat statbuf;
  3094. exp = expand_args(args);
  3095. if (exp == NULL)
  3096. goto ret;
  3097. if (!useforce && (fn == NULL || strcmp(fn, exp) != 0) &&
  3098. stat(exp, &statbuf) == 0) {
  3099. status_line_bold("File exists (:w! overrides)");
  3100. goto ret;
  3101. }
  3102. fn = exp;
  3103. init_filename(fn);
  3104. }
  3105. # if ENABLE_FEATURE_VI_READONLY
  3106. else if (readonly_mode && !useforce && fn) {
  3107. status_line_bold("'%s' is read only", fn);
  3108. goto ret;
  3109. }
  3110. # endif
  3111. //if (useforce) {
  3112. // if "fn" is not write-able, chmod u+w
  3113. // sprintf(syscmd, "chmod u+w %s", fn);
  3114. // system(syscmd);
  3115. // forced = TRUE;
  3116. //}
  3117. if (modified_count != 0 || cmd[0] != 'x') {
  3118. size = r - q + 1;
  3119. l = file_write(fn, q, r);
  3120. } else {
  3121. size = 0;
  3122. l = 0;
  3123. }
  3124. //if (useforce && forced) {
  3125. // chmod u-w
  3126. // sprintf(syscmd, "chmod u-w %s", fn);
  3127. // system(syscmd);
  3128. // forced = FALSE;
  3129. //}
  3130. if (l < 0) {
  3131. if (l == -1)
  3132. status_line_bold_errno(fn);
  3133. } else {
  3134. // how many lines written
  3135. li = count_lines(q, q + l - 1);
  3136. status_line("'%s' %uL, %uC", fn, li, l);
  3137. if (l == size) {
  3138. if (q == text && q + l == end) {
  3139. modified_count = 0;
  3140. last_modified_count = -1;
  3141. }
  3142. if (cmd[1] == 'n') {
  3143. editing = 0;
  3144. } else if (cmd[0] == 'x' || cmd[1] == 'q') {
  3145. // are there other files to edit?
  3146. int n = cmdline_filecnt - optind - 1;
  3147. if (n > 0) {
  3148. if (useforce) {
  3149. // force end of argv list
  3150. optind = cmdline_filecnt;
  3151. } else {
  3152. status_line_bold("%u more file(s) to edit", n);
  3153. goto ret;
  3154. }
  3155. }
  3156. editing = 0;
  3157. }
  3158. }
  3159. }
  3160. # if ENABLE_FEATURE_VI_YANKMARK
  3161. } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
  3162. if (!GOT_ADDRESS) { // no addr given- use defaults
  3163. q = begin_line(dot); // assume .,. for the range
  3164. r = end_line(dot);
  3165. }
  3166. text_yank(q, r, YDreg, WHOLE);
  3167. li = count_lines(q, r);
  3168. status_line("Yank %d lines (%d chars) into [%c]",
  3169. li, strlen(reg[YDreg]), what_reg());
  3170. # endif
  3171. } else {
  3172. // cmd unknown
  3173. not_implemented(cmd);
  3174. }
  3175. ret:
  3176. # if ENABLE_FEATURE_VI_COLON_EXPAND
  3177. free(exp);
  3178. # endif
  3179. dot = bound_dot(dot); // make sure "dot" is valid
  3180. return;
  3181. # if ENABLE_FEATURE_VI_SEARCH
  3182. colon_s_fail:
  3183. status_line(":s expression missing delimiters");
  3184. # endif
  3185. #endif /* FEATURE_VI_COLON */
  3186. }
  3187. //----- Char Routines --------------------------------------------
  3188. // Chars that are part of a word-
  3189. // 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
  3190. // Chars that are Not part of a word (stoppers)
  3191. // !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
  3192. // Chars that are WhiteSpace
  3193. // TAB NEWLINE VT FF RETURN SPACE
  3194. // DO NOT COUNT NEWLINE AS WHITESPACE
  3195. static int st_test(char *p, int type, int dir, char *tested)
  3196. {
  3197. char c, c0, ci;
  3198. int test, inc;
  3199. inc = dir;
  3200. c = c0 = p[0];
  3201. ci = p[inc];
  3202. test = 0;
  3203. if (type == S_BEFORE_WS) {
  3204. c = ci;
  3205. test = (!isspace(c) || c == '\n');
  3206. }
  3207. if (type == S_TO_WS) {
  3208. c = c0;
  3209. test = (!isspace(c) || c == '\n');
  3210. }
  3211. if (type == S_OVER_WS) {
  3212. c = c0;
  3213. test = isspace(c);
  3214. }
  3215. if (type == S_END_PUNCT) {
  3216. c = ci;
  3217. test = ispunct(c);
  3218. }
  3219. if (type == S_END_ALNUM) {
  3220. c = ci;
  3221. test = (isalnum(c) || c == '_');
  3222. }
  3223. *tested = c;
  3224. return test;
  3225. }
  3226. static char *skip_thing(char *p, int linecnt, int dir, int type)
  3227. {
  3228. char c;
  3229. while (st_test(p, type, dir, &c)) {
  3230. // make sure we limit search to correct number of lines
  3231. if (c == '\n' && --linecnt < 1)
  3232. break;
  3233. if (dir >= 0 && p >= end - 1)
  3234. break;
  3235. if (dir < 0 && p <= text)
  3236. break;
  3237. p += dir; // move to next char
  3238. }
  3239. return p;
  3240. }
  3241. #if ENABLE_FEATURE_VI_USE_SIGNALS
  3242. static void winch_handler(int sig UNUSED_PARAM)
  3243. {
  3244. int save_errno = errno;
  3245. // FIXME: do it in main loop!!!
  3246. signal(SIGWINCH, winch_handler);
  3247. query_screen_dimensions();
  3248. new_screen(rows, columns); // get memory for virtual screen
  3249. redraw(TRUE); // re-draw the screen
  3250. errno = save_errno;
  3251. }
  3252. static void tstp_handler(int sig UNUSED_PARAM)
  3253. {
  3254. int save_errno = errno;
  3255. // ioctl inside cookmode() was seen to generate SIGTTOU,
  3256. // stopping us too early. Prevent that:
  3257. signal(SIGTTOU, SIG_IGN);
  3258. go_bottom_and_clear_to_eol();
  3259. cookmode(); // terminal to "cooked"
  3260. // stop now
  3261. //signal(SIGTSTP, SIG_DFL);
  3262. //raise(SIGTSTP);
  3263. raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
  3264. //signal(SIGTSTP, tstp_handler);
  3265. // we have been "continued" with SIGCONT, restore screen and termios
  3266. rawmode(); // terminal to "raw"
  3267. last_status_cksum = 0; // force status update
  3268. redraw(TRUE); // re-draw the screen
  3269. errno = save_errno;
  3270. }
  3271. static void int_handler(int sig)
  3272. {
  3273. signal(SIGINT, int_handler);
  3274. siglongjmp(restart, sig);
  3275. }
  3276. #endif /* FEATURE_VI_USE_SIGNALS */
  3277. static void do_cmd(int c);
  3278. static int at_eof(const char *s)
  3279. {
  3280. // does 's' point to end of file, even with no terminating newline?
  3281. return ((s == end - 2 && s[1] == '\n') || s == end - 1);
  3282. }
  3283. static int find_range(char **start, char **stop, int cmd)
  3284. {
  3285. char *p, *q, *t;
  3286. int buftype = -1;
  3287. int c;
  3288. p = q = dot;
  3289. #if ENABLE_FEATURE_VI_YANKMARK
  3290. if (cmd == 'Y') {
  3291. c = 'y';
  3292. } else
  3293. #endif
  3294. {
  3295. c = get_motion_char();
  3296. }
  3297. #if ENABLE_FEATURE_VI_YANKMARK
  3298. if ((cmd == 'Y' || cmd == c) && strchr("cdy><", c)) {
  3299. #else
  3300. if (cmd == c && strchr("cd><", c)) {
  3301. #endif
  3302. // these cmds operate on whole lines
  3303. buftype = WHOLE;
  3304. if (--cmdcnt > 0) {
  3305. do_cmd('j');
  3306. if (cmd_error)
  3307. buftype = -1;
  3308. }
  3309. } else if (strchr("^%$0bBeEfFtThnN/?|{}\b\177", c)) {
  3310. // Most operate on char positions within a line. Of those that
  3311. // don't '%' needs no special treatment, search commands are
  3312. // marked as MULTI and "{}" are handled below.
  3313. buftype = strchr("nN/?", c) ? MULTI : PARTIAL;
  3314. do_cmd(c); // execute movement cmd
  3315. if (p == dot) // no movement is an error
  3316. buftype = -1;
  3317. } else if (strchr("wW", c)) {
  3318. buftype = MULTI;
  3319. do_cmd(c); // execute movement cmd
  3320. // step back one char, but not if we're at end of file,
  3321. // or if we are at EOF and search was for 'w' and we're at
  3322. // the start of a 'W' word.
  3323. if (dot > p && (!at_eof(dot) || (c == 'w' && ispunct(*dot))))
  3324. dot--;
  3325. t = dot;
  3326. // don't include trailing WS as part of word
  3327. while (dot > p && isspace(*dot)) {
  3328. if (*dot-- == '\n')
  3329. t = dot;
  3330. }
  3331. // for non-change operations WS after NL is not part of word
  3332. if (cmd != 'c' && dot != t && *dot != '\n')
  3333. dot = t;
  3334. } else if (strchr("GHL+-gjk'\r\n", c)) {
  3335. // these operate on whole lines
  3336. buftype = WHOLE;
  3337. do_cmd(c); // execute movement cmd
  3338. if (cmd_error)
  3339. buftype = -1;
  3340. } else if (c == ' ' || c == 'l') {
  3341. // forward motion by character
  3342. int tmpcnt = (cmdcnt ?: 1);
  3343. buftype = PARTIAL;
  3344. do_cmd(c); // execute movement cmd
  3345. // exclude last char unless range isn't what we expected
  3346. // this indicates we've hit EOL
  3347. if (tmpcnt == dot - p)
  3348. dot--;
  3349. }
  3350. if (buftype == -1) {
  3351. if (c != 27)
  3352. indicate_error();
  3353. return buftype;
  3354. }
  3355. q = dot;
  3356. if (q < p) {
  3357. t = q;
  3358. q = p;
  3359. p = t;
  3360. }
  3361. // movements which don't include end of range
  3362. if (q > p) {
  3363. if (strchr("^0bBFThnN/?|\b\177", c)) {
  3364. q--;
  3365. } else if (strchr("{}", c)) {
  3366. buftype = (p == begin_line(p) && (*q == '\n' || at_eof(q))) ?
  3367. WHOLE : MULTI;
  3368. if (!at_eof(q)) {
  3369. q--;
  3370. if (q > p && p != begin_line(p))
  3371. q--;
  3372. }
  3373. }
  3374. }
  3375. *start = p;
  3376. *stop = q;
  3377. return buftype;
  3378. }
  3379. //---------------------------------------------------------------------
  3380. //----- the Ascii Chart -----------------------------------------------
  3381. // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
  3382. // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
  3383. // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
  3384. // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
  3385. // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
  3386. // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
  3387. // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
  3388. // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
  3389. // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
  3390. // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
  3391. // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
  3392. // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
  3393. // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
  3394. // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
  3395. // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
  3396. // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
  3397. //---------------------------------------------------------------------
  3398. //----- Execute a Vi Command -----------------------------------
  3399. static void do_cmd(int c)
  3400. {
  3401. char *p, *q, *save_dot;
  3402. char buf[12];
  3403. int dir;
  3404. int cnt, i, j;
  3405. int c1;
  3406. #if ENABLE_FEATURE_VI_YANKMARK
  3407. char *orig_dot = dot;
  3408. #endif
  3409. #if ENABLE_FEATURE_VI_UNDO
  3410. int allow_undo = ALLOW_UNDO;
  3411. int undo_del = UNDO_DEL;
  3412. #endif
  3413. // c1 = c; // quiet the compiler
  3414. // cnt = yf = 0; // quiet the compiler
  3415. // p = q = save_dot = buf; // quiet the compiler
  3416. memset(buf, '\0', sizeof(buf));
  3417. keep_index = FALSE;
  3418. cmd_error = FALSE;
  3419. show_status_line();
  3420. // if this is a cursor key, skip these checks
  3421. switch (c) {
  3422. case KEYCODE_UP:
  3423. case KEYCODE_DOWN:
  3424. case KEYCODE_LEFT:
  3425. case KEYCODE_RIGHT:
  3426. case KEYCODE_HOME:
  3427. case KEYCODE_END:
  3428. case KEYCODE_PAGEUP:
  3429. case KEYCODE_PAGEDOWN:
  3430. case KEYCODE_DELETE:
  3431. goto key_cmd_mode;
  3432. }
  3433. if (cmd_mode == 2) {
  3434. // flip-flop Insert/Replace mode
  3435. if (c == KEYCODE_INSERT)
  3436. goto dc_i;
  3437. // we are 'R'eplacing the current *dot with new char
  3438. if (*dot == '\n') {
  3439. // don't Replace past E-o-l
  3440. cmd_mode = 1; // convert to insert
  3441. undo_queue_commit();
  3442. } else {
  3443. if (1 <= c || Isprint(c)) {
  3444. if (c != 27 && !isbackspace(c))
  3445. dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);
  3446. dot = char_insert(dot, c, ALLOW_UNDO_CHAIN);
  3447. }
  3448. goto dc1;
  3449. }
  3450. }
  3451. if (cmd_mode == 1) {
  3452. // hitting "Insert" twice means "R" replace mode
  3453. if (c == KEYCODE_INSERT) goto dc5;
  3454. // insert the char c at "dot"
  3455. if (1 <= c || Isprint(c)) {
  3456. dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
  3457. }
  3458. goto dc1;
  3459. }
  3460. key_cmd_mode:
  3461. switch (c) {
  3462. //case 0x01: // soh
  3463. //case 0x09: // ht
  3464. //case 0x0b: // vt
  3465. //case 0x0e: // so
  3466. //case 0x0f: // si
  3467. //case 0x10: // dle
  3468. //case 0x11: // dc1
  3469. //case 0x13: // dc3
  3470. #if ENABLE_FEATURE_VI_CRASHME
  3471. case 0x14: // dc4 ctrl-T
  3472. crashme = (crashme == 0) ? 1 : 0;
  3473. break;
  3474. #endif
  3475. //case 0x16: // syn
  3476. //case 0x17: // etb
  3477. //case 0x18: // can
  3478. //case 0x1c: // fs
  3479. //case 0x1d: // gs
  3480. //case 0x1e: // rs
  3481. //case 0x1f: // us
  3482. //case '!': // !-
  3483. //case '#': // #-
  3484. //case '&': // &-
  3485. //case '(': // (-
  3486. //case ')': // )-
  3487. //case '*': // *-
  3488. //case '=': // =-
  3489. //case '@': // @-
  3490. //case 'K': // K-
  3491. //case 'Q': // Q-
  3492. //case 'S': // S-
  3493. //case 'V': // V-
  3494. //case '[': // [-
  3495. //case '\\': // \-
  3496. //case ']': // ]-
  3497. //case '_': // _-
  3498. //case '`': // `-
  3499. //case 'v': // v-
  3500. default: // unrecognized command
  3501. buf[0] = c;
  3502. buf[1] = '\0';
  3503. not_implemented(buf);
  3504. end_cmd_q(); // stop adding to q
  3505. case 0x00: // nul- ignore
  3506. break;
  3507. case 2: // ctrl-B scroll up full screen
  3508. case KEYCODE_PAGEUP: // Cursor Key Page Up
  3509. dot_scroll(rows - 2, -1);
  3510. break;
  3511. case 4: // ctrl-D scroll down half screen
  3512. dot_scroll((rows - 2) / 2, 1);
  3513. break;
  3514. case 5: // ctrl-E scroll down one line
  3515. dot_scroll(1, 1);
  3516. break;
  3517. case 6: // ctrl-F scroll down full screen
  3518. case KEYCODE_PAGEDOWN: // Cursor Key Page Down
  3519. dot_scroll(rows - 2, 1);
  3520. break;
  3521. case 7: // ctrl-G show current status
  3522. last_status_cksum = 0; // force status update
  3523. break;
  3524. case 'h': // h- move left
  3525. case KEYCODE_LEFT: // cursor key Left
  3526. case 8: // ctrl-H- move left (This may be ERASE char)
  3527. case 0x7f: // DEL- move left (This may be ERASE char)
  3528. do {
  3529. dot_left();
  3530. } while (--cmdcnt > 0);
  3531. break;
  3532. case 10: // Newline ^J
  3533. case 'j': // j- goto next line, same col
  3534. case KEYCODE_DOWN: // cursor key Down
  3535. case 13: // Carriage Return ^M
  3536. case '+': // +- goto next line
  3537. q = dot;
  3538. do {
  3539. p = next_line(q);
  3540. if (p == end_line(q)) {
  3541. indicate_error();
  3542. goto dc1;
  3543. }
  3544. q = p;
  3545. } while (--cmdcnt > 0);
  3546. dot = q;
  3547. if (c == 13 || c == '+') {
  3548. dot_skip_over_ws();
  3549. } else {
  3550. // try to stay in saved column
  3551. dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
  3552. keep_index = TRUE;
  3553. }
  3554. break;
  3555. case 12: // ctrl-L force redraw whole screen
  3556. case 18: // ctrl-R force redraw
  3557. redraw(TRUE); // this will redraw the entire display
  3558. break;
  3559. case 21: // ctrl-U scroll up half screen
  3560. dot_scroll((rows - 2) / 2, -1);
  3561. break;
  3562. case 25: // ctrl-Y scroll up one line
  3563. dot_scroll(1, -1);
  3564. break;
  3565. case 27: // esc
  3566. if (cmd_mode == 0)
  3567. indicate_error();
  3568. cmd_mode = 0; // stop inserting
  3569. undo_queue_commit();
  3570. end_cmd_q();
  3571. last_status_cksum = 0; // force status update
  3572. break;
  3573. case ' ': // move right
  3574. case 'l': // move right
  3575. case KEYCODE_RIGHT: // Cursor Key Right
  3576. do {
  3577. dot_right();
  3578. } while (--cmdcnt > 0);
  3579. break;
  3580. #if ENABLE_FEATURE_VI_YANKMARK
  3581. case '"': // "- name a register to use for Delete/Yank
  3582. c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
  3583. if ((unsigned)c1 <= 25) { // a-z?
  3584. YDreg = c1;
  3585. } else {
  3586. indicate_error();
  3587. }
  3588. break;
  3589. case '\'': // '- goto a specific mark
  3590. c1 = (get_one_char() | 0x20);
  3591. if ((unsigned)(c1 - 'a') <= 25) { // a-z?
  3592. c1 = (c1 - 'a');
  3593. // get the b-o-l
  3594. q = mark[c1];
  3595. if (text <= q && q < end) {
  3596. dot = q;
  3597. dot_begin(); // go to B-o-l
  3598. dot_skip_over_ws();
  3599. } else {
  3600. indicate_error();
  3601. }
  3602. } else if (c1 == '\'') { // goto previous context
  3603. dot = swap_context(dot); // swap current and previous context
  3604. dot_begin(); // go to B-o-l
  3605. dot_skip_over_ws();
  3606. #if ENABLE_FEATURE_VI_YANKMARK
  3607. orig_dot = dot; // this doesn't update stored contexts
  3608. #endif
  3609. } else {
  3610. indicate_error();
  3611. }
  3612. break;
  3613. case 'm': // m- Mark a line
  3614. // this is really stupid. If there are any inserts or deletes
  3615. // between text[0] and dot then this mark will not point to the
  3616. // correct location! It could be off by many lines!
  3617. // Well..., at least its quick and dirty.
  3618. c1 = (get_one_char() | 0x20) - 'a';
  3619. if ((unsigned)c1 <= 25) { // a-z?
  3620. // remember the line
  3621. mark[c1] = dot;
  3622. } else {
  3623. indicate_error();
  3624. }
  3625. break;
  3626. case 'P': // P- Put register before
  3627. case 'p': // p- put register after
  3628. p = reg[YDreg];
  3629. if (p == NULL) {
  3630. status_line_bold("Nothing in register %c", what_reg());
  3631. break;
  3632. }
  3633. cnt = 0;
  3634. i = cmdcnt ?: 1;
  3635. // are we putting whole lines or strings
  3636. if (regtype[YDreg] == WHOLE) {
  3637. if (c == 'P') {
  3638. dot_begin(); // putting lines- Put above
  3639. }
  3640. else /* if ( c == 'p') */ {
  3641. // are we putting after very last line?
  3642. if (end_line(dot) == (end - 1)) {
  3643. dot = end; // force dot to end of text[]
  3644. } else {
  3645. dot_next(); // next line, then put before
  3646. }
  3647. }
  3648. } else {
  3649. if (c == 'p')
  3650. dot_right(); // move to right, can move to NL
  3651. // how far to move cursor if register doesn't have a NL
  3652. if (strchr(p, '\n') == NULL)
  3653. cnt = i * strlen(p) - 1;
  3654. }
  3655. do {
  3656. // dot is adjusted if text[] is reallocated so we don't have to
  3657. string_insert(dot, p, allow_undo); // insert the string
  3658. # if ENABLE_FEATURE_VI_UNDO
  3659. allow_undo = ALLOW_UNDO_CHAIN;
  3660. # endif
  3661. } while (--cmdcnt > 0);
  3662. dot += cnt;
  3663. dot_skip_over_ws();
  3664. # if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
  3665. yank_status("Put", p, i);
  3666. # endif
  3667. end_cmd_q(); // stop adding to q
  3668. break;
  3669. case 'U': // U- Undo; replace current line with original version
  3670. if (reg[Ureg] != NULL) {
  3671. p = begin_line(dot);
  3672. q = end_line(dot);
  3673. p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
  3674. p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
  3675. dot = p;
  3676. dot_skip_over_ws();
  3677. # if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
  3678. yank_status("Undo", reg[Ureg], 1);
  3679. # endif
  3680. }
  3681. break;
  3682. #endif /* FEATURE_VI_YANKMARK */
  3683. #if ENABLE_FEATURE_VI_UNDO
  3684. case 'u': // u- undo last operation
  3685. undo_pop();
  3686. break;
  3687. #endif
  3688. case '$': // $- goto end of line
  3689. case KEYCODE_END: // Cursor Key End
  3690. for (;;) {
  3691. dot = end_line(dot);
  3692. if (--cmdcnt <= 0)
  3693. break;
  3694. dot_next();
  3695. }
  3696. cindex = C_END;
  3697. keep_index = TRUE;
  3698. break;
  3699. case '%': // %- find matching char of pair () [] {}
  3700. for (q = dot; q < end && *q != '\n'; q++) {
  3701. if (strchr("()[]{}", *q) != NULL) {
  3702. // we found half of a pair
  3703. p = find_pair(q, *q);
  3704. if (p == NULL) {
  3705. indicate_error();
  3706. } else {
  3707. dot = p;
  3708. }
  3709. break;
  3710. }
  3711. }
  3712. if (*q == '\n')
  3713. indicate_error();
  3714. break;
  3715. case 'f': // f- forward to a user specified char
  3716. case 'F': // F- backward to a user specified char
  3717. case 't': // t- move to char prior to next x
  3718. case 'T': // T- move to char after previous x
  3719. last_search_char = get_one_char(); // get the search char
  3720. last_search_cmd = c;
  3721. // fall through
  3722. case ';': // ;- look at rest of line for last search char
  3723. case ',': // ,- repeat latest search in opposite direction
  3724. dot_to_char(c != ',' ? last_search_cmd : last_search_cmd ^ 0x20);
  3725. break;
  3726. #if ENABLE_FEATURE_VI_DOT_CMD
  3727. case '.': // .- repeat the last modifying command
  3728. // Stuff the last_modifying_cmd back into stdin
  3729. // and let it be re-executed.
  3730. if (lmc_len != 0) {
  3731. if (cmdcnt) // update saved count if current count is non-zero
  3732. dotcnt = cmdcnt;
  3733. last_modifying_cmd[lmc_len] = '\0';
  3734. ioq = ioq_start = xasprintf("%u%s", dotcnt, last_modifying_cmd);
  3735. }
  3736. break;
  3737. #endif
  3738. #if ENABLE_FEATURE_VI_SEARCH
  3739. case 'N': // N- backward search for last pattern
  3740. dir = last_search_pattern[0] == '/' ? BACK : FORWARD;
  3741. goto dc4; // now search for pattern
  3742. break;
  3743. case '?': // ?- backward search for a pattern
  3744. case '/': // /- forward search for a pattern
  3745. buf[0] = c;
  3746. buf[1] = '\0';
  3747. q = get_input_line(buf); // get input line- use "status line"
  3748. if (!q[0]) // user changed mind and erased the "/"- do nothing
  3749. break;
  3750. if (!q[1]) { // if no pat re-use old pat
  3751. if (last_search_pattern[0])
  3752. last_search_pattern[0] = c;
  3753. } else { // strlen(q) > 1: new pat- save it and find
  3754. free(last_search_pattern);
  3755. last_search_pattern = xstrdup(q);
  3756. }
  3757. // fall through
  3758. case 'n': // n- repeat search for last pattern
  3759. // search rest of text[] starting at next char
  3760. // if search fails "dot" is unchanged
  3761. dir = last_search_pattern[0] == '/' ? FORWARD : BACK;
  3762. dc4:
  3763. if (last_search_pattern[1] == '\0') {
  3764. status_line_bold("No previous search");
  3765. break;
  3766. }
  3767. do {
  3768. q = char_search(dot + dir, last_search_pattern + 1,
  3769. (dir << 1) | FULL);
  3770. if (q != NULL) {
  3771. dot = q; // good search, update "dot"
  3772. } else {
  3773. // no pattern found between "dot" and top/bottom of file
  3774. // continue from other end of file
  3775. const char *msg;
  3776. q = char_search(dir == FORWARD ? text : end - 1,
  3777. last_search_pattern + 1, (dir << 1) | FULL);
  3778. if (q != NULL) { // found something
  3779. dot = q; // found new pattern- goto it
  3780. msg = "search hit %s, continuing at %s";
  3781. } else { // pattern is nowhere in file
  3782. cmdcnt = 0; // force exit from loop
  3783. msg = "Pattern not found";
  3784. }
  3785. if (dir == FORWARD)
  3786. status_line_bold(msg, "BOTTOM", "TOP");
  3787. else
  3788. status_line_bold(msg, "TOP", "BOTTOM");
  3789. }
  3790. } while (--cmdcnt > 0);
  3791. break;
  3792. case '{': // {- move backward paragraph
  3793. case '}': // }- move forward paragraph
  3794. dir = c == '}' ? FORWARD : BACK;
  3795. do {
  3796. int skip = TRUE; // initially skip consecutive empty lines
  3797. while (dir == FORWARD ? dot < end - 1 : dot > text) {
  3798. if (*dot == '\n' && dot[dir] == '\n') {
  3799. if (!skip) {
  3800. if (dir == FORWARD)
  3801. ++dot; // move to next blank line
  3802. goto dc2;
  3803. }
  3804. }
  3805. else {
  3806. skip = FALSE;
  3807. }
  3808. dot += dir;
  3809. }
  3810. goto dc6; // end of file
  3811. dc2: continue;
  3812. } while (--cmdcnt > 0);
  3813. break;
  3814. #endif /* FEATURE_VI_SEARCH */
  3815. case '0': // 0- goto beginning of line
  3816. case '1': // 1-
  3817. case '2': // 2-
  3818. case '3': // 3-
  3819. case '4': // 4-
  3820. case '5': // 5-
  3821. case '6': // 6-
  3822. case '7': // 7-
  3823. case '8': // 8-
  3824. case '9': // 9-
  3825. if (c == '0' && cmdcnt < 1) {
  3826. dot_begin(); // this was a standalone zero
  3827. } else {
  3828. cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
  3829. }
  3830. break;
  3831. case ':': // :- the colon mode commands
  3832. p = get_input_line(":"); // get input line- use "status line"
  3833. colon(p); // execute the command
  3834. break;
  3835. case '<': // <- Left shift something
  3836. case '>': // >- Right shift something
  3837. cnt = count_lines(text, dot); // remember what line we are on
  3838. if (find_range(&p, &q, c) == -1)
  3839. goto dc6;
  3840. i = count_lines(p, q); // # of lines we are shifting
  3841. for (p = begin_line(p); i > 0; i--, p = next_line(p)) {
  3842. if (c == '<') {
  3843. // shift left- remove tab or tabstop spaces
  3844. if (*p == '\t') {
  3845. // shrink buffer 1 char
  3846. text_hole_delete(p, p, allow_undo);
  3847. } else if (*p == ' ') {
  3848. // we should be calculating columns, not just SPACE
  3849. for (j = 0; *p == ' ' && j < tabstop; j++) {
  3850. text_hole_delete(p, p, allow_undo);
  3851. #if ENABLE_FEATURE_VI_UNDO
  3852. allow_undo = ALLOW_UNDO_CHAIN;
  3853. #endif
  3854. }
  3855. }
  3856. } else if (/* c == '>' && */ p != end_line(p)) {
  3857. // shift right -- add tab or tabstop spaces on non-empty lines
  3858. char_insert(p, '\t', allow_undo);
  3859. }
  3860. #if ENABLE_FEATURE_VI_UNDO
  3861. allow_undo = ALLOW_UNDO_CHAIN;
  3862. #endif
  3863. }
  3864. dot = find_line(cnt); // what line were we on
  3865. dot_skip_over_ws();
  3866. end_cmd_q(); // stop adding to q
  3867. break;
  3868. case 'A': // A- append at e-o-l
  3869. dot_end(); // go to e-o-l
  3870. //**** fall through to ... 'a'
  3871. case 'a': // a- append after current char
  3872. if (*dot != '\n')
  3873. dot++;
  3874. goto dc_i;
  3875. break;
  3876. case 'B': // B- back a blank-delimited Word
  3877. case 'E': // E- end of a blank-delimited word
  3878. case 'W': // W- forward a blank-delimited word
  3879. dir = FORWARD;
  3880. if (c == 'B')
  3881. dir = BACK;
  3882. do {
  3883. if (c == 'W' || isspace(dot[dir])) {
  3884. dot = skip_thing(dot, 1, dir, S_TO_WS);
  3885. dot = skip_thing(dot, 2, dir, S_OVER_WS);
  3886. }
  3887. if (c != 'W')
  3888. dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
  3889. } while (--cmdcnt > 0);
  3890. break;
  3891. case 'C': // C- Change to e-o-l
  3892. case 'D': // D- delete to e-o-l
  3893. save_dot = dot;
  3894. dot = dollar_line(dot); // move to before NL
  3895. // copy text into a register and delete
  3896. dot = yank_delete(save_dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO); // delete to e-o-l
  3897. if (c == 'C')
  3898. goto dc_i; // start inserting
  3899. #if ENABLE_FEATURE_VI_DOT_CMD
  3900. if (c == 'D')
  3901. end_cmd_q(); // stop adding to q
  3902. #endif
  3903. break;
  3904. case 'g': // 'gg' goto a line number (vim) (default: very first line)
  3905. c1 = get_one_char();
  3906. if (c1 != 'g') {
  3907. buf[0] = 'g';
  3908. // c1 < 0 if the key was special. Try "g<up-arrow>"
  3909. // TODO: if Unicode?
  3910. buf[1] = (c1 >= 0 ? c1 : '*');
  3911. buf[2] = '\0';
  3912. not_implemented(buf);
  3913. cmd_error = TRUE;
  3914. break;
  3915. }
  3916. if (cmdcnt == 0)
  3917. cmdcnt = 1;
  3918. // fall through
  3919. case 'G': // G- goto to a line number (default= E-O-F)
  3920. dot = end - 1; // assume E-O-F
  3921. if (cmdcnt > 0) {
  3922. dot = find_line(cmdcnt); // what line is #cmdcnt
  3923. }
  3924. dot_begin();
  3925. dot_skip_over_ws();
  3926. break;
  3927. case 'H': // H- goto top line on screen
  3928. dot = screenbegin;
  3929. if (cmdcnt > (rows - 1)) {
  3930. cmdcnt = (rows - 1);
  3931. }
  3932. while (--cmdcnt > 0) {
  3933. dot_next();
  3934. }
  3935. dot_begin();
  3936. dot_skip_over_ws();
  3937. break;
  3938. case 'I': // I- insert before first non-blank
  3939. dot_begin(); // 0
  3940. dot_skip_over_ws();
  3941. //**** fall through to ... 'i'
  3942. case 'i': // i- insert before current char
  3943. case KEYCODE_INSERT: // Cursor Key Insert
  3944. dc_i:
  3945. #if ENABLE_FEATURE_VI_SETOPTS
  3946. newindent = -1;
  3947. #endif
  3948. cmd_mode = 1; // start inserting
  3949. undo_queue_commit(); // commit queue when cmd_mode changes
  3950. break;
  3951. case 'J': // J- join current and next lines together
  3952. do {
  3953. dot_end(); // move to NL
  3954. if (dot < end - 1) { // make sure not last char in text[]
  3955. #if ENABLE_FEATURE_VI_UNDO
  3956. undo_push(dot, 1, UNDO_DEL);
  3957. *dot++ = ' '; // replace NL with space
  3958. undo_push((dot - 1), 1, UNDO_INS_CHAIN);
  3959. #else
  3960. *dot++ = ' ';
  3961. modified_count++;
  3962. #endif
  3963. while (isblank(*dot)) { // delete leading WS
  3964. text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
  3965. }
  3966. }
  3967. } while (--cmdcnt > 0);
  3968. end_cmd_q(); // stop adding to q
  3969. break;
  3970. case 'L': // L- goto bottom line on screen
  3971. dot = end_screen();
  3972. if (cmdcnt > (rows - 1)) {
  3973. cmdcnt = (rows - 1);
  3974. }
  3975. while (--cmdcnt > 0) {
  3976. dot_prev();
  3977. }
  3978. dot_begin();
  3979. dot_skip_over_ws();
  3980. break;
  3981. case 'M': // M- goto middle line on screen
  3982. dot = screenbegin;
  3983. for (cnt = 0; cnt < (rows-1) / 2; cnt++)
  3984. dot = next_line(dot);
  3985. dot_skip_over_ws();
  3986. break;
  3987. case 'O': // O- open an empty line above
  3988. dot_begin();
  3989. #if ENABLE_FEATURE_VI_SETOPTS
  3990. // special case: use indent of current line
  3991. newindent = get_column(dot + indent_len(dot));
  3992. #endif
  3993. goto dc3;
  3994. case 'o': // o- open an empty line below
  3995. dot_end();
  3996. dc3:
  3997. #if ENABLE_FEATURE_VI_SETOPTS
  3998. cmd_mode = 1; // switch to insert mode early
  3999. #endif
  4000. dot = char_insert(dot, '\n', ALLOW_UNDO);
  4001. if (c == 'O' && !autoindent) {
  4002. // done in char_insert() for 'O'+autoindent
  4003. dot_prev();
  4004. }
  4005. goto dc_i;
  4006. break;
  4007. case 'R': // R- continuous Replace char
  4008. dc5:
  4009. cmd_mode = 2;
  4010. undo_queue_commit();
  4011. rstart = dot;
  4012. break;
  4013. case KEYCODE_DELETE:
  4014. if (dot < end - 1)
  4015. dot = yank_delete(dot, dot, PARTIAL, YANKDEL, ALLOW_UNDO);
  4016. break;
  4017. case 'X': // X- delete char before dot
  4018. case 'x': // x- delete the current char
  4019. case 's': // s- substitute the current char
  4020. dir = 0;
  4021. if (c == 'X')
  4022. dir = -1;
  4023. do {
  4024. if (dot[dir] != '\n') {
  4025. if (c == 'X')
  4026. dot--; // delete prev char
  4027. dot = yank_delete(dot, dot, PARTIAL, YANKDEL, allow_undo); // delete char
  4028. #if ENABLE_FEATURE_VI_UNDO
  4029. allow_undo = ALLOW_UNDO_CHAIN;
  4030. #endif
  4031. }
  4032. } while (--cmdcnt > 0);
  4033. end_cmd_q(); // stop adding to q
  4034. if (c == 's')
  4035. goto dc_i; // start inserting
  4036. break;
  4037. case 'Z': // Z- if modified, {write}; exit
  4038. c1 = get_one_char();
  4039. // ZQ means to exit without saving
  4040. if (c1 == 'Q') {
  4041. editing = 0;
  4042. optind = cmdline_filecnt;
  4043. break;
  4044. }
  4045. // ZZ means to save file (if necessary), then exit
  4046. if (c1 != 'Z') {
  4047. indicate_error();
  4048. break;
  4049. }
  4050. if (modified_count) {
  4051. if (ENABLE_FEATURE_VI_READONLY && readonly_mode && current_filename) {
  4052. status_line_bold("'%s' is read only", current_filename);
  4053. break;
  4054. }
  4055. cnt = file_write(current_filename, text, end - 1);
  4056. if (cnt < 0) {
  4057. if (cnt == -1)
  4058. status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
  4059. } else if (cnt == (end - 1 - text + 1)) {
  4060. editing = 0;
  4061. }
  4062. } else {
  4063. editing = 0;
  4064. }
  4065. // are there other files to edit?
  4066. j = cmdline_filecnt - optind - 1;
  4067. if (editing == 0 && j > 0) {
  4068. editing = 1;
  4069. modified_count = 0;
  4070. last_modified_count = -1;
  4071. status_line_bold("%u more file(s) to edit", j);
  4072. }
  4073. break;
  4074. case '^': // ^- move to first non-blank on line
  4075. dot_begin();
  4076. dot_skip_over_ws();
  4077. break;
  4078. case 'b': // b- back a word
  4079. case 'e': // e- end of word
  4080. dir = FORWARD;
  4081. if (c == 'b')
  4082. dir = BACK;
  4083. do {
  4084. if ((dot + dir) < text || (dot + dir) > end - 1)
  4085. break;
  4086. dot += dir;
  4087. if (isspace(*dot)) {
  4088. dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
  4089. }
  4090. if (isalnum(*dot) || *dot == '_') {
  4091. dot = skip_thing(dot, 1, dir, S_END_ALNUM);
  4092. } else if (ispunct(*dot)) {
  4093. dot = skip_thing(dot, 1, dir, S_END_PUNCT);
  4094. }
  4095. } while (--cmdcnt > 0);
  4096. break;
  4097. case 'c': // c- change something
  4098. case 'd': // d- delete something
  4099. #if ENABLE_FEATURE_VI_YANKMARK
  4100. case 'y': // y- yank something
  4101. case 'Y': // Y- Yank a line
  4102. #endif
  4103. {
  4104. int yf = YANKDEL; // assume either "c" or "d"
  4105. int buftype;
  4106. #if ENABLE_FEATURE_VI_YANKMARK
  4107. # if ENABLE_FEATURE_VI_VERBOSE_STATUS
  4108. char *savereg = reg[YDreg];
  4109. # endif
  4110. if (c == 'y' || c == 'Y')
  4111. yf = YANKONLY;
  4112. #endif
  4113. // determine range, and whether it spans lines
  4114. buftype = find_range(&p, &q, c);
  4115. if (buftype == -1) // invalid range
  4116. goto dc6;
  4117. if (buftype == WHOLE) {
  4118. save_dot = p; // final cursor position is start of range
  4119. p = begin_line(p);
  4120. #if ENABLE_FEATURE_VI_SETOPTS
  4121. if (c == 'c') // special case: use indent of current line
  4122. newindent = get_column(p + indent_len(p));
  4123. #endif
  4124. q = end_line(q);
  4125. }
  4126. dot = yank_delete(p, q, buftype, yf, ALLOW_UNDO); // delete word
  4127. if (buftype == WHOLE) {
  4128. if (c == 'c') {
  4129. #if ENABLE_FEATURE_VI_SETOPTS
  4130. cmd_mode = 1; // switch to insert mode early
  4131. #endif
  4132. dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
  4133. // on the last line of file don't move to prev line,
  4134. // handled in char_insert() if autoindent is enabled
  4135. if (dot != (end-1) && !autoindent) {
  4136. dot_prev();
  4137. }
  4138. } else if (c == 'd') {
  4139. dot_begin();
  4140. dot_skip_over_ws();
  4141. } else {
  4142. dot = save_dot;
  4143. }
  4144. }
  4145. // if CHANGING, not deleting, start inserting after the delete
  4146. if (c == 'c') {
  4147. goto dc_i; // start inserting
  4148. }
  4149. #if ENABLE_FEATURE_VI_YANKMARK && ENABLE_FEATURE_VI_VERBOSE_STATUS
  4150. // only update status if a yank has actually happened
  4151. if (reg[YDreg] != savereg)
  4152. yank_status(c == 'd' ? "Delete" : "Yank", reg[YDreg], 1);
  4153. #endif
  4154. dc6:
  4155. end_cmd_q(); // stop adding to q
  4156. break;
  4157. }
  4158. case 'k': // k- goto prev line, same col
  4159. case KEYCODE_UP: // cursor key Up
  4160. case '-': // -- goto prev line
  4161. q = dot;
  4162. do {
  4163. p = prev_line(q);
  4164. if (p == begin_line(q)) {
  4165. indicate_error();
  4166. goto dc1;
  4167. }
  4168. q = p;
  4169. } while (--cmdcnt > 0);
  4170. dot = q;
  4171. if (c == '-') {
  4172. dot_skip_over_ws();
  4173. } else {
  4174. // try to stay in saved column
  4175. dot = cindex == C_END ? end_line(dot) : move_to_col(dot, cindex);
  4176. keep_index = TRUE;
  4177. }
  4178. break;
  4179. case 'r': // r- replace the current char with user input
  4180. c1 = get_one_char(); // get the replacement char
  4181. if (c1 != 27) {
  4182. if (end_line(dot) - dot < (cmdcnt ?: 1)) {
  4183. indicate_error();
  4184. goto dc6;
  4185. }
  4186. do {
  4187. dot = text_hole_delete(dot, dot, allow_undo);
  4188. #if ENABLE_FEATURE_VI_UNDO
  4189. allow_undo = ALLOW_UNDO_CHAIN;
  4190. #endif
  4191. dot = char_insert(dot, c1, allow_undo);
  4192. } while (--cmdcnt > 0);
  4193. dot_left();
  4194. }
  4195. end_cmd_q(); // stop adding to q
  4196. break;
  4197. case 'w': // w- forward a word
  4198. do {
  4199. if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
  4200. dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
  4201. } else if (ispunct(*dot)) { // we are on PUNCT
  4202. dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
  4203. }
  4204. if (dot < end - 1)
  4205. dot++; // move over word
  4206. if (isspace(*dot)) {
  4207. dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
  4208. }
  4209. } while (--cmdcnt > 0);
  4210. break;
  4211. case 'z': // z-
  4212. c1 = get_one_char(); // get the replacement char
  4213. cnt = 0;
  4214. if (c1 == '.')
  4215. cnt = (rows - 2) / 2; // put dot at center
  4216. if (c1 == '-')
  4217. cnt = rows - 2; // put dot at bottom
  4218. screenbegin = begin_line(dot); // start dot at top
  4219. dot_scroll(cnt, -1);
  4220. break;
  4221. case '|': // |- move to column "cmdcnt"
  4222. dot = move_to_col(dot, cmdcnt - 1); // try to move to column
  4223. break;
  4224. case '~': // ~- flip the case of letters a-z -> A-Z
  4225. do {
  4226. #if ENABLE_FEATURE_VI_UNDO
  4227. if (isalpha(*dot)) {
  4228. undo_push(dot, 1, undo_del);
  4229. *dot = islower(*dot) ? toupper(*dot) : tolower(*dot);
  4230. undo_push(dot, 1, UNDO_INS_CHAIN);
  4231. undo_del = UNDO_DEL_CHAIN;
  4232. }
  4233. #else
  4234. if (islower(*dot)) {
  4235. *dot = toupper(*dot);
  4236. modified_count++;
  4237. } else if (isupper(*dot)) {
  4238. *dot = tolower(*dot);
  4239. modified_count++;
  4240. }
  4241. #endif
  4242. dot_right();
  4243. } while (--cmdcnt > 0);
  4244. end_cmd_q(); // stop adding to q
  4245. break;
  4246. //----- The Cursor and Function Keys -----------------------------
  4247. case KEYCODE_HOME: // Cursor Key Home
  4248. dot_begin();
  4249. break;
  4250. // The Fn keys could point to do_macro which could translate them
  4251. #if 0
  4252. case KEYCODE_FUN1: // Function Key F1
  4253. case KEYCODE_FUN2: // Function Key F2
  4254. case KEYCODE_FUN3: // Function Key F3
  4255. case KEYCODE_FUN4: // Function Key F4
  4256. case KEYCODE_FUN5: // Function Key F5
  4257. case KEYCODE_FUN6: // Function Key F6
  4258. case KEYCODE_FUN7: // Function Key F7
  4259. case KEYCODE_FUN8: // Function Key F8
  4260. case KEYCODE_FUN9: // Function Key F9
  4261. case KEYCODE_FUN10: // Function Key F10
  4262. case KEYCODE_FUN11: // Function Key F11
  4263. case KEYCODE_FUN12: // Function Key F12
  4264. break;
  4265. #endif
  4266. }
  4267. dc1:
  4268. // if text[] just became empty, add back an empty line
  4269. if (end == text) {
  4270. char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
  4271. dot = text;
  4272. }
  4273. // it is OK for dot to exactly equal to end, otherwise check dot validity
  4274. if (dot != end) {
  4275. dot = bound_dot(dot); // make sure "dot" is valid
  4276. }
  4277. #if ENABLE_FEATURE_VI_YANKMARK
  4278. if (dot != orig_dot)
  4279. check_context(c); // update the current context
  4280. #endif
  4281. if (!isdigit(c))
  4282. cmdcnt = 0; // cmd was not a number, reset cmdcnt
  4283. cnt = dot - begin_line(dot);
  4284. // Try to stay off of the Newline
  4285. if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
  4286. dot--;
  4287. }
  4288. // NB! the CRASHME code is unmaintained, and doesn't currently build
  4289. #if ENABLE_FEATURE_VI_CRASHME
  4290. static int totalcmds = 0;
  4291. static int Mp = 85; // Movement command Probability
  4292. static int Np = 90; // Non-movement command Probability
  4293. static int Dp = 96; // Delete command Probability
  4294. static int Ip = 97; // Insert command Probability
  4295. static int Yp = 98; // Yank command Probability
  4296. static int Pp = 99; // Put command Probability
  4297. static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
  4298. static const char chars[20] = "\t012345 abcdABCD-=.$";
  4299. static const char *const words[20] = {
  4300. "this", "is", "a", "test",
  4301. "broadcast", "the", "emergency", "of",
  4302. "system", "quick", "brown", "fox",
  4303. "jumped", "over", "lazy", "dogs",
  4304. "back", "January", "Febuary", "March"
  4305. };
  4306. static const char *const lines[20] = {
  4307. "You should have received a copy of the GNU General Public License\n",
  4308. "char c, cm, *cmd, *cmd1;\n",
  4309. "generate a command by percentages\n",
  4310. "Numbers may be typed as a prefix to some commands.\n",
  4311. "Quit, discarding changes!\n",
  4312. "Forced write, if permission originally not valid.\n",
  4313. "In general, any ex or ed command (such as substitute or delete).\n",
  4314. "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
  4315. "Please get w/ me and I will go over it with you.\n",
  4316. "The following is a list of scheduled, committed changes.\n",
  4317. "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
  4318. "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
  4319. "Any question about transactions please contact Sterling Huxley.\n",
  4320. "I will try to get back to you by Friday, December 31.\n",
  4321. "This Change will be implemented on Friday.\n",
  4322. "Let me know if you have problems accessing this;\n",
  4323. "Sterling Huxley recently added you to the access list.\n",
  4324. "Would you like to go to lunch?\n",
  4325. "The last command will be automatically run.\n",
  4326. "This is too much english for a computer geek.\n",
  4327. };
  4328. static char *multilines[20] = {
  4329. "You should have received a copy of the GNU General Public License\n",
  4330. "char c, cm, *cmd, *cmd1;\n",
  4331. "generate a command by percentages\n",
  4332. "Numbers may be typed as a prefix to some commands.\n",
  4333. "Quit, discarding changes!\n",
  4334. "Forced write, if permission originally not valid.\n",
  4335. "In general, any ex or ed command (such as substitute or delete).\n",
  4336. "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
  4337. "Please get w/ me and I will go over it with you.\n",
  4338. "The following is a list of scheduled, committed changes.\n",
  4339. "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
  4340. "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
  4341. "Any question about transactions please contact Sterling Huxley.\n",
  4342. "I will try to get back to you by Friday, December 31.\n",
  4343. "This Change will be implemented on Friday.\n",
  4344. "Let me know if you have problems accessing this;\n",
  4345. "Sterling Huxley recently added you to the access list.\n",
  4346. "Would you like to go to lunch?\n",
  4347. "The last command will be automatically run.\n",
  4348. "This is too much english for a computer geek.\n",
  4349. };
  4350. // create a random command to execute
  4351. static void crash_dummy()
  4352. {
  4353. static int sleeptime; // how long to pause between commands
  4354. char c, cm, *cmd, *cmd1;
  4355. int i, cnt, thing, rbi, startrbi, percent;
  4356. // "dot" movement commands
  4357. cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
  4358. // is there already a command running?
  4359. if (readbuffer[0] > 0)
  4360. goto cd1;
  4361. cd0:
  4362. readbuffer[0] = 'X';
  4363. startrbi = rbi = 1;
  4364. sleeptime = 0; // how long to pause between commands
  4365. memset(readbuffer, '\0', sizeof(readbuffer));
  4366. // generate a command by percentages
  4367. percent = (int) lrand48() % 100; // get a number from 0-99
  4368. if (percent < Mp) { // Movement commands
  4369. // available commands
  4370. cmd = cmd1;
  4371. M++;
  4372. } else if (percent < Np) { // non-movement commands
  4373. cmd = "mz<>\'\""; // available commands
  4374. N++;
  4375. } else if (percent < Dp) { // Delete commands
  4376. cmd = "dx"; // available commands
  4377. D++;
  4378. } else if (percent < Ip) { // Inset commands
  4379. cmd = "iIaAsrJ"; // available commands
  4380. I++;
  4381. } else if (percent < Yp) { // Yank commands
  4382. cmd = "yY"; // available commands
  4383. Y++;
  4384. } else if (percent < Pp) { // Put commands
  4385. cmd = "pP"; // available commands
  4386. P++;
  4387. } else {
  4388. // We do not know how to handle this command, try again
  4389. U++;
  4390. goto cd0;
  4391. }
  4392. // randomly pick one of the available cmds from "cmd[]"
  4393. i = (int) lrand48() % strlen(cmd);
  4394. cm = cmd[i];
  4395. if (strchr(":\024", cm))
  4396. goto cd0; // dont allow colon or ctrl-T commands
  4397. readbuffer[rbi++] = cm; // put cmd into input buffer
  4398. // now we have the command-
  4399. // there are 1, 2, and multi char commands
  4400. // find out which and generate the rest of command as necessary
  4401. if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
  4402. cmd1 = " \n\r0$^-+wWeEbBhjklHL";
  4403. if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
  4404. cmd1 = "abcdefghijklmnopqrstuvwxyz";
  4405. }
  4406. thing = (int) lrand48() % strlen(cmd1); // pick a movement command
  4407. c = cmd1[thing];
  4408. readbuffer[rbi++] = c; // add movement to input buffer
  4409. }
  4410. if (strchr("iIaAsc", cm)) { // multi-char commands
  4411. if (cm == 'c') {
  4412. // change some thing
  4413. thing = (int) lrand48() % strlen(cmd1); // pick a movement command
  4414. c = cmd1[thing];
  4415. readbuffer[rbi++] = c; // add movement to input buffer
  4416. }
  4417. thing = (int) lrand48() % 4; // what thing to insert
  4418. cnt = (int) lrand48() % 10; // how many to insert
  4419. for (i = 0; i < cnt; i++) {
  4420. if (thing == 0) { // insert chars
  4421. readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
  4422. } else if (thing == 1) { // insert words
  4423. strcat(readbuffer, words[(int) lrand48() % 20]);
  4424. strcat(readbuffer, " ");
  4425. sleeptime = 0; // how fast to type
  4426. } else if (thing == 2) { // insert lines
  4427. strcat(readbuffer, lines[(int) lrand48() % 20]);
  4428. sleeptime = 0; // how fast to type
  4429. } else { // insert multi-lines
  4430. strcat(readbuffer, multilines[(int) lrand48() % 20]);
  4431. sleeptime = 0; // how fast to type
  4432. }
  4433. }
  4434. strcat(readbuffer, ESC);
  4435. }
  4436. readbuffer[0] = strlen(readbuffer + 1);
  4437. cd1:
  4438. totalcmds++;
  4439. if (sleeptime > 0)
  4440. mysleep(sleeptime); // sleep 1/100 sec
  4441. }
  4442. // test to see if there are any errors
  4443. static void crash_test()
  4444. {
  4445. static time_t oldtim;
  4446. time_t tim;
  4447. char d[2], msg[80];
  4448. msg[0] = '\0';
  4449. if (end < text) {
  4450. strcat(msg, "end<text ");
  4451. }
  4452. if (end > textend) {
  4453. strcat(msg, "end>textend ");
  4454. }
  4455. if (dot < text) {
  4456. strcat(msg, "dot<text ");
  4457. }
  4458. if (dot > end) {
  4459. strcat(msg, "dot>end ");
  4460. }
  4461. if (screenbegin < text) {
  4462. strcat(msg, "screenbegin<text ");
  4463. }
  4464. if (screenbegin > end - 1) {
  4465. strcat(msg, "screenbegin>end-1 ");
  4466. }
  4467. if (msg[0]) {
  4468. printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
  4469. totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
  4470. fflush_all();
  4471. while (safe_read(STDIN_FILENO, d, 1) > 0) {
  4472. if (d[0] == '\n' || d[0] == '\r')
  4473. break;
  4474. }
  4475. }
  4476. tim = time(NULL);
  4477. if (tim >= (oldtim + 3)) {
  4478. sprintf(status_buffer,
  4479. "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
  4480. totalcmds, M, N, I, D, Y, P, U, end - text + 1);
  4481. oldtim = tim;
  4482. }
  4483. }
  4484. #endif
  4485. #if ENABLE_FEATURE_VI_COLON
  4486. static void run_cmds(char *p)
  4487. {
  4488. while (p) {
  4489. char *q = p;
  4490. p = strchr(q, '\n');
  4491. if (p)
  4492. while (*p == '\n')
  4493. *p++ = '\0';
  4494. if (strlen(q) < MAX_INPUT_LEN)
  4495. colon(q);
  4496. }
  4497. }
  4498. #endif
  4499. static void edit_file(char *fn)
  4500. {
  4501. #if ENABLE_FEATURE_VI_YANKMARK
  4502. #define cur_line edit_file__cur_line
  4503. #endif
  4504. int c;
  4505. #if ENABLE_FEATURE_VI_USE_SIGNALS
  4506. int sig;
  4507. #endif
  4508. editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
  4509. rawmode();
  4510. rows = 24;
  4511. columns = 80;
  4512. IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
  4513. #if ENABLE_FEATURE_VI_ASK_TERMINAL
  4514. if (G.get_rowcol_error /* TODO? && no input on stdin */) {
  4515. uint64_t k;
  4516. write1(ESC"[999;999H" ESC"[6n");
  4517. fflush_all();
  4518. k = safe_read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
  4519. if ((int32_t)k == KEYCODE_CURSOR_POS) {
  4520. uint32_t rc = (k >> 32);
  4521. columns = (rc & 0x7fff);
  4522. if (columns > MAX_SCR_COLS)
  4523. columns = MAX_SCR_COLS;
  4524. rows = ((rc >> 16) & 0x7fff);
  4525. if (rows > MAX_SCR_ROWS)
  4526. rows = MAX_SCR_ROWS;
  4527. }
  4528. }
  4529. #endif
  4530. new_screen(rows, columns); // get memory for virtual screen
  4531. init_text_buffer(fn);
  4532. #if ENABLE_FEATURE_VI_YANKMARK
  4533. YDreg = 26; // default Yank/Delete reg
  4534. // Ureg = 27; - const // hold orig line for "U" cmd
  4535. mark[26] = mark[27] = text; // init "previous context"
  4536. #endif
  4537. #if ENABLE_FEATURE_VI_CRASHME
  4538. last_input_char = '\0';
  4539. #endif
  4540. crow = 0;
  4541. ccol = 0;
  4542. #if ENABLE_FEATURE_VI_USE_SIGNALS
  4543. signal(SIGWINCH, winch_handler);
  4544. signal(SIGTSTP, tstp_handler);
  4545. sig = sigsetjmp(restart, 1);
  4546. if (sig != 0) {
  4547. screenbegin = dot = text;
  4548. }
  4549. // int_handler() can jump to "restart",
  4550. // must install handler *after* initializing "restart"
  4551. signal(SIGINT, int_handler);
  4552. #endif
  4553. cmd_mode = 0; // 0=command 1=insert 2='R'eplace
  4554. cmdcnt = 0;
  4555. offset = 0; // no horizontal offset
  4556. c = '\0';
  4557. #if ENABLE_FEATURE_VI_DOT_CMD
  4558. free(ioq_start);
  4559. ioq_start = NULL;
  4560. adding2q = 0;
  4561. #endif
  4562. #if ENABLE_FEATURE_VI_COLON
  4563. while (initial_cmds)
  4564. run_cmds((char *)llist_pop(&initial_cmds));
  4565. #endif
  4566. redraw(FALSE); // dont force every col re-draw
  4567. //------This is the main Vi cmd handling loop -----------------------
  4568. while (editing > 0) {
  4569. #if ENABLE_FEATURE_VI_CRASHME
  4570. if (crashme > 0) {
  4571. if ((end - text) > 1) {
  4572. crash_dummy(); // generate a random command
  4573. } else {
  4574. crashme = 0;
  4575. string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO);
  4576. dot = text;
  4577. refresh(FALSE);
  4578. }
  4579. }
  4580. #endif
  4581. c = get_one_char(); // get a cmd from user
  4582. #if ENABLE_FEATURE_VI_CRASHME
  4583. last_input_char = c;
  4584. #endif
  4585. #if ENABLE_FEATURE_VI_YANKMARK
  4586. // save a copy of the current line- for the 'U" command
  4587. if (begin_line(dot) != cur_line) {
  4588. cur_line = begin_line(dot);
  4589. text_yank(begin_line(dot), end_line(dot), Ureg, PARTIAL);
  4590. }
  4591. #endif
  4592. #if ENABLE_FEATURE_VI_DOT_CMD
  4593. // If c is a command that changes text[],
  4594. // (re)start remembering the input for the "." command.
  4595. if (!adding2q
  4596. && ioq_start == NULL
  4597. && cmd_mode == 0 // command mode
  4598. && c > '\0' // exclude NUL and non-ASCII chars
  4599. && c < 0x7f // (Unicode and such)
  4600. && strchr(modifying_cmds, c)
  4601. ) {
  4602. start_new_cmd_q(c);
  4603. }
  4604. #endif
  4605. do_cmd(c); // execute the user command
  4606. // poll to see if there is input already waiting. if we are
  4607. // not able to display output fast enough to keep up, skip
  4608. // the display update until we catch up with input.
  4609. if (!readbuffer[0] && mysleep(0) == 0) {
  4610. // no input pending - so update output
  4611. refresh(FALSE);
  4612. show_status_line();
  4613. }
  4614. #if ENABLE_FEATURE_VI_CRASHME
  4615. if (crashme > 0)
  4616. crash_test(); // test editor variables
  4617. #endif
  4618. }
  4619. //-------------------------------------------------------------------
  4620. go_bottom_and_clear_to_eol();
  4621. cookmode();
  4622. #undef cur_line
  4623. }
  4624. #define VI_OPTSTR \
  4625. IF_FEATURE_VI_CRASHME("C") \
  4626. IF_FEATURE_VI_COLON("c:*") \
  4627. "Hh" \
  4628. IF_FEATURE_VI_READONLY("R")
  4629. enum {
  4630. IF_FEATURE_VI_CRASHME(OPTBIT_C,)
  4631. IF_FEATURE_VI_COLON(OPTBIT_c,)
  4632. OPTBIT_H,
  4633. OPTBIT_h,
  4634. IF_FEATURE_VI_READONLY(OPTBIT_R,)
  4635. OPT_C = IF_FEATURE_VI_CRASHME( (1 << OPTBIT_C)) + 0,
  4636. OPT_c = IF_FEATURE_VI_COLON( (1 << OPTBIT_c)) + 0,
  4637. OPT_H = 1 << OPTBIT_H,
  4638. OPT_h = 1 << OPTBIT_h,
  4639. OPT_R = IF_FEATURE_VI_READONLY( (1 << OPTBIT_R)) + 0,
  4640. };
  4641. int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  4642. int vi_main(int argc, char **argv)
  4643. {
  4644. int opts;
  4645. INIT_G();
  4646. #if ENABLE_FEATURE_VI_UNDO
  4647. //undo_stack_tail = NULL; - already is
  4648. # if ENABLE_FEATURE_VI_UNDO_QUEUE
  4649. undo_queue_state = UNDO_EMPTY;
  4650. //undo_q = 0; - already is
  4651. # endif
  4652. #endif
  4653. #if ENABLE_FEATURE_VI_CRASHME
  4654. srand((long) getpid());
  4655. #endif
  4656. #ifdef NO_SUCH_APPLET_YET
  4657. // if we aren't "vi", we are "view"
  4658. if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
  4659. SET_READONLY_MODE(readonly_mode);
  4660. }
  4661. #endif
  4662. // 0: all of our options are disabled by default in vim
  4663. //vi_setops = 0;
  4664. opts = getopt32(argv, VI_OPTSTR IF_FEATURE_VI_COLON(, &initial_cmds));
  4665. #if ENABLE_FEATURE_VI_CRASHME
  4666. if (opts & OPT_C)
  4667. crashme = 1;
  4668. #endif
  4669. if (opts & OPT_R)
  4670. SET_READONLY_MODE(readonly_mode);
  4671. if (opts & OPT_H)
  4672. show_help();
  4673. if (opts & (OPT_H | OPT_h)) {
  4674. bb_show_usage();
  4675. return 1;
  4676. }
  4677. argv += optind;
  4678. cmdline_filecnt = argc - optind;
  4679. // 1- process EXINIT variable from environment
  4680. // 2- if EXINIT is unset process $HOME/.exrc file
  4681. // 3- process command line args
  4682. #if ENABLE_FEATURE_VI_COLON
  4683. {
  4684. const char *exinit = getenv("EXINIT");
  4685. char *cmds = NULL;
  4686. if (exinit) {
  4687. cmds = xstrdup(exinit);
  4688. } else {
  4689. const char *home = getenv("HOME");
  4690. if (home && *home) {
  4691. char *exrc = concat_path_file(home, ".exrc");
  4692. struct stat st;
  4693. // .exrc must belong to and only be writable by user
  4694. if (stat(exrc, &st) == 0) {
  4695. if ((st.st_mode & (S_IWGRP|S_IWOTH)) == 0
  4696. && st.st_uid == getuid()
  4697. ) {
  4698. cmds = xmalloc_open_read_close(exrc, NULL);
  4699. } else {
  4700. status_line_bold(".exrc: permission denied");
  4701. }
  4702. }
  4703. free(exrc);
  4704. }
  4705. }
  4706. if (cmds) {
  4707. init_text_buffer(NULL);
  4708. run_cmds(cmds);
  4709. free(cmds);
  4710. }
  4711. }
  4712. #endif
  4713. // "Save cursor, use alternate screen buffer, clear screen"
  4714. write1(ESC"[?1049h");
  4715. // This is the main file handling loop
  4716. optind = 0;
  4717. while (1) {
  4718. edit_file(argv[optind]); // might be NULL on 1st iteration
  4719. // NB: optind can be changed by ":next" and ":rewind" commands
  4720. optind++;
  4721. if (optind >= cmdline_filecnt)
  4722. break;
  4723. }
  4724. // "Use normal screen buffer, restore cursor"
  4725. write1(ESC"[?1049l");
  4726. return 0;
  4727. }