vi.c 118 KB

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