vi.c 106 KB

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