vi.c 121 KB

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