bim.c 90 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922
  1. /* vim: tabstop=4 shiftwidth=4 noexpandtab
  2. * This file is part of ToaruOS and is released under the terms
  3. * of the NCSA / University of Illinois License - see LICENSE.md
  4. * Copyright (C) 2012-2018 K. Lange
  5. *
  6. * Alternatively, this source file is also released under the
  7. * following terms:
  8. *
  9. * Permission to use, copy, modify, and/or distribute this software for any
  10. * purpose with or without fee is hereby granted, provided that the above
  11. * copyright notice and this permission notice appear in all copies.
  12. *
  13. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  14. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  15. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  16. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  17. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  18. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  19. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  20. *
  21. * bim - Text editor
  22. *
  23. * Bim is inspired by vim, and its name is short for "Bad IMitation".
  24. *
  25. * Bim supports syntax highlighting, extensive editing, line selection
  26. * and copy-paste, and can be built for ToaruOS and Linux (and should be
  27. * easily portable to other Unix-like environments).
  28. *
  29. * Future goals:
  30. * - History stack
  31. * - Character selection
  32. */
  33. #define _XOPEN_SOURCE 500
  34. #include <stdio.h>
  35. #include <stdlib.h>
  36. #include <string.h>
  37. #include <stdint.h>
  38. #include <stdarg.h>
  39. #include <unistd.h>
  40. #include <termios.h>
  41. #include <signal.h>
  42. #include <locale.h>
  43. #include <wchar.h>
  44. #include <ctype.h>
  45. #include <dirent.h>
  46. #include <sys/types.h>
  47. #include <sys/ioctl.h>
  48. #include <sys/stat.h>
  49. #ifdef __toaru__
  50. #include <sys/fswait.h>
  51. #include <toaru/decodeutf8.h>
  52. #else
  53. #include <poll.h>
  54. #include "../base/usr/include/toaru/decodeutf8.h"
  55. #endif
  56. #define BLOCK_SIZE 4096
  57. #define ENTER_KEY '\n'
  58. #define BACKSPACE_KEY 0x08
  59. #define DELETE_KEY 0x7F
  60. /**
  61. * Theming data
  62. */
  63. const char * COLOR_FG = "5;230";
  64. const char * COLOR_BG = "5;235";
  65. const char * COLOR_ALT_FG = "5;244";
  66. const char * COLOR_ALT_BG = "5;236";
  67. const char * COLOR_NUMBER_BG = "5;232";
  68. const char * COLOR_NUMBER_FG = "5;101";
  69. const char * COLOR_STATUS_FG = "5;230";
  70. const char * COLOR_STATUS_BG = "5;238";
  71. const char * COLOR_TABBAR_BG = "5;230";
  72. const char * COLOR_TAB_BG = "5;248";
  73. const char * COLOR_ERROR_FG = "5;15";
  74. const char * COLOR_ERROR_BG = "5;196";
  75. const char * COLOR_SEARCH_FG = "5;234";
  76. const char * COLOR_SEARCH_BG = "5;226";
  77. const char * COLOR_KEYWORD = "5;117";
  78. const char * COLOR_STRING = "5;113";
  79. const char * COLOR_COMMENT = "5;102;3";
  80. const char * COLOR_TYPE = "5;185";
  81. const char * COLOR_PRAGMA = "5;173";
  82. const char * COLOR_NUMERAL = "5;173";
  83. #define FLAG_NONE 0
  84. #define FLAG_KEYWORD 1
  85. #define FLAG_STRING 2
  86. #define FLAG_COMMENT 3
  87. #define FLAG_TYPE 4
  88. #define FLAG_PRAGMA 5
  89. #define FLAG_NUMERAL 6
  90. #define FLAG_SELECT 7
  91. #define FLAG_NORM_MAX 15
  92. #define FLAG_COMMENT_ML 16
  93. #define FLAG_STRING_ML1 17
  94. #define FLAG_STRING_ML2 18
  95. void load_colorscheme_wombat(void) {
  96. /* Based on the wombat256 theme for vim */
  97. COLOR_FG = "5;230";
  98. COLOR_BG = "5;235";
  99. COLOR_ALT_FG = "5;244";
  100. COLOR_ALT_BG = "5;236";
  101. COLOR_NUMBER_BG = "5;232";
  102. COLOR_NUMBER_FG = "5;101";
  103. COLOR_STATUS_FG = "5;230";
  104. COLOR_STATUS_BG = "5;238";
  105. COLOR_TABBAR_BG = "5;230";
  106. COLOR_TAB_BG = "5;248";
  107. COLOR_KEYWORD = "5;117";
  108. COLOR_STRING = "5;113";
  109. COLOR_COMMENT = "5;102;3";
  110. COLOR_TYPE = "5;185";
  111. COLOR_PRAGMA = "5;173";
  112. COLOR_NUMERAL = COLOR_PRAGMA;
  113. COLOR_ERROR_FG = "5;15";
  114. COLOR_ERROR_BG = "5;196";
  115. COLOR_SEARCH_FG = "5;234";
  116. COLOR_SEARCH_BG = "5;226";
  117. }
  118. void load_colorscheme_citylights(void) {
  119. /* "City Lights" based on citylights.xyz */
  120. COLOR_FG = "2;113;140;161";
  121. COLOR_BG = "2;29;37;44";
  122. COLOR_ALT_FG = "2;45;55;65";
  123. COLOR_ALT_BG = "2;33;42;50";
  124. COLOR_NUMBER_FG = "2;71;89;103";
  125. COLOR_NUMBER_BG = "2;37;47;56";
  126. COLOR_STATUS_FG = "2;116;144;166";
  127. COLOR_STATUS_BG = "2;53;67;78";
  128. COLOR_TABBAR_BG = "2;37;47;56";
  129. COLOR_TAB_BG = "2;29;37;44";
  130. COLOR_KEYWORD = "2;94;196;255";
  131. COLOR_STRING = "2;83;154;252";
  132. COLOR_COMMENT = "2;107;133;153;3";
  133. COLOR_TYPE = "2;139;212;156";
  134. COLOR_PRAGMA = "2;0;139;148";
  135. COLOR_NUMERAL = "2;207;118;132";
  136. COLOR_ERROR_FG = "5;15";
  137. COLOR_ERROR_BG = "5;196";
  138. COLOR_SEARCH_FG = "5;234";
  139. COLOR_SEARCH_BG = "5;226";
  140. }
  141. void load_colorscheme_solarized_dark(void) {
  142. /* Solarized Dark, popular theme */
  143. COLOR_FG = "2;147;161;161";
  144. COLOR_BG = "2;0;43;54";
  145. COLOR_ALT_FG = "2;147;161;161";
  146. COLOR_ALT_BG = "2;7;54;66";
  147. COLOR_NUMBER_FG = "2;131;148;149";
  148. COLOR_NUMBER_BG = "2;7;54;66";
  149. COLOR_STATUS_FG = "2;131;148;150";
  150. COLOR_STATUS_BG = "2;7;54;66";
  151. COLOR_TABBAR_BG = "2;7;54;66";
  152. COLOR_TAB_BG = "2;131;148;150";
  153. COLOR_KEYWORD = "2;133;153;0";
  154. COLOR_STRING = "2;42;161;152";
  155. COLOR_COMMENT = "2;101;123;131";
  156. COLOR_TYPE = "2;181;137;0";
  157. COLOR_PRAGMA = "2;203;75;22";
  158. COLOR_NUMERAL = "2;220;50;47";
  159. COLOR_ERROR_FG = "5;15";
  160. COLOR_ERROR_BG = "5;196";
  161. COLOR_SEARCH_FG = "5;234";
  162. COLOR_SEARCH_BG = "5;226";
  163. }
  164. void load_colorscheme_ansi(void) {
  165. /* 16-color theme */
  166. COLOR_FG = "@15";
  167. COLOR_BG = "@0";
  168. COLOR_ALT_FG = "@8";
  169. COLOR_ALT_BG = "@0";
  170. COLOR_NUMBER_FG = "@3";
  171. COLOR_NUMBER_BG = "@0";
  172. COLOR_STATUS_FG = "@15";
  173. COLOR_STATUS_BG = "@8";
  174. COLOR_TABBAR_BG = "@8";
  175. COLOR_TAB_BG = "@0";
  176. COLOR_KEYWORD = "@12";
  177. COLOR_STRING = "@2";
  178. COLOR_COMMENT = "@7";
  179. COLOR_TYPE = "@6";
  180. COLOR_PRAGMA = "@1";
  181. COLOR_NUMERAL = "@5";
  182. COLOR_ERROR_FG = "@15";
  183. COLOR_ERROR_BG = "@1";
  184. COLOR_SEARCH_FG = "@0";
  185. COLOR_SEARCH_BG = "@11";
  186. }
  187. struct theme_def {
  188. const char * name;
  189. void (*load)(void);
  190. } themes[] = {
  191. {"wombat", load_colorscheme_wombat},
  192. {"citylights", load_colorscheme_citylights},
  193. {"solarized-dark", load_colorscheme_solarized_dark},
  194. {"ansi", load_colorscheme_ansi},
  195. {NULL, NULL}
  196. };
  197. /**
  198. * Convert syntax hilighting flag to color code
  199. */
  200. const char * flag_to_color(int flag) {
  201. switch (flag) {
  202. case FLAG_KEYWORD:
  203. return COLOR_KEYWORD;
  204. case FLAG_STRING:
  205. case FLAG_STRING_ML1:
  206. case FLAG_STRING_ML2:
  207. return COLOR_STRING;
  208. case FLAG_COMMENT:
  209. case FLAG_COMMENT_ML:
  210. return COLOR_COMMENT;
  211. case FLAG_TYPE:
  212. return COLOR_TYPE;
  213. case FLAG_NUMERAL:
  214. return COLOR_NUMERAL;
  215. case FLAG_PRAGMA:
  216. return COLOR_PRAGMA;
  217. case FLAG_SELECT:
  218. return COLOR_BG;
  219. default:
  220. return COLOR_FG;
  221. }
  222. }
  223. /**
  224. * Line buffer definitions
  225. *
  226. * Lines are essentially resizable vectors of char_t structs,
  227. * which represent single codepoints in the file.
  228. */
  229. typedef struct {
  230. uint32_t display_width:5;
  231. uint32_t flags:6;
  232. uint32_t codepoint:21;
  233. } __attribute__((packed)) char_t;
  234. /**
  235. * Lines have available and actual lengths, describing
  236. * how much space was allocated vs. how much is being
  237. * used at the moment.
  238. */
  239. typedef struct {
  240. int available;
  241. int actual;
  242. int istate;
  243. char_t text[0];
  244. } line_t;
  245. /**
  246. * Global configuration state
  247. */
  248. struct {
  249. /* Terminal size */
  250. int term_width, term_height;
  251. int bottom_size;
  252. /* Command-line parameters */
  253. int hilight_on_open;
  254. int initial_file_is_read_only;
  255. line_t ** yanks;
  256. size_t yank_count;
  257. int tty_in;
  258. } global_config = {
  259. 0, /* term_width */
  260. 0, /* term_height */
  261. 2, /* bottom_size */
  262. 1, /* hilight_on_open */
  263. 0, /* initial_file_is_read_only */
  264. NULL, /* yanks */
  265. 0, /* yank_count */
  266. STDIN_FILENO
  267. };
  268. void redraw_line(int j, int x);
  269. /**
  270. * Special implementation of getch with a timeout
  271. */
  272. int _bim_unget = -1;
  273. void bim_unget(int c) {
  274. _bim_unget = c;
  275. }
  276. int bim_getch(void) {
  277. if (_bim_unget != -1) {
  278. int out = _bim_unget;
  279. _bim_unget = -1;
  280. return out;
  281. }
  282. #ifdef __toaru__
  283. int fds[] = {global_config.tty_in};
  284. int index = fswait2(1,fds,200);
  285. if (index == 0) {
  286. unsigned char buf[1];
  287. read(global_config.tty_in, buf, 1);
  288. return buf[0];
  289. } else {
  290. return -1;
  291. }
  292. #else
  293. struct pollfd fds[1];
  294. fds[0].fd = global_config.tty_in;
  295. fds[0].events = POLLIN;
  296. int ret = poll(fds,1,200);
  297. if (ret > 0 && fds[0].revents & POLLIN) {
  298. unsigned char buf[1];
  299. read(global_config.tty_in, buf, 1);
  300. return buf[0];
  301. } else {
  302. return -1;
  303. }
  304. #endif
  305. }
  306. /**
  307. * Buffer data
  308. *
  309. * A buffer describes a file, and stores
  310. * its name as well as the editor state
  311. * (cursor offsets, etc.) and the actual
  312. * line buffers.
  313. */
  314. typedef struct _env {
  315. short loading:1;
  316. short tabs:1;
  317. short modified:1;
  318. short readonly:1;
  319. short mode;
  320. short tabstop;
  321. char * file_name;
  322. int offset;
  323. int coffset;
  324. int line_no;
  325. int line_count;
  326. int line_avail;
  327. int col_no;
  328. char * search;
  329. struct syntax_definition * syntax;
  330. line_t ** lines;
  331. } buffer_t;
  332. /**
  333. * Pointer to current active buffer
  334. */
  335. buffer_t * env;
  336. /**
  337. * Editor modes (like in vim)
  338. */
  339. #define MODE_NORMAL 0
  340. #define MODE_INSERT 1
  341. #define MODE_LINE_SELECTION 2
  342. /**
  343. * Available buffers
  344. */
  345. int buffers_len;
  346. int buffers_avail;
  347. buffer_t ** buffers;
  348. /**
  349. * Create a new buffer
  350. */
  351. buffer_t * buffer_new(void) {
  352. if (buffers_len == buffers_avail) {
  353. /* If we are out of buffer space, expand the buffers vector */
  354. buffers_avail *= 2;
  355. buffers = realloc(buffers, sizeof(buffer_t *) * buffers_avail);
  356. }
  357. /* Allocate a new buffer */
  358. buffers[buffers_len] = malloc(sizeof(buffer_t));
  359. memset(buffers[buffers_len], 0x00, sizeof(buffer_t));
  360. buffers_len++;
  361. return buffers[buffers_len-1];
  362. }
  363. /**
  364. * Close a buffer
  365. */
  366. buffer_t * buffer_close(buffer_t * buf) {
  367. int i;
  368. /* Locate the buffer in the buffer pointer vector */
  369. for (i = 0; i < buffers_len; i++) {
  370. if (buf == buffers[i])
  371. break;
  372. }
  373. /* Invalid buffer? */
  374. if (i == buffers_len) {
  375. return env; /* wtf */
  376. }
  377. /* Remove the buffer from the vector, moving others up */
  378. if (i != buffers_len - 1) {
  379. memmove(&buffers[i], &buffers[i+1], sizeof(*buffers) * (buffers_len - i));
  380. }
  381. /* There is one less buffer */
  382. buffers_len--;
  383. if (!buffers_len) {
  384. /* There are no more buffers. */
  385. return NULL;
  386. }
  387. /* If this was the last buffer, return the previous last buffer */
  388. if (i == buffers_len) {
  389. return buffers[buffers_len-1];
  390. }
  391. /* Otherwise return the new last buffer */
  392. return buffers[i];
  393. }
  394. /**
  395. * Syntax definition for C
  396. */
  397. int syn_c_iskeywordchar(int c) {
  398. if (isalnum(c)) return 1;
  399. if (c == '_') return 1;
  400. return 0;
  401. }
  402. static char * syn_c_keywords[] = {
  403. "while","if","for","continue","return","break","switch","case","sizeof",
  404. "struct","union","typedef","do","default","else","goto",
  405. "alignas","alignof","offsetof",
  406. NULL
  407. };
  408. static char * syn_c_types[] = {
  409. "static","int","char","short","float","double","void","unsigned","volatile","const",
  410. "register","long","inline","restrict","enum","auto","extern","bool","complex",
  411. "uint8_t","uint16_t","uint32_t","uint64_t",
  412. "int8_t","int16_t","int32_t","int64_t",
  413. NULL
  414. };
  415. static int syn_c_extended(line_t * line, int i, int c, int last, int * out_left) {
  416. if (i == 0 && c == '#') {
  417. *out_left = line->actual+1;
  418. return FLAG_PRAGMA;
  419. }
  420. if ((!last || !syn_c_iskeywordchar(last)) && (i < line->actual - 3) &&
  421. line->text[i].codepoint == 'N' &&
  422. line->text[i+1].codepoint == 'U' &&
  423. line->text[i+2].codepoint == 'L' &&
  424. line->text[i+3].codepoint == 'L' &&
  425. (i == line->actual - 4 || !syn_c_iskeywordchar(line->text[i+4].codepoint))) {
  426. *out_left = 3;
  427. return FLAG_NUMERAL;
  428. }
  429. if ((!last || !syn_c_iskeywordchar(last)) && isdigit(c)) {
  430. if (c == '0' && i < line->actual - 1 && line->text[i+1].codepoint == 'x') {
  431. int j = 2;
  432. for (; i + j < line->actual && isxdigit(line->text[i+j].codepoint); ++j);
  433. if (i + j < line->actual && syn_c_iskeywordchar(line->text[i+j].codepoint)) {
  434. return FLAG_NONE;
  435. }
  436. *out_left = j - 1;
  437. return FLAG_NUMERAL;
  438. } else {
  439. int j = 1;
  440. while (i + j < line->actual && isdigit(line->text[i+j].codepoint)) {
  441. j++;
  442. }
  443. if (i + j < line->actual && syn_c_iskeywordchar(line->text[i+j].codepoint)) {
  444. return FLAG_NONE;
  445. }
  446. *out_left = j - 1;
  447. return FLAG_NUMERAL;
  448. }
  449. }
  450. if (c == '/') {
  451. if (i < line->actual - 1 && line->text[i+1].codepoint == '/') {
  452. *out_left = (line->actual + 1) - i;
  453. return FLAG_COMMENT;
  454. }
  455. if (i < line->actual - 1 && line->text[i+1].codepoint == '*') {
  456. int last = 0;
  457. for (int j = i + 2; j < line->actual; ++j) {
  458. int c = line->text[j].codepoint;
  459. if (c == '/' && last == '*') {
  460. *out_left = j - i;
  461. return FLAG_COMMENT;
  462. }
  463. last = c;
  464. }
  465. /* TODO multiline - update next */
  466. *out_left = (line->actual + 1) - i;
  467. return FLAG_COMMENT_ML;
  468. }
  469. }
  470. if (c == '\'') {
  471. if (i < line->actual - 3 && line->text[i+1].codepoint == '\\' &&
  472. line->text[i+3].codepoint == '\'') {
  473. *out_left = 3;
  474. return FLAG_NUMERAL;
  475. }
  476. if (i < line->actual - 2 && line->text[i+2].codepoint == '\'') {
  477. *out_left = 2;
  478. return FLAG_NUMERAL;
  479. }
  480. }
  481. if (c == '"') {
  482. int last = 0;
  483. for (int j = i+1; j < line->actual; ++j) {
  484. int c = line->text[j].codepoint;
  485. if (last != '\\' && c == '"') {
  486. *out_left = j - i;
  487. return FLAG_STRING;
  488. }
  489. if (last == '\\' && c == '\\') {
  490. last = 0;
  491. }
  492. last = c;
  493. }
  494. *out_left = (line->actual + 1) - i; /* unterminated string */
  495. return FLAG_STRING;
  496. }
  497. return 0;
  498. }
  499. char * syn_c_ext[] = {".c",".h",NULL};
  500. static int syn_c_finish(line_t * line, int * left, int state) {
  501. if (state == FLAG_COMMENT_ML) {
  502. int last = 0;
  503. for (int i = 0; i < line->actual; ++i) {
  504. if (line->text[i].codepoint == '/' && last == '*') {
  505. *left = i+2;
  506. return FLAG_COMMENT;
  507. }
  508. last = line->text[i].codepoint;
  509. }
  510. return FLAG_COMMENT_ML;
  511. }
  512. return 0;
  513. }
  514. /**
  515. * Syntax definition for Python
  516. */
  517. static char * syn_py_keywords[] = {
  518. "class","def","return","del","if","else","elif",
  519. "for","while","continue","break","assert",
  520. "as","and","or","except","finally","from",
  521. "global","import","in","is","lambda","with",
  522. "nonlocal","not","pass","raise","try","yield",
  523. NULL
  524. };
  525. static char * syn_py_types[] = {
  526. "True","False","None",
  527. "object","set","dict","int","str","bytes",
  528. NULL
  529. };
  530. static int syn_py_extended(line_t * line, int i, int c, int last, int * out_left) {
  531. if (i == 0 && c == 'i') {
  532. /* Check for import */
  533. char * import = "import ";
  534. for (int j = 0; j < line->actual + 1; ++j) {
  535. if (import[j] == '\0') {
  536. *out_left = j - 2;
  537. return FLAG_PRAGMA;
  538. }
  539. if (line->text[j].codepoint != import[j]) break;
  540. }
  541. }
  542. if (c == '#') {
  543. *out_left = (line->actual + 1) - i;
  544. return FLAG_COMMENT;
  545. }
  546. if (c == '@') {
  547. for (int j = i+1; j < line->actual + 1; ++j) {
  548. if (!syn_c_iskeywordchar(line->text[j].codepoint)) {
  549. *out_left = j - i - 1;
  550. return FLAG_PRAGMA;
  551. }
  552. *out_left = (line->actual + 1) - i;
  553. return FLAG_PRAGMA;
  554. }
  555. }
  556. if ((!last || !syn_c_iskeywordchar(last)) && isdigit(c)) {
  557. if (c == '0' && i < line->actual - 1 && line->text[i+1].codepoint == 'x') {
  558. int j = 2;
  559. for (; i + j < line->actual && isxdigit(line->text[i+j].codepoint); ++j);
  560. if (i + j < line->actual && syn_c_iskeywordchar(line->text[i+j].codepoint)) {
  561. return FLAG_NONE;
  562. }
  563. *out_left = j - 1;
  564. return FLAG_NUMERAL;
  565. } else {
  566. int j = 1;
  567. while (i + j < line->actual && isdigit(line->text[i+j].codepoint)) {
  568. j++;
  569. }
  570. if (i + j < line->actual && syn_c_iskeywordchar(line->text[i+j].codepoint)) {
  571. return FLAG_NONE;
  572. }
  573. *out_left = j - 1;
  574. return FLAG_NUMERAL;
  575. }
  576. }
  577. if (line->text[i].codepoint == '\'') {
  578. if (i + 2 < line->actual && line->text[i+1].codepoint == '\'' && line->text[i+2].codepoint == '\'') {
  579. /* Begin multiline */
  580. for (int j = i + 3; j < line->actual - 2; ++j) {
  581. if (line->text[j].codepoint == '\'' &&
  582. line->text[j+1].codepoint == '\'' &&
  583. line->text[j+2].codepoint == '\'') {
  584. *out_left = (j+2) - i;
  585. return FLAG_STRING;
  586. }
  587. }
  588. return FLAG_STRING_ML1;
  589. }
  590. int last = 0;
  591. for (int j = i+1; j < line->actual; ++j) {
  592. int c = line->text[j].codepoint;
  593. if (last != '\\' && c == '\'') {
  594. *out_left = j - i;
  595. return FLAG_STRING;
  596. }
  597. if (last == '\\' && c == '\\') {
  598. last = 0;
  599. }
  600. last = c;
  601. }
  602. *out_left = (line->actual + 1) - i; /* unterminated string */
  603. return FLAG_STRING;
  604. }
  605. if (line->text[i].codepoint == '"') {
  606. if (i + 2 < line->actual && line->text[i+1].codepoint == '"' && line->text[i+2].codepoint == '"') {
  607. /* Begin multiline */
  608. for (int j = i + 3; j < line->actual - 2; ++j) {
  609. if (line->text[j].codepoint == '"' &&
  610. line->text[j+1].codepoint == '"' &&
  611. line->text[j+2].codepoint == '"') {
  612. *out_left = (j+2) - i;
  613. return FLAG_STRING;
  614. }
  615. }
  616. return FLAG_STRING_ML2;
  617. }
  618. int last = 0;
  619. for (int j = i+1; j < line->actual; ++j) {
  620. int c = line->text[j].codepoint;
  621. if (last != '\\' && c == '"') {
  622. *out_left = j - i;
  623. return FLAG_STRING;
  624. }
  625. if (last == '\\' && c == '\\') {
  626. last = 0;
  627. }
  628. last = c;
  629. }
  630. *out_left = (line->actual + 1) - i; /* unterminated string */
  631. return FLAG_STRING;
  632. }
  633. return 0;
  634. }
  635. static int syn_py_finish(line_t * line, int * left, int state) {
  636. /* TODO support multiline quotes */
  637. if (state == FLAG_STRING_ML1) {
  638. for (int j = 0; j < line->actual - 2; ++j) {
  639. if (line->text[j].codepoint == '\'' &&
  640. line->text[j+1].codepoint == '\'' &&
  641. line->text[j+2].codepoint == '\'') {
  642. *left = (j+3);
  643. return FLAG_STRING;
  644. }
  645. }
  646. return FLAG_STRING_ML1;
  647. }
  648. if (state == FLAG_STRING_ML2) {
  649. for (int j = 0; j < line->actual - 2; ++j) {
  650. if (line->text[j].codepoint == '"' &&
  651. line->text[j+1].codepoint == '"' &&
  652. line->text[j+2].codepoint == '"') {
  653. *left = (j+3);
  654. return FLAG_STRING;
  655. }
  656. }
  657. return FLAG_STRING_ML2;
  658. }
  659. return 0;
  660. }
  661. char * syn_py_ext[] = {".py",NULL};
  662. /**
  663. * Syntax definition for ToaruOS shell
  664. */
  665. static char * syn_sh_keywords[] = {
  666. "cd","exit","export","help","history","if",
  667. "empty?","equals?","return","export-cmd",
  668. "source","exec","not","while","then","else",
  669. NULL,
  670. };
  671. static char * syn_sh_types[] = {NULL};
  672. static int syn_sh_extended(line_t * line, int i, int c, int last, int * out_left) {
  673. (void)last;
  674. if (c == '#') {
  675. *out_left = (line->actual + 1) - i;
  676. return FLAG_COMMENT;
  677. }
  678. if (line->text[i].codepoint == '\'') {
  679. int last = 0;
  680. for (int j = i+1; j < line->actual + 1; ++j) {
  681. int c = line->text[j].codepoint;
  682. if (last != '\\' && c == '\'') {
  683. *out_left = j - i;
  684. return FLAG_STRING;
  685. }
  686. if (last == '\\' && c == '\\') {
  687. last = 0;
  688. }
  689. last = c;
  690. }
  691. *out_left = (line->actual + 1) - i; /* unterminated string */
  692. return FLAG_STRING;
  693. }
  694. if (line->text[i].codepoint == '"') {
  695. int last = 0;
  696. for (int j = i+1; j < line->actual + 1; ++j) {
  697. int c = line->text[j].codepoint;
  698. if (last != '\\' && c == '"') {
  699. *out_left = j - i;
  700. return FLAG_STRING;
  701. }
  702. if (last == '\\' && c == '\\') {
  703. last = 0;
  704. }
  705. last = c;
  706. }
  707. *out_left = (line->actual + 1) - i; /* unterminated string */
  708. return FLAG_STRING;
  709. }
  710. return 0;
  711. }
  712. static int syn_sh_iskeywordchar(int c) {
  713. if (isalnum(c)) return 1;
  714. if (c == '-') return 1;
  715. if (c == '_') return 1;
  716. if (c == '?') return 1;
  717. return 0;
  718. }
  719. static char * syn_sh_ext[] = {".sh",".eshrc",".esh",NULL};
  720. static int syn_sh_finish(line_t * line, int * left, int state) {
  721. /* No multilines supported */
  722. (void)line;
  723. (void)left;
  724. (void)state;
  725. return 0;
  726. }
  727. /**
  728. * Syntax hilighting definition database
  729. */
  730. struct syntax_definition {
  731. char * name;
  732. char ** ext;
  733. char ** keywords;
  734. char ** types;
  735. int (*extended)(line_t *, int, int, int, int *);
  736. int (*iskwchar)(int);
  737. int (*finishml)(line_t *, int *, int);
  738. } syntaxes[] = {
  739. {"c",syn_c_ext,syn_c_keywords,syn_c_types,syn_c_extended,syn_c_iskeywordchar,syn_c_finish},
  740. {"python",syn_py_ext,syn_py_keywords,syn_py_types,syn_py_extended,syn_c_iskeywordchar,syn_py_finish},
  741. {"esh",syn_sh_ext,syn_sh_keywords,syn_sh_types,syn_sh_extended,syn_sh_iskeywordchar,syn_sh_finish},
  742. {NULL}
  743. };
  744. /**
  745. * Checks whether the character pointed to by `c` is the start of a match for
  746. * keyword or type name `str`.
  747. */
  748. int check_line(line_t * line, int c, char * str, int last) {
  749. if (env->syntax->iskwchar(last)) return 0;
  750. for (int i = c; i < line->actual; ++i, ++str) {
  751. if (*str == '\0' && !env->syntax->iskwchar(line->text[i].codepoint)) return 1;
  752. if (line->text[i].codepoint == *str) continue;
  753. return 0;
  754. }
  755. if (*str == '\0') return 1;
  756. return 0;
  757. }
  758. /**
  759. * Calculate syntax hilighting for the given line.
  760. */
  761. void recalculate_syntax(line_t * line, int offset) {
  762. if (!env->syntax) {
  763. for (int i = 0; i < line->actual; ++i) {
  764. line->text[i].flags = 0;
  765. }
  766. return;
  767. }
  768. /* Start from the line's stored in initial state */
  769. int state = line->istate;
  770. int left = 0;
  771. int last = 0;
  772. if (state) {
  773. /*
  774. * If we are already highlighting coming in, then we need to check
  775. * for a finishing sequence for the curent state.
  776. */
  777. state = env->syntax->finishml(line,&left,state);
  778. if (state > FLAG_NORM_MAX) {
  779. /* The finish check said that this multiline state continues. */
  780. for (int i = 0; i < line->actual; i++) {
  781. /* Set the entire line to draw with this state */
  782. line->text[i].flags = state;
  783. }
  784. /* Recalculate later lines if needed */
  785. goto _multiline;
  786. }
  787. }
  788. for (int i = 0; i < line->actual; last = line->text[i++].codepoint) {
  789. if (!left) state = 0;
  790. if (state) {
  791. /* Currently hilighting, have `left` characters remaining with this state */
  792. left--;
  793. line->text[i].flags = state;
  794. if (!left) {
  795. /* Done hilighting this state, go back to parsing on next character */
  796. state = 0;
  797. }
  798. /* If we are hilighting something, don't parse */
  799. continue;
  800. }
  801. int c = line->text[i].codepoint;
  802. line->text[i].flags = FLAG_NONE;
  803. /* Language-specific syntax hilighting */
  804. if (env->syntax->extended) {
  805. int s = env->syntax->extended(line,i,c,last,&left);
  806. if (s) {
  807. state = s;
  808. if (state > FLAG_NORM_MAX) {
  809. /* A multiline state was returned. Fill the rest of the line */
  810. for (; i < line->actual; i++) {
  811. line->text[i].flags = state;
  812. }
  813. /* And recalculate later lines if needed */
  814. goto _multiline;
  815. }
  816. goto _continue;
  817. }
  818. }
  819. /* Keywords */
  820. for (char ** kw = env->syntax->keywords; *kw; kw++) {
  821. int c = check_line(line, i, *kw, last);
  822. if (c == 1) {
  823. left = strlen(*kw)-1;
  824. state = FLAG_KEYWORD;
  825. goto _continue;
  826. }
  827. }
  828. /* Type names */
  829. for (char ** kw = env->syntax->types; *kw; kw++) {
  830. int c = check_line(line, i, *kw, last);
  831. if (c == 1) {
  832. left = strlen(*kw)-1;
  833. state = FLAG_TYPE;
  834. goto _continue;
  835. }
  836. }
  837. _continue:
  838. line->text[i].flags = state;
  839. }
  840. state = 0;
  841. _multiline:
  842. /*
  843. * If the next line's initial state does not match the state we ended on,
  844. * then it needs to be recalculated (and redraw). This may lead to multiple
  845. * recursive calls until a match is found.
  846. */
  847. if (offset + 1 < env->line_count && env->lines[offset+1]->istate != state) {
  848. /* Set the next line's initial state to our ending state */
  849. env->lines[offset+1]->istate = state;
  850. /* Recursively recalculate */
  851. recalculate_syntax(env->lines[offset+1],offset+1);
  852. /*
  853. * Determine if this is an on-screen line so we can redraw it;
  854. * this ends up drawing from bottom to top when multiple lines
  855. * need to be redrawn by a recursive call.
  856. */
  857. if (offset+1 >= env->offset && offset+1 < env->offset + global_config.term_height - global_config.bottom_size - 1) {
  858. redraw_line(offset + 1 - env->offset,offset+1);
  859. }
  860. }
  861. }
  862. /**
  863. * Recalculate tab widths.
  864. */
  865. void recalculate_tabs(line_t * line) {
  866. if (env->loading) return;
  867. int j = 0;
  868. for (int i = 0; i < line->actual; ++i) {
  869. if (line->text[i].codepoint == '\t') {
  870. line->text[i].display_width = env->tabstop - (j % env->tabstop);
  871. }
  872. j += line->text[i].display_width;
  873. }
  874. }
  875. /**
  876. * TODO:
  877. *
  878. * The line editing functions should probably take a buffer_t *
  879. * so that they can act on buffers other than the active one.
  880. */
  881. /**
  882. * Insert a character into an existing line.
  883. */
  884. line_t * line_insert(line_t * line, char_t c, int offset, int lineno) {
  885. /* If there is not enough space... */
  886. if (line->actual == line->available) {
  887. /* Expand the line buffer */
  888. line->available *= 2;
  889. line = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available);
  890. }
  891. /* If this was not the last character, then shift remaining characters forward. */
  892. if (offset < line->actual) {
  893. memmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset));
  894. }
  895. /* Insert the new character */
  896. line->text[offset] = c;
  897. /* There is one new character in the line */
  898. line->actual += 1;
  899. recalculate_tabs(line);
  900. recalculate_syntax(line, lineno);
  901. return line;
  902. }
  903. /**
  904. * Delete a character from a line
  905. */
  906. void line_delete(line_t * line, int offset, int lineno) {
  907. /* Can't delete character before start of line. */
  908. if (offset == 0) return;
  909. /* If this isn't the last character, we need to move all subsequent characters backwards */
  910. if (offset < line->actual) {
  911. memmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset));
  912. }
  913. /* The line is one character shorter */
  914. line->actual -= 1;
  915. recalculate_tabs(line);
  916. recalculate_syntax(line, lineno);
  917. }
  918. /**
  919. * Remove a line from the active buffer
  920. */
  921. line_t ** remove_line(line_t ** lines, int offset) {
  922. /* If there is only one line, clear it instead of removing it. */
  923. if (env->line_count == 1) {
  924. lines[offset]->actual = 0;
  925. return lines;
  926. }
  927. /* Otherwise, free the data used by the line */
  928. free(lines[offset]);
  929. /* Move other lines up */
  930. if (offset < env->line_count) {
  931. memmove(&lines[offset], &lines[offset+1], sizeof(line_t *) * (env->line_count - (offset - 1)));
  932. lines[env->line_count-1] = NULL;
  933. }
  934. /* There is one less line */
  935. env->line_count -= 1;
  936. return lines;
  937. }
  938. /**
  939. * Add a new line to the active buffer.
  940. */
  941. line_t ** add_line(line_t ** lines, int offset) {
  942. /* Invalid offset? */
  943. if (offset > env->line_count) return lines;
  944. /* Not enough space */
  945. if (env->line_count == env->line_avail) {
  946. /* Allocate more space */
  947. env->line_avail *= 2;
  948. lines = realloc(lines, sizeof(line_t *) * env->line_avail);
  949. }
  950. /* If this isn't the last line, move other lines down */
  951. if (offset < env->line_count) {
  952. memmove(&lines[offset+1], &lines[offset], sizeof(line_t *) * (env->line_count - offset));
  953. }
  954. /* Allocate the new line */
  955. lines[offset] = malloc(sizeof(line_t) + sizeof(char_t) * 32);
  956. lines[offset]->available = 32;
  957. lines[offset]->actual = 0;
  958. lines[offset]->istate = 0;
  959. /* There is one new line */
  960. env->line_count += 1;
  961. env->lines = lines;
  962. if (offset > 0) {
  963. recalculate_syntax(lines[offset-1],offset-1);
  964. }
  965. return lines;
  966. }
  967. /**
  968. * Replace a line with data from another line (used by paste to paste yanked lines)
  969. */
  970. void replace_line(line_t ** lines, int offset, line_t * replacement) {
  971. if (lines[offset]->available < replacement->actual) {
  972. lines[offset] = realloc(lines[offset], sizeof(line_t) + sizeof(char_t) * replacement->available);
  973. lines[offset]->available = replacement->available;
  974. }
  975. lines[offset]->actual = replacement->actual;
  976. memcpy(&lines[offset]->text, &replacement->text, sizeof(char_t) * replacement->actual);
  977. recalculate_syntax(lines[offset],offset);
  978. }
  979. /**
  980. * Merge two consecutive lines.
  981. * lineb is the offset of the second line.
  982. */
  983. line_t ** merge_lines(line_t ** lines, int lineb) {
  984. /* linea is the line immediately before lineb */
  985. int linea = lineb - 1;
  986. /* If there isn't enough space in linea hold both... */
  987. while (lines[linea]->available < lines[linea]->actual + lines[lineb]->actual) {
  988. /* ... allocate more space until it fits */
  989. lines[linea]->available *= 2;
  990. /* XXX why not just do this once after calculating appropriate size */
  991. lines[linea] = realloc(lines[linea], sizeof(line_t) + sizeof(char_t) * lines[linea]->available);
  992. }
  993. /* Copy the second line into the first line */
  994. memcpy(&lines[linea]->text[lines[linea]->actual], &lines[lineb]->text, sizeof(char_t) * lines[lineb]->actual);
  995. /* The first line is now longer */
  996. lines[linea]->actual = lines[linea]->actual + lines[lineb]->actual;
  997. recalculate_tabs(lines[linea]);
  998. recalculate_syntax(lines[linea], linea);
  999. /* Remove the second line */
  1000. return remove_line(lines, lineb);
  1001. }
  1002. /**
  1003. * Split a line into two lines at the given column
  1004. */
  1005. line_t ** split_line(line_t ** lines, int line, int split) {
  1006. /* If we're trying to split from the start, just add a new blank line before */
  1007. if (split == 0) {
  1008. return add_line(lines, line - 1);
  1009. }
  1010. /* Allocate more space as needed */
  1011. if (env->line_count == env->line_avail) {
  1012. env->line_avail *= 2;
  1013. lines = realloc(lines, sizeof(line_t *) * env->line_avail);
  1014. }
  1015. /* Shift later lines down */
  1016. if (line < env->line_count) {
  1017. memmove(&lines[line+1], &lines[line], sizeof(line_t *) * (env->line_count - line));
  1018. }
  1019. /* I have no idea what this is doing */
  1020. int remaining = lines[line-1]->actual - split;
  1021. int v = remaining;
  1022. v--;
  1023. v |= v >> 1;
  1024. v |= v >> 2;
  1025. v |= v >> 4;
  1026. v |= v >> 8;
  1027. v |= v >> 16;
  1028. v++;
  1029. /* Allocate space for the new line */
  1030. lines[line] = malloc(sizeof(line_t) + sizeof(char_t) * v);
  1031. lines[line]->available = v;
  1032. lines[line]->actual = remaining;
  1033. lines[line]->istate = 0;
  1034. /* Move the data from the old line into the new line */
  1035. memmove(lines[line]->text, &lines[line-1]->text[split], sizeof(char_t) * remaining);
  1036. lines[line-1]->actual = split;
  1037. recalculate_tabs(lines[line-1]);
  1038. recalculate_tabs(lines[line]);
  1039. recalculate_syntax(lines[line-1], line-1);
  1040. recalculate_syntax(lines[line], line);
  1041. /* There is one new line */
  1042. env->line_count += 1;
  1043. /* We may have reallocated lines */
  1044. return lines;
  1045. }
  1046. /**
  1047. * Initialize a buffer with default values
  1048. */
  1049. void setup_buffer(buffer_t * env) {
  1050. /* If this buffer was already initialized, clear out its line data */
  1051. if (env->lines) {
  1052. for (int i = 0; i < env->line_count; ++i) {
  1053. free(env->lines[i]);
  1054. }
  1055. free(env->lines);
  1056. }
  1057. /* Default state parameters */
  1058. env->line_no = 1; /* Default cursor position */
  1059. env->col_no = 1;
  1060. env->line_count = 1; /* Buffers always have at least one line */
  1061. env->modified = 0;
  1062. env->readonly = 0;
  1063. env->offset = 0;
  1064. env->line_avail = 8; /* Default line buffer capacity */
  1065. env->tabs = 1; /* Tabs by default */
  1066. env->tabstop = 4; /* Tab stop width */
  1067. /* Allocate line buffer */
  1068. env->lines = malloc(sizeof(line_t *) * env->line_avail);
  1069. /* Initialize the first line */
  1070. env->lines[0] = malloc(sizeof(line_t) + sizeof(char_t) * 32);
  1071. env->lines[0]->available = 32;
  1072. env->lines[0]->actual = 0;
  1073. env->lines[0]->istate = 0;
  1074. }
  1075. /**
  1076. * Toggle buffered / unbuffered modes
  1077. */
  1078. struct termios old;
  1079. void set_unbuffered(void) {
  1080. tcgetattr(STDOUT_FILENO, &old);
  1081. struct termios new = old;
  1082. new.c_lflag &= (~ICANON & ~ECHO);
  1083. tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new);
  1084. }
  1085. void set_buffered(void) {
  1086. tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old);
  1087. }
  1088. /**
  1089. * Convert codepoint to utf-8 string
  1090. */
  1091. int to_eight(uint32_t codepoint, char * out) {
  1092. memset(out, 0x00, 7);
  1093. if (codepoint < 0x0080) {
  1094. out[0] = (char)codepoint;
  1095. } else if (codepoint < 0x0800) {
  1096. out[0] = 0xC0 | (codepoint >> 6);
  1097. out[1] = 0x80 | (codepoint & 0x3F);
  1098. } else if (codepoint < 0x10000) {
  1099. out[0] = 0xE0 | (codepoint >> 12);
  1100. out[1] = 0x80 | ((codepoint >> 6) & 0x3F);
  1101. out[2] = 0x80 | (codepoint & 0x3F);
  1102. } else if (codepoint < 0x200000) {
  1103. out[0] = 0xF0 | (codepoint >> 18);
  1104. out[1] = 0x80 | ((codepoint >> 12) & 0x3F);
  1105. out[2] = 0x80 | ((codepoint >> 6) & 0x3F);
  1106. out[3] = 0x80 | ((codepoint) & 0x3F);
  1107. } else if (codepoint < 0x4000000) {
  1108. out[0] = 0xF8 | (codepoint >> 24);
  1109. out[1] = 0x80 | (codepoint >> 18);
  1110. out[2] = 0x80 | ((codepoint >> 12) & 0x3F);
  1111. out[3] = 0x80 | ((codepoint >> 6) & 0x3F);
  1112. out[4] = 0x80 | ((codepoint) & 0x3F);
  1113. } else {
  1114. out[0] = 0xF8 | (codepoint >> 30);
  1115. out[1] = 0x80 | ((codepoint >> 24) & 0x3F);
  1116. out[2] = 0x80 | ((codepoint >> 18) & 0x3F);
  1117. out[3] = 0x80 | ((codepoint >> 12) & 0x3F);
  1118. out[4] = 0x80 | ((codepoint >> 6) & 0x3F);
  1119. out[5] = 0x80 | ((codepoint) & 0x3F);
  1120. }
  1121. return strlen(out);
  1122. }
  1123. /**
  1124. * Get the presentation width of a codepoint
  1125. */
  1126. int codepoint_width(wchar_t codepoint) {
  1127. if (codepoint == '\t') {
  1128. return 1; /* Recalculate later */
  1129. }
  1130. if (codepoint < 32) {
  1131. /* We render these as ^@ */
  1132. return 2;
  1133. }
  1134. if (codepoint == 0x7F) {
  1135. /* Renders as ^? */
  1136. return 2;
  1137. }
  1138. if (codepoint > 0x7f && codepoint < 0xa0) {
  1139. /* Upper control bytes <xx> */
  1140. return 4;
  1141. }
  1142. if (codepoint == 0xa0) {
  1143. /* Non-breaking space _ */
  1144. return 1;
  1145. }
  1146. /* Skip wcwidth for anything under 256 */
  1147. if (codepoint > 256) {
  1148. /* Higher codepoints may be wider (eg. Japanese) */
  1149. return wcwidth(codepoint);
  1150. }
  1151. return 1;
  1152. }
  1153. /**
  1154. * Move the terminal cursor
  1155. */
  1156. void place_cursor(int x, int y) {
  1157. printf("\033[%d;%dH", y, x);
  1158. fflush(stdout);
  1159. }
  1160. /**
  1161. * Move the terminal cursor, but only horizontally
  1162. */
  1163. void place_cursor_h(int h) {
  1164. printf("\033[%dG", h);
  1165. fflush(stdout);
  1166. }
  1167. /**
  1168. * Set text colors
  1169. *
  1170. * Normally, text colors are just strings, but if they
  1171. * start with @ they will be parsed as integers
  1172. * representing one of the 16 standard colors, suitable
  1173. * for terminals without support for the 256- or 24-bit
  1174. * color modes.
  1175. */
  1176. void set_colors(const char * fg, const char * bg) {
  1177. printf("\033[22;23;");
  1178. if (*bg == '@') {
  1179. int _bg = atoi(bg+1);
  1180. if (_bg < 8) {
  1181. printf("4%d;", _bg);
  1182. } else {
  1183. printf("10%d;", _bg-8);
  1184. }
  1185. } else {
  1186. printf("48;%s;", bg);
  1187. }
  1188. if (*fg == '@') {
  1189. int _fg = atoi(fg+1);
  1190. if (_fg < 8) {
  1191. printf("3%dm", _fg);
  1192. } else {
  1193. printf("9%dm", _fg-8);
  1194. }
  1195. } else {
  1196. printf("38;%sm", fg);
  1197. }
  1198. fflush(stdout);
  1199. }
  1200. /**
  1201. * Set just the foreground color
  1202. *
  1203. * (See set_colors above)
  1204. */
  1205. void set_fg_color(const char * fg) {
  1206. printf("\033[22;23;");
  1207. if (*fg == '@') {
  1208. int _fg = atoi(fg+1);
  1209. if (_fg < 8) {
  1210. printf("3%dm", _fg);
  1211. } else {
  1212. printf("9%dm", _fg-8);
  1213. }
  1214. } else {
  1215. printf("38;%sm", fg);
  1216. }
  1217. fflush(stdout);
  1218. }
  1219. /**
  1220. * Clear the rest of this line
  1221. */
  1222. void clear_to_end(void) {
  1223. printf("\033[K");
  1224. fflush(stdout);
  1225. }
  1226. /**
  1227. * Enable bold text display
  1228. */
  1229. void set_bold(void) {
  1230. printf("\033[1m");
  1231. fflush(stdout);
  1232. }
  1233. /**
  1234. * Enable underlined text display
  1235. */
  1236. void set_underline(void) {
  1237. printf("\033[4m");
  1238. fflush(stdout);
  1239. }
  1240. /**
  1241. * Reset text display attributes
  1242. */
  1243. void reset(void) {
  1244. printf("\033[0m");
  1245. fflush(stdout);
  1246. }
  1247. /**
  1248. * Clear the entire screen
  1249. */
  1250. void clear_screen(void) {
  1251. printf("\033[H\033[2J");
  1252. fflush(stdout);
  1253. }
  1254. /**
  1255. * Hide the cursor
  1256. */
  1257. void hide_cursor(void) {
  1258. printf("\033[?25l");
  1259. fflush(stdout);
  1260. }
  1261. /*
  1262. * Show the cursor
  1263. */
  1264. void show_cursor(void) {
  1265. printf("\033[?25h");
  1266. fflush(stdout);
  1267. }
  1268. /**
  1269. * Request mouse events
  1270. */
  1271. void mouse_enable(void) {
  1272. printf("\033[?1000h");
  1273. fflush(stdout);
  1274. }
  1275. /**
  1276. * Stop mouse events
  1277. */
  1278. void mouse_disable(void) {
  1279. printf("\033[?1000l");
  1280. fflush(stdout);
  1281. }
  1282. /**
  1283. * Shift the screen up one line
  1284. */
  1285. void shift_up(void) {
  1286. printf("\033[1S");
  1287. }
  1288. /**
  1289. * Shift the screen down one line.
  1290. */
  1291. void shift_down(void) {
  1292. printf("\033[1T");
  1293. }
  1294. /**
  1295. * Redaw the tabbar, with a tab for each buffer.
  1296. *
  1297. * The active buffer is highlighted.
  1298. */
  1299. void redraw_tabbar(void) {
  1300. /* Hide cursor while rendering UI */
  1301. hide_cursor();
  1302. /* Move to upper left */
  1303. place_cursor(1,1);
  1304. /* For each buffer... */
  1305. for (int i = 0; i < buffers_len; i++) {
  1306. buffer_t * _env = buffers[i];
  1307. if (_env == env) {
  1308. /* If this is the active buffer, hilight it */
  1309. reset();
  1310. set_colors(COLOR_FG, COLOR_BG);
  1311. set_bold();
  1312. } else {
  1313. /* Otherwise use default tab color */
  1314. reset();
  1315. set_colors(COLOR_FG, COLOR_TAB_BG);
  1316. set_underline();
  1317. }
  1318. /* If this buffer is modified, indicate that with a prepended + */
  1319. if (_env->modified) {
  1320. printf(" +");
  1321. }
  1322. /* Print the filename */
  1323. if (_env->file_name) {
  1324. printf(" %s ", _env->file_name);
  1325. } else {
  1326. printf(" [No Name] ");
  1327. }
  1328. }
  1329. /* Reset bold/underline */
  1330. reset();
  1331. /* Fill the rest of the tab bar */
  1332. set_colors(COLOR_FG, COLOR_TABBAR_BG);
  1333. clear_to_end();
  1334. }
  1335. /**
  1336. * Braindead log10 implementation for the line numbers
  1337. */
  1338. int log_base_10(unsigned int v) {
  1339. int r = (v >= 1000000000) ? 9 : (v >= 100000000) ? 8 : (v >= 10000000) ? 7 :
  1340. (v >= 1000000) ? 6 : (v >= 100000) ? 5 : (v >= 10000) ? 4 :
  1341. (v >= 1000) ? 3 : (v >= 100) ? 2 : (v >= 10) ? 1 : 0;
  1342. return r;
  1343. }
  1344. /**
  1345. * Render a line of text
  1346. *
  1347. * This handles rendering the actual text content. A full line of text
  1348. * also includes a line number and some padding.
  1349. *
  1350. * width: width of the text display region (term width - line number width)
  1351. * offset: how many cells into the line to start rendering at
  1352. */
  1353. void render_line(line_t * line, int width, int offset) {
  1354. int i = 0; /* Offset in char_t line data entries */
  1355. int j = 0; /* Offset in terminal cells */
  1356. const char * last_color = NULL;
  1357. /* Set default text colors */
  1358. set_colors(COLOR_FG, COLOR_BG);
  1359. /*
  1360. * When we are rendering in the middle of a wide character,
  1361. * we render -'s to fill the remaining amount of the
  1362. * charater's width
  1363. */
  1364. int remainder = 0;
  1365. /* For each character in the line ... */
  1366. while (i < line->actual) {
  1367. /* If there is remaining text... */
  1368. if (remainder) {
  1369. /* If we should be drawing by now... */
  1370. if (j >= offset) {
  1371. /* Fill remainder with -'s */
  1372. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1373. printf("-");
  1374. set_colors(COLOR_FG, COLOR_BG);
  1375. }
  1376. /* One less remaining width cell to fill */
  1377. remainder--;
  1378. /* Terminal offset moves forward */
  1379. j++;
  1380. /*
  1381. * If this was the last remaining character, move to
  1382. * the next codepoint in the line
  1383. */
  1384. if (remainder == 0) {
  1385. i++;
  1386. }
  1387. continue;
  1388. }
  1389. /* Get the next character to draw */
  1390. char_t c = line->text[i];
  1391. /* If we should be drawing by now... */
  1392. if (j >= offset) {
  1393. /* If this character is going to fall off the edge of the screen... */
  1394. if (j - offset + c.display_width >= width) {
  1395. /* We draw this with special colors so it isn't ambiguous */
  1396. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1397. /* If it's wide, draw ---> as needed */
  1398. while (j - offset < width - 1) {
  1399. printf("-");
  1400. j++;
  1401. }
  1402. /* End the line with a > to show it overflows */
  1403. printf(">");
  1404. break;
  1405. }
  1406. /* Syntax hilighting */
  1407. const char * color = flag_to_color(c.flags);
  1408. if (c.flags == FLAG_SELECT) {
  1409. set_colors(COLOR_BG, COLOR_FG);
  1410. } else {
  1411. if (!last_color || strcmp(color, last_color)) {
  1412. set_fg_color(color);
  1413. last_color = color;
  1414. }
  1415. }
  1416. void _set_colors(const char * fg, const char * bg) {
  1417. if (c.flags != FLAG_SELECT) {
  1418. set_colors(fg,bg);
  1419. }
  1420. }
  1421. /* Render special characters */
  1422. if (c.codepoint == '\t') {
  1423. _set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1424. printf("»");
  1425. for (int i = 1; i < c.display_width; ++i) {
  1426. printf("·");
  1427. }
  1428. _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  1429. } else if (c.codepoint < 32) {
  1430. /* Codepoints under 32 to get converted to ^@ escapes */
  1431. _set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1432. printf("^%c", '@' + c.codepoint);
  1433. _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  1434. } else if (c.codepoint == 0x7f) {
  1435. _set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1436. printf("^?");
  1437. _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  1438. } else if (c.codepoint > 0x7f && c.codepoint < 0xa0) {
  1439. _set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1440. printf("<%2x>", c.codepoint);
  1441. _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  1442. } else if (c.codepoint == 0xa0) {
  1443. _set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1444. printf("_");
  1445. _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  1446. } else {
  1447. /* Normal characters get output */
  1448. char tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */
  1449. to_eight(c.codepoint, tmp);
  1450. printf("%s", tmp);
  1451. }
  1452. /* Advance the terminal cell offset by the render width of this character */
  1453. j += c.display_width;
  1454. /* Advance to the next character */
  1455. i++;
  1456. } else if (c.display_width > 1) {
  1457. /*
  1458. * If this is a wide character but we aren't ready to render yet,
  1459. * we may need to draw some filler text for the remainder of its
  1460. * width to ensure we don't jump around when horizontally scrolling
  1461. * past wide characters.
  1462. */
  1463. remainder = c.display_width - 1;
  1464. j++;
  1465. } else {
  1466. /* Regular character, not ready to draw, advance without doing anything */
  1467. j++;
  1468. i++;
  1469. }
  1470. }
  1471. }
  1472. /**
  1473. * Get the width of the line number region
  1474. */
  1475. int num_width(void) {
  1476. int w = log_base_10(env->line_count) + 1;
  1477. if (w < 2) return 2;
  1478. return w;
  1479. }
  1480. /**
  1481. * Redraw line.
  1482. *
  1483. * This draws the line number as well as the actual text.
  1484. * j = screen-relative line offset.
  1485. */
  1486. void redraw_line(int j, int x) {
  1487. if (env->loading) return;
  1488. /* Hide cursor when drawing */
  1489. hide_cursor();
  1490. /* Move cursor to upper left most cell of this line */
  1491. place_cursor(1,2 + j);
  1492. /* Draw a gutter on the left.
  1493. * TODO: The gutter can be used to show single-character
  1494. * line annotations, such as collapse state, or
  1495. * whether a search result was found on this line.
  1496. */
  1497. set_colors(COLOR_NUMBER_FG, COLOR_ALT_FG);
  1498. printf(" ");
  1499. /* Draw the line number */
  1500. set_colors(COLOR_NUMBER_FG, COLOR_NUMBER_BG);
  1501. int num_size = num_width();
  1502. for (int y = 0; y < num_size - log_base_10(x + 1); ++y) {
  1503. printf(" ");
  1504. }
  1505. printf("%d%c", x + 1, (x+1 == env->line_no && env->coffset > 0) ? '<' : ' ');
  1506. /*
  1507. * Draw the line text
  1508. * If this is the active line, the current character cell offset should be used.
  1509. * (Non-active lines are not shifted and always render from the start of the line)
  1510. */
  1511. render_line(env->lines[x], global_config.term_width - 3 - num_size, (x + 1 == env->line_no) ? env->coffset : 0);
  1512. /* Clear the rest of the line */
  1513. clear_to_end();
  1514. }
  1515. /**
  1516. * Redraw the entire text area
  1517. */
  1518. void redraw_text(void) {
  1519. /* Hide cursor while rendering */
  1520. hide_cursor();
  1521. /* Figure out the available size of the text region */
  1522. int l = global_config.term_height - global_config.bottom_size - 1;
  1523. int j = 0;
  1524. /* Draw each line */
  1525. for (int x = env->offset; j < l && x < env->line_count; x++) {
  1526. redraw_line(j,x);
  1527. j++;
  1528. }
  1529. /* Draw the rest of the text region as ~ lines */
  1530. for (; j < l; ++j) {
  1531. place_cursor(1,2 + j);
  1532. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1533. printf("~");
  1534. clear_to_end();
  1535. }
  1536. }
  1537. /**
  1538. * Draw the status bar
  1539. *
  1540. * The status bar shows the name of the file, whether it has modifications,
  1541. * and (in the future) what syntax highlighting mode is enabled.
  1542. *
  1543. * The right side of the tatus bar shows the line number and column.
  1544. */
  1545. void redraw_statusbar(void) {
  1546. /* Hide cursor while rendering */
  1547. hide_cursor();
  1548. /* Move cursor to the status bar line (second from bottom */
  1549. place_cursor(1, global_config.term_height - 1);
  1550. /* Set background colors for status line */
  1551. set_colors(COLOR_STATUS_FG, COLOR_STATUS_BG);
  1552. /* Print the file name */
  1553. if (env->file_name) {
  1554. printf("%s", env->file_name);
  1555. } else {
  1556. printf("[No Name]");
  1557. }
  1558. printf(" ");
  1559. if (env->syntax) {
  1560. printf("[%s]", env->syntax->name);
  1561. }
  1562. /* Print file status indicators */
  1563. if (env->modified) {
  1564. printf("[+]");
  1565. }
  1566. if (env->readonly) {
  1567. printf("[ro]");
  1568. }
  1569. printf(" ");
  1570. if (global_config.yanks) {
  1571. printf("[y:%ld]", global_config.yank_count);
  1572. }
  1573. /* Clear the rest of the status bar */
  1574. clear_to_end();
  1575. /* Pre-render the right hand side of the status bar */
  1576. char right_hand[1024];
  1577. sprintf(right_hand, "Line %d/%d Col: %d ", env->line_no, env->line_count, env->col_no);
  1578. /* Move the cursor appropriately to draw it */
  1579. place_cursor_h(global_config.term_width - strlen(right_hand)); /* TODO: What if we're localized and this has wide chars? */
  1580. printf("%s",right_hand);
  1581. fflush(stdout);
  1582. }
  1583. /**
  1584. * Draw the command line
  1585. *
  1586. * The command line either has input from the user (:quit, :!make, etc.)
  1587. * or shows the INSERT (or VISUAL in the future) mode name.
  1588. */
  1589. void redraw_commandline(void) {
  1590. /* Hide cursor while rendering */
  1591. hide_cursor();
  1592. /* Move cursor to the last line */
  1593. place_cursor(1, global_config.term_height);
  1594. /* Set background color */
  1595. set_colors(COLOR_FG, COLOR_BG);
  1596. /* If we are in an edit mode, note that. */
  1597. if (env->mode == MODE_INSERT) {
  1598. set_bold();
  1599. printf("-- INSERT --");
  1600. clear_to_end();
  1601. reset();
  1602. } else if (env->mode == MODE_LINE_SELECTION) {
  1603. set_bold();
  1604. printf("-- LINE SELECTION --");
  1605. clear_to_end();
  1606. reset();
  1607. } else {
  1608. clear_to_end();
  1609. }
  1610. }
  1611. /**
  1612. * Draw all screen elements
  1613. */
  1614. void redraw_all(void) {
  1615. redraw_tabbar();
  1616. redraw_text();
  1617. redraw_statusbar();
  1618. redraw_commandline();
  1619. }
  1620. /**
  1621. * Update the terminal title bar
  1622. */
  1623. void update_title(void) {
  1624. char cwd[1024] = {'/',0};
  1625. getcwd(cwd, 1024);
  1626. /*
  1627. * XXX: I think this only works in a few terminals.
  1628. * VTE seems to have a different escape sequence for tab names.
  1629. */
  1630. printf("\033]1;%s%s (%s) - BIM\007", env->file_name, env->modified ? " +" : "", cwd);
  1631. }
  1632. /**
  1633. * Mark this buffer as modified and
  1634. * redraw the status and tabbar if needed.
  1635. */
  1636. void set_modified(void) {
  1637. /* If it was already marked modified, no need to do anything */
  1638. if (env->modified) return;
  1639. /* Mark as modified */
  1640. env->modified = 1;
  1641. /* Redraw some things */
  1642. update_title();
  1643. redraw_tabbar();
  1644. redraw_statusbar();
  1645. }
  1646. /**
  1647. * Draw a message on the status line
  1648. */
  1649. void render_status_message(char * message, ...) {
  1650. /* varargs setup */
  1651. va_list args;
  1652. va_start(args, message);
  1653. char buf[1024];
  1654. /* Process format string */
  1655. vsprintf(buf, message, args);
  1656. va_end(args);
  1657. /* Hide cursor while rendering */
  1658. hide_cursor();
  1659. /* Move cursor to the status bar line (second from bottom */
  1660. place_cursor(1, global_config.term_height - 1);
  1661. /* Set background colors for status line */
  1662. set_colors(COLOR_STATUS_FG, COLOR_STATUS_BG);
  1663. printf("%s", buf);
  1664. /* Clear the rest of the status bar */
  1665. clear_to_end();
  1666. }
  1667. /**
  1668. * Draw an errormessage to the command line.
  1669. */
  1670. void render_error(char * message, ...) {
  1671. /* varargs setup */
  1672. va_list args;
  1673. va_start(args, message);
  1674. char buf[1024];
  1675. /* Process format string */
  1676. vsprintf(buf, message, args);
  1677. va_end(args);
  1678. /* Hide cursor and redraw command line */
  1679. redraw_commandline();
  1680. /* Set appropriate error message colors */
  1681. set_colors(COLOR_ERROR_FG, COLOR_ERROR_BG);
  1682. /* Draw the message */
  1683. printf("%s", buf);
  1684. fflush(stdout);
  1685. }
  1686. /**
  1687. * Move the cursor to the appropriate location based
  1688. * on where it is in the text region.
  1689. *
  1690. * This does some additional math to set the text
  1691. * region horizontal offset.
  1692. */
  1693. void place_cursor_actual(void) {
  1694. /* Account for the left hand gutter */
  1695. int num_size = num_width() + 3;
  1696. int x = num_size + 1 - env->coffset;
  1697. /* Determine where the cursor is physically */
  1698. for (int i = 0; i < env->col_no - 1; ++i) {
  1699. char_t * c = &env->lines[env->line_no-1]->text[i];
  1700. x += c->display_width;
  1701. }
  1702. /* y is a bit easier to calculate */
  1703. int y = env->line_no - env->offset + 1;
  1704. int needs_redraw = 0;
  1705. while (y < 2) {
  1706. y++;
  1707. env->offset--;
  1708. needs_redraw = 1;
  1709. }
  1710. while (y > global_config.term_height - global_config.bottom_size) {
  1711. y--;
  1712. env->offset++;
  1713. needs_redraw = 1;
  1714. }
  1715. if (needs_redraw) {
  1716. redraw_text();
  1717. redraw_tabbar();
  1718. redraw_statusbar();
  1719. redraw_commandline();
  1720. }
  1721. /* If the cursor has gone off screen to the right... */
  1722. if (x > global_config.term_width - 1) {
  1723. /* Adjust the offset appropriately to scroll horizontally */
  1724. int diff = x - (global_config.term_width - 1);
  1725. env->coffset += diff;
  1726. x -= diff;
  1727. redraw_text();
  1728. }
  1729. /* Same for scrolling horizontally to the left */
  1730. if (x < num_size + 1) {
  1731. int diff = (num_size + 1) - x;
  1732. env->coffset -= diff;
  1733. x += diff;
  1734. redraw_text();
  1735. }
  1736. /* Move the actual terminal cursor */
  1737. place_cursor(x,y);
  1738. /* Show the cursor */
  1739. show_cursor();
  1740. }
  1741. /**
  1742. * Handle terminal size changes
  1743. */
  1744. void SIGWINCH_handler(int sig) {
  1745. (void)sig;
  1746. struct winsize w;
  1747. ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
  1748. global_config.term_width = w.ws_col;
  1749. global_config.term_height = w.ws_row;
  1750. redraw_all();
  1751. signal(SIGWINCH, SIGWINCH_handler);
  1752. }
  1753. /**
  1754. * Run global initialization tasks
  1755. */
  1756. void initialize(void) {
  1757. setlocale(LC_ALL, "");
  1758. buffers_avail = 4;
  1759. buffers = malloc(sizeof(buffer_t *) * buffers_avail);
  1760. struct winsize w;
  1761. ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
  1762. global_config.term_width = w.ws_col;
  1763. global_config.term_height = w.ws_row;
  1764. set_unbuffered();
  1765. mouse_enable();
  1766. signal(SIGWINCH, SIGWINCH_handler);
  1767. }
  1768. /**
  1769. * Move the cursor to a specific line.
  1770. */
  1771. void goto_line(int line) {
  1772. /* Respect file bounds */
  1773. if (line < 1) line = 1;
  1774. if (line > env->line_count) line = env->line_count;
  1775. /* Move the cursor / text region offsets */
  1776. env->coffset = 0;
  1777. env->offset = line - 1;
  1778. env->line_no = line;
  1779. env->col_no = 1;
  1780. redraw_all();
  1781. }
  1782. /**
  1783. * UTF-8 parser state
  1784. */
  1785. static uint32_t codepoint_r;
  1786. static uint32_t state = 0;
  1787. /**
  1788. * Processs (part of) a file and add it to a buffer.
  1789. */
  1790. void add_buffer(uint8_t * buf, int size) {
  1791. for (int i = 0; i < size; ++i) {
  1792. if (!decode(&state, &codepoint_r, buf[i])) {
  1793. uint32_t c = codepoint_r;
  1794. if (c == '\n') {
  1795. env->lines = add_line(env->lines, env->line_no);
  1796. env->col_no = 1;
  1797. env->line_no += 1;
  1798. } else {
  1799. char_t _c;
  1800. _c.codepoint = (uint32_t)c;
  1801. _c.flags = 0;
  1802. _c.display_width = codepoint_width((wchar_t)c);
  1803. line_t * line = env->lines[env->line_no - 1];
  1804. line_t * nline = line_insert(line, _c, env->col_no - 1, env->line_no-1);
  1805. if (line != nline) {
  1806. env->lines[env->line_no - 1] = nline;
  1807. }
  1808. env->col_no += 1;
  1809. }
  1810. } else if (state == UTF8_REJECT) {
  1811. state = 0;
  1812. }
  1813. }
  1814. }
  1815. struct syntax_definition * match_syntax(char * file) {
  1816. for (struct syntax_definition * s = syntaxes; s->name; ++s) {
  1817. for (char ** ext = s->ext; *ext; ++ext) {
  1818. int i = strlen(file);
  1819. int j = strlen(*ext);
  1820. do {
  1821. if (file[i] != (*ext)[j]) break;
  1822. if (j == 0) return s;
  1823. if (i == 0) break;
  1824. i--;
  1825. j--;
  1826. } while (1);
  1827. }
  1828. }
  1829. return NULL;
  1830. }
  1831. /**
  1832. * Create a new buffer from a file.
  1833. */
  1834. void open_file(char * file) {
  1835. env = buffer_new();
  1836. env->loading = 1;
  1837. setup_buffer(env);
  1838. FILE * f;
  1839. if (!strcmp(file,"-")) {
  1840. /**
  1841. * Read file from stdin. stderr provides terminal input.
  1842. */
  1843. f = stdin;
  1844. global_config.tty_in = STDERR_FILENO;
  1845. env->modified = 1;
  1846. } else {
  1847. f = fopen(file, "r");
  1848. env->file_name = strdup(file);
  1849. }
  1850. if (!f) {
  1851. if (global_config.hilight_on_open) {
  1852. env->syntax = match_syntax(file);
  1853. }
  1854. env->loading = 0;
  1855. return;
  1856. }
  1857. uint8_t buf[BLOCK_SIZE];
  1858. state = 0;
  1859. while (!feof(f)) {
  1860. size_t r = fread(buf, 1, BLOCK_SIZE, f);
  1861. add_buffer(buf, r);
  1862. }
  1863. if (env->line_no && env->lines[env->line_no-1] && env->lines[env->line_no-1]->actual == 0) {
  1864. /* Remove blank line from end */
  1865. remove_line(env->lines, env->line_no-1);
  1866. }
  1867. if (global_config.hilight_on_open) {
  1868. env->syntax = match_syntax(file);
  1869. for (int i = 0; i < env->line_count; ++i) {
  1870. recalculate_syntax(env->lines[i],i);
  1871. }
  1872. }
  1873. env->loading = 0;
  1874. for (int i = 0; i < env->line_count; ++i) {
  1875. recalculate_tabs(env->lines[i]);
  1876. }
  1877. update_title();
  1878. goto_line(0);
  1879. fclose(f);
  1880. }
  1881. /**
  1882. * Clean up the terminal and exit the editor.
  1883. */
  1884. void quit(void) {
  1885. mouse_disable();
  1886. set_buffered();
  1887. reset();
  1888. clear_screen();
  1889. show_cursor();
  1890. printf("Thanks for flying bim!\n");
  1891. exit(0);
  1892. }
  1893. /**
  1894. * Try to quit, but don't continue if there are
  1895. * modified buffers open.
  1896. */
  1897. void try_quit(void) {
  1898. for (int i = 0; i < buffers_len; i++ ) {
  1899. buffer_t * _env = buffers[i];
  1900. if (_env->modified) {
  1901. if (_env->file_name) {
  1902. render_error("Modifications made to file `%s` in tab %d. Aborting.", _env->file_name, i+1);
  1903. } else {
  1904. render_error("Unsaved new file in tab %d. Aborting.", i+1);
  1905. }
  1906. return;
  1907. }
  1908. }
  1909. quit();
  1910. }
  1911. /**
  1912. * Switch to the previous buffer
  1913. */
  1914. void previous_tab(void) {
  1915. buffer_t * last = NULL;
  1916. for (int i = 0; i < buffers_len; i++) {
  1917. buffer_t * _env = buffers[i];
  1918. if (_env == env) {
  1919. if (last) {
  1920. /* Wrap around */
  1921. env = last;
  1922. redraw_all();
  1923. return;
  1924. } else {
  1925. env = buffers[buffers_len-1];
  1926. redraw_all();
  1927. return;
  1928. }
  1929. }
  1930. last = _env;
  1931. }
  1932. }
  1933. /**
  1934. * Switch to the next buffer
  1935. */
  1936. void next_tab(void) {
  1937. for (int i = 0; i < buffers_len; i++) {
  1938. buffer_t * _env = buffers[i];
  1939. if (_env == env) {
  1940. if (i != buffers_len - 1) {
  1941. env = buffers[i+1];
  1942. redraw_all();
  1943. return;
  1944. } else {
  1945. /* Wrap around */
  1946. env = buffers[0];
  1947. redraw_all();
  1948. return;
  1949. }
  1950. }
  1951. }
  1952. }
  1953. /**
  1954. * Write active buffer to file
  1955. */
  1956. void write_file(char * file) {
  1957. if (!file) {
  1958. render_error("Need a file to write to.");
  1959. return;
  1960. }
  1961. FILE * f = fopen(file, "w+");
  1962. if (!f) {
  1963. render_error("Failed to open file for writing.");
  1964. return;
  1965. }
  1966. /* Go through each line and convert it back to UTF-8 */
  1967. int i, j;
  1968. for (i = 0; i < env->line_count; ++i) {
  1969. line_t * line = env->lines[i];
  1970. for (j = 0; j < line->actual; j++) {
  1971. char_t c = line->text[j];
  1972. if (c.codepoint == 0) {
  1973. char buf[1] = {0};
  1974. fwrite(buf, 1, 1, f);
  1975. } else {
  1976. char tmp[8] = {0};
  1977. int i = to_eight(c.codepoint, tmp);
  1978. fwrite(tmp, i, 1, f);
  1979. }
  1980. }
  1981. fputc('\n', f);
  1982. }
  1983. fclose(f);
  1984. /* Mark it no longer modified */
  1985. env->modified = 0;
  1986. /* If there was no file name set, set one */
  1987. if (!env->file_name) {
  1988. env->file_name = malloc(strlen(file) + 1);
  1989. memcpy(env->file_name, file, strlen(file) + 1);
  1990. }
  1991. redraw_all();
  1992. }
  1993. /**
  1994. * Close the active buffer
  1995. */
  1996. void close_buffer(void) {
  1997. buffer_t * previous_env = env;
  1998. buffer_t * new_env = buffer_close(env);
  1999. if (new_env == previous_env) {
  2000. /* ?? Failed to close buffer */
  2001. render_error("lolwat");
  2002. }
  2003. /* No more buffers, exit */
  2004. if (!new_env) {
  2005. quit();
  2006. }
  2007. /* Clean up the old buffer */
  2008. free(previous_env);
  2009. /* Set the new active buffer */
  2010. env = new_env;
  2011. /* Redraw the screen */
  2012. redraw_all();
  2013. }
  2014. /**
  2015. * Move the cursor down one line in the text region
  2016. */
  2017. void cursor_down(void) {
  2018. /* If this isn't already the last line... */
  2019. if (env->line_no < env->line_count) {
  2020. /* Move the cursor down */
  2021. env->line_no += 1;
  2022. /*
  2023. * If the horizontal cursor position exceeds the width the new line,
  2024. * then move the cursor left to the extent of the new line.
  2025. *
  2026. * If we're in insert mode, we can go one cell beyond the end of the line
  2027. */
  2028. if (env->col_no > env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT)) {
  2029. env->col_no = env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT);
  2030. if (env->col_no == 0) env->col_no = 1;
  2031. }
  2032. /*
  2033. * If the screen was scrolled horizontally, unscroll it;
  2034. * if it will be scrolled on this line as well, that will
  2035. * be handled by place_cursor_actual
  2036. */
  2037. int redraw = 0;
  2038. if (env->coffset != 0) {
  2039. env->coffset = 0;
  2040. redraw = 1;
  2041. }
  2042. /* If we've scrolled past the bottom of the screen, scroll the screen */
  2043. if (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - 1) {
  2044. env->offset += 1;
  2045. /* Tell terminal to scroll */
  2046. shift_up();
  2047. /* A new line appears on screen at the bottom, draw it */
  2048. int l = global_config.term_height - global_config.bottom_size - 1;
  2049. if (env->offset + l < env->line_count + 1) {
  2050. redraw_line(l-1, env->offset + l-1);
  2051. } else {
  2052. place_cursor(1, 2 + l - 1);
  2053. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  2054. printf("~");
  2055. clear_to_end();
  2056. }
  2057. /* Redraw elements that were moved by scrolling */
  2058. redraw_tabbar();
  2059. redraw_statusbar();
  2060. redraw_commandline();
  2061. place_cursor_actual();
  2062. return;
  2063. } else if (redraw) {
  2064. /* Otherwise, if we need to redraw because of coffset change, do that */
  2065. redraw_text();
  2066. }
  2067. /* Update the status bar */
  2068. redraw_statusbar();
  2069. /* Place the terminal cursor again */
  2070. place_cursor_actual();
  2071. }
  2072. }
  2073. /**
  2074. * Move the cursor up one line in the text region
  2075. */
  2076. void cursor_up(void) {
  2077. /* If this isn't the first line already */
  2078. if (env->line_no > 1) {
  2079. /* Move the cursor down */
  2080. env->line_no -= 1;
  2081. /*
  2082. * If the horizontal cursor position exceeds the width the new line,
  2083. * then move the cursor left to the extent of the new line.
  2084. *
  2085. * If we're in insert mode, we can go one cell beyond the end of the line
  2086. */
  2087. if (env->col_no > env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT)) {
  2088. env->col_no = env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT);
  2089. if (env->col_no == 0) env->col_no = 1;
  2090. }
  2091. /*
  2092. * If the screen was scrolled horizontally, unscroll it;
  2093. * if it will be scrolled on this line as well, that will
  2094. * be handled by place_cursor_actual
  2095. */
  2096. int redraw = 0;
  2097. if (env->coffset != 0) {
  2098. env->coffset = 0;
  2099. redraw = 1;
  2100. }
  2101. if (env->line_no <= env->offset) {
  2102. env->offset -= 1;
  2103. /* Tell terminal to scroll */
  2104. shift_down();
  2105. /*
  2106. * The line at the top of the screen should always be real
  2107. * so we can just call redraw_line here
  2108. */
  2109. redraw_line(0,env->offset);
  2110. /* Redraw elements that were moved by scrolling */
  2111. redraw_tabbar();
  2112. redraw_statusbar();
  2113. redraw_commandline();
  2114. place_cursor_actual();
  2115. return;
  2116. } else if (redraw) {
  2117. /* Otherwise, if we need to redraw because of coffset change, do that */
  2118. redraw_text();
  2119. }
  2120. /* Update the status bar */
  2121. redraw_statusbar();
  2122. /* Place the terminal cursor again */
  2123. place_cursor_actual();
  2124. }
  2125. }
  2126. /**
  2127. * Move the cursor one column left.
  2128. */
  2129. void cursor_left(void) {
  2130. if (env->col_no > 1) {
  2131. env->col_no -= 1;
  2132. /* Update the status bar */
  2133. redraw_statusbar();
  2134. /* Place the terminal cursor again */
  2135. place_cursor_actual();
  2136. }
  2137. }
  2138. /**
  2139. * Move the cursor one column right.
  2140. */
  2141. void cursor_right(void) {
  2142. /* If this isn't already the rightmost column we can reach on this line in this mode... */
  2143. if (env->col_no < env->lines[env->line_no-1]->actual + !!(env->mode == MODE_INSERT)) {
  2144. env->col_no += 1;
  2145. /* Update the status bar */
  2146. redraw_statusbar();
  2147. /* Place the terminal cursor again */
  2148. place_cursor_actual();
  2149. }
  2150. }
  2151. /**
  2152. * Move the cursor to the fron the of the line
  2153. */
  2154. void cursor_home(void) {
  2155. env->col_no = 1;
  2156. /* Update the status bar */
  2157. redraw_statusbar();
  2158. /* Place the terminal cursor again */
  2159. place_cursor_actual();
  2160. }
  2161. /**
  2162. * Move the cursor to the end of the line.
  2163. *
  2164. * In INSERT mode, moves one cell right of the end of the line.
  2165. * In NORMAL mode, moves the cursor to the last occupied cell.
  2166. */
  2167. void cursor_end(void) {
  2168. env->col_no = env->lines[env->line_no-1]->actual+!!(env->mode == MODE_INSERT);
  2169. /* Update the status bar */
  2170. redraw_statusbar();
  2171. /* Place the terminal cursor again */
  2172. place_cursor_actual();
  2173. }
  2174. /**
  2175. * Leave INSERT mode
  2176. *
  2177. * If the cursor is too far right, adjust it.
  2178. * Redraw the command line.
  2179. */
  2180. void leave_insert(void) {
  2181. if (env->col_no > env->lines[env->line_no-1]->actual) {
  2182. env->col_no = env->lines[env->line_no-1]->actual;
  2183. if (env->col_no == 0) env->col_no = 1;
  2184. }
  2185. env->mode = MODE_NORMAL;
  2186. redraw_commandline();
  2187. }
  2188. /**
  2189. * Process a user command.
  2190. */
  2191. void process_command(char * cmd) {
  2192. /* Special case ! to run shell commands without parsing tokens */
  2193. if (*cmd == '!') {
  2194. /* Reset and draw some line feeds */
  2195. reset();
  2196. printf("\n\n");
  2197. /* Set buffered for shell application */
  2198. set_buffered();
  2199. /* Call the shell and wait for completion */
  2200. system(&cmd[1]);
  2201. /* Return to the editor, wait for user to press enter. */
  2202. set_unbuffered();
  2203. printf("\n\nPress ENTER to continue.");
  2204. fflush(stdout);
  2205. while (bim_getch() != ENTER_KEY);
  2206. /* Redraw the screen */
  2207. redraw_all();
  2208. /* Done processing command */
  2209. return;
  2210. }
  2211. /* Tokenize argument string on spaces */
  2212. char *p, *argv[512], *last;
  2213. int argc = 0;
  2214. for ((p = strtok_r(cmd, " ", &last)); p;
  2215. (p = strtok_r(NULL, " ", &last)), argc++) {
  2216. if (argc < 511) argv[argc] = p;
  2217. }
  2218. argv[argc] = NULL;
  2219. if (argc < 1) {
  2220. /* no op */
  2221. return;
  2222. }
  2223. if (!strcmp(argv[0], "e")) {
  2224. /* e: edit file */
  2225. if (argc > 1) {
  2226. /* This actually opens a new tab */
  2227. open_file(argv[1]);
  2228. } else {
  2229. /* TODO: Reopen file? */
  2230. render_error("Expected a file to open...");
  2231. }
  2232. } else if (!strcmp(argv[0], "tabnew")) {
  2233. env = buffer_new();
  2234. setup_buffer(env);
  2235. redraw_all();
  2236. } else if (!strcmp(argv[0], "w")) {
  2237. /* w: write file */
  2238. if (argc > 1) {
  2239. write_file(argv[1]);
  2240. } else {
  2241. write_file(env->file_name);
  2242. }
  2243. } else if (!strcmp(argv[0], "wq")) {
  2244. /* wq: write file and close buffer; if there's no file to write to, may do weird things */
  2245. write_file(env->file_name);
  2246. close_buffer();
  2247. } else if (!strcmp(argv[0], "q")) {
  2248. /* close buffer if unmodified */
  2249. if (env->modified) {
  2250. render_error("No write since last change. Use :q! to force exit.");
  2251. } else {
  2252. close_buffer();
  2253. }
  2254. } else if (!strcmp(argv[0], "q!")) {
  2255. /* close buffer without warning if unmodified */
  2256. close_buffer();
  2257. } else if (!strcmp(argv[0], "qa") || !strcmp(argv[0], "qall")) {
  2258. /* Close all */
  2259. try_quit();
  2260. } else if (!strcmp(argv[0], "qa!")) {
  2261. /* Forcefully exit editor */
  2262. quit();
  2263. } else if (!strcmp(argv[0], "tabp")) {
  2264. /* Next tab */
  2265. previous_tab();
  2266. } else if (!strcmp(argv[0], "tabn")) {
  2267. /* Previous tab */
  2268. next_tab();
  2269. } else if (!strcmp(argv[0], "noh")) {
  2270. if (env->search) {
  2271. free(env->search);
  2272. redraw_text();
  2273. }
  2274. } else if (!strcmp(argv[0], "help")) {
  2275. /*
  2276. * The repeated calls to redraw_commandline here make use
  2277. * of scrolling to draw this multiline help message on
  2278. * the same background as the command line.
  2279. */
  2280. redraw_commandline(); printf("\n");
  2281. redraw_commandline(); printf(" \033[1mbim - The standard ToaruOS Text Editor\033[22m\n");
  2282. redraw_commandline(); printf("\n");
  2283. redraw_commandline(); printf(" Available commands:\n");
  2284. redraw_commandline(); printf(" Quit with \033[3m:q\033[23m, \033[3m:qa\033[23m, \033[3m:q!\033[23m, \033[3m:qa!\033[23m\n");
  2285. redraw_commandline(); printf(" Write out with \033[3m:w \033[4mfile\033[24;23m\n");
  2286. redraw_commandline(); printf(" Set syntax with \033[3m:syntax \033[4mlanguage\033[24;23m\n");
  2287. redraw_commandline(); printf(" Open a new tab with \033[3m:e \033[4mpath/to/file\033[24;23m\n");
  2288. redraw_commandline(); printf(" \033[3m:tabn\033[23m and \033[3m:tabp\033[23m can be used to switch tabs\n");
  2289. redraw_commandline(); printf(" Set the color scheme with \033[3m:theme \033[4mtheme\033[24;23m\n");
  2290. redraw_commandline(); printf(" Set the behavior of the tab key with \033[3m:tabs\033[23m or \033[3m:spaces\033[23m\n");
  2291. redraw_commandline(); printf(" Set tabstop with \033[3m:tabstop \033[4mwidth\033[24;23m\n");
  2292. redraw_commandline(); printf("\n");
  2293. redraw_commandline(); printf(" Copyright 2013-2018 K. Lange <\033[3mklange@toaruos.org\033[23m>\n");
  2294. redraw_commandline(); printf("\n");
  2295. /* Redrawing the tabbar makes it look like we just shifted the whole view up */
  2296. redraw_tabbar();
  2297. redraw_commandline();
  2298. fflush(stdout);
  2299. /* Wait for a character so we can redraw the screen before continuing */
  2300. int c;
  2301. while ((c = bim_getch())== -1);
  2302. /* Make sure that key press actually gets used */
  2303. bim_unget(c);
  2304. /*
  2305. * Redraw everything to hide the help message and get the
  2306. * upper few lines of text on screen again
  2307. */
  2308. redraw_all();
  2309. } else if (!strcmp(argv[0], "theme")) {
  2310. if (argc < 2) {
  2311. return;
  2312. }
  2313. for (struct theme_def * d = themes; d->name; ++d) {
  2314. if (!strcmp(argv[1], d->name)) {
  2315. d->load();
  2316. redraw_all();
  2317. return;
  2318. }
  2319. }
  2320. } else if (!strcmp(argv[0], "syntax")) {
  2321. if (argc < 2) {
  2322. render_status_message("syntax=%s", env->syntax ? env->syntax->name : "none");
  2323. return;
  2324. }
  2325. if (!strcmp(argv[1],"none")) {
  2326. for (int i = 0; i < env->line_count; ++i) {
  2327. env->lines[i]->istate = 0;
  2328. for (int j = 0; j < env->lines[i]->actual; ++j) {
  2329. env->lines[i]->text[j].flags = 0;
  2330. }
  2331. }
  2332. redraw_all();
  2333. return;
  2334. }
  2335. for (struct syntax_definition * s = syntaxes; s->name; ++s) {
  2336. if (!strcmp(argv[1],s->name)) {
  2337. env->syntax = s;
  2338. for (int i = 0; i < env->line_count; ++i) {
  2339. env->lines[i]->istate = 0;
  2340. }
  2341. for (int i = 0; i < env->line_count; ++i) {
  2342. recalculate_syntax(env->lines[i],i);
  2343. }
  2344. redraw_all();
  2345. return;
  2346. }
  2347. }
  2348. render_error("unrecognized syntax type");
  2349. } else if (!strcmp(argv[0], "recalc")) {
  2350. for (int i = 0; i < env->line_count; ++i) {
  2351. env->lines[i]->istate = 0;
  2352. }
  2353. for (int i = 0; i < env->line_count; ++i) {
  2354. recalculate_syntax(env->lines[i],i);
  2355. }
  2356. redraw_all();
  2357. } else if (!strcmp(argv[0], "tabs")) {
  2358. env->tabs = 1;
  2359. } else if (!strcmp(argv[0], "spaces")) {
  2360. env->tabs = 0;
  2361. } else if (!strcmp(argv[0], "tabstop")) {
  2362. if (argc < 2) {
  2363. render_status_message("tabstop=%d", env->tabstop);
  2364. } else {
  2365. int t = atoi(argv[1]);
  2366. if (t > 0 && t < 32) {
  2367. env->tabstop = t;
  2368. for (int i = 0; i < env->line_count; ++i) {
  2369. recalculate_tabs(env->lines[i]);
  2370. }
  2371. redraw_all();
  2372. } else {
  2373. render_error("Invalid tabstop: %s", argv[1]);
  2374. }
  2375. }
  2376. } else if (!strcmp(argv[0], "clearyank")) {
  2377. if (global_config.yanks) {
  2378. for (unsigned int i = 0; i < global_config.yank_count; ++i) {
  2379. free(global_config.yanks[i]);
  2380. }
  2381. free(global_config.yanks);
  2382. global_config.yanks = NULL;
  2383. global_config.yank_count = 0;
  2384. redraw_statusbar();
  2385. }
  2386. } else if (isdigit(*argv[0])) {
  2387. /* Go to line number */
  2388. goto_line(atoi(argv[0]));
  2389. } else {
  2390. /* Unrecognized command */
  2391. render_error("Not an editor command: %s", argv[0]);
  2392. }
  2393. }
  2394. /**
  2395. * Tab completion for command mode.
  2396. */
  2397. void command_tab_complete(char * buffer) {
  2398. /* Figure out which argument this is and where it starts */
  2399. int arg = 0;
  2400. char * buf = strdup(buffer);
  2401. char * b = buf;
  2402. char * args[32];
  2403. int candidate_count= 0;
  2404. int candidate_space = 4;
  2405. char ** candidates = malloc(sizeof(char*)*candidate_space);
  2406. /* Accept whitespace before first argument */
  2407. while (*b == ' ') b++;
  2408. char * start = b;
  2409. args[0] = start;
  2410. while (*b && *b != ' ') b++;
  2411. while (*b) {
  2412. while (*b == ' ') {
  2413. *b = '\0';
  2414. b++;
  2415. }
  2416. start = b;
  2417. arg++;
  2418. if (arg < 32) {
  2419. args[arg] = start;
  2420. }
  2421. while (*b && *b != ' ') b++;
  2422. }
  2423. /**
  2424. * Check a possible candidate and add it to the
  2425. * candidates list, expanding as necessary,
  2426. * if it matches for the current argument.
  2427. */
  2428. void add_candidate(const char * candidate) {
  2429. char * _arg = args[arg];
  2430. int r = strncmp(_arg, candidate, strlen(_arg));
  2431. if (!r) {
  2432. if (candidate_count == candidate_space) {
  2433. candidate_space *= 2;
  2434. candidates = realloc(candidates,sizeof(char *) * candidate_space);
  2435. }
  2436. candidates[candidate_count] = strdup(candidate);
  2437. candidate_count++;
  2438. }
  2439. }
  2440. if (arg == 0) {
  2441. /* Complete command names */
  2442. add_candidate("help");
  2443. add_candidate("recalc");
  2444. add_candidate("syntax");
  2445. add_candidate("tabn");
  2446. add_candidate("tabp");
  2447. add_candidate("tabnew");
  2448. add_candidate("theme");
  2449. add_candidate("tabs");
  2450. add_candidate("tabstop");
  2451. add_candidate("spaces");
  2452. add_candidate("noh");
  2453. add_candidate("clearyank");
  2454. goto _accept_candidate;
  2455. }
  2456. if (arg == 1 && !strcmp(args[0], "syntax")) {
  2457. /* Complete syntax options */
  2458. add_candidate("none");
  2459. for (struct syntax_definition * s = syntaxes; s->name; ++s) {
  2460. add_candidate(s->name);
  2461. }
  2462. goto _accept_candidate;
  2463. }
  2464. if (arg == 1 && !strcmp(args[0], "theme")) {
  2465. /* Complete color theme names */
  2466. for (struct theme_def * s = themes; s->name; ++s) {
  2467. add_candidate(s->name);
  2468. }
  2469. goto _accept_candidate;
  2470. }
  2471. if (arg == 1 && !strcmp(args[0], "e")) {
  2472. /* Complete file paths */
  2473. /* First, find the deepest directory match */
  2474. char * tmp = strdup(args[arg]);
  2475. char * last_slash = strrchr(tmp, '/');
  2476. DIR * dirp;
  2477. if (last_slash) {
  2478. *last_slash = '\0';
  2479. if (last_slash == tmp) {
  2480. /* Started with slash, and it was the only slash */
  2481. dirp = opendir("/");
  2482. } else {
  2483. dirp = opendir(tmp);
  2484. }
  2485. } else {
  2486. /* No directory match, completing from current directory */
  2487. dirp = opendir(".");
  2488. tmp[0] = '\0';
  2489. }
  2490. if (!dirp) {
  2491. /* Directory match doesn't exist, no candidates to populate */
  2492. free(tmp);
  2493. goto done;
  2494. }
  2495. struct dirent * ent = readdir(dirp);
  2496. while (ent != NULL) {
  2497. if (ent->d_name[0] != '.') {
  2498. struct stat statbuf;
  2499. /* Figure out if this file is a directory */
  2500. if (last_slash) {
  2501. char * x = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1);
  2502. sprintf(x,"%s/%s",tmp,ent->d_name);
  2503. lstat(x, &statbuf);
  2504. free(x);
  2505. } else {
  2506. lstat(ent->d_name, &statbuf);
  2507. }
  2508. /* Build the complete argument name to tab complete */
  2509. char s[1024] = {0};
  2510. if (last_slash == tmp) {
  2511. strcat(s,"/");
  2512. } else if (*tmp) {
  2513. strcat(s,tmp);
  2514. strcat(s,"/");
  2515. }
  2516. strcat(s,ent->d_name);
  2517. /*
  2518. * If it is a directory, add a / to the end so the next completion
  2519. * attempt will complete the directory's contents.
  2520. */
  2521. if (S_ISDIR(statbuf.st_mode)) {
  2522. strcat(s,"/");
  2523. }
  2524. add_candidate(s);
  2525. }
  2526. ent = readdir(dirp);
  2527. }
  2528. closedir(dirp);
  2529. free(tmp);
  2530. goto _accept_candidate;
  2531. }
  2532. _accept_candidate:
  2533. if (candidate_count == 0) {
  2534. redraw_statusbar();
  2535. goto done;
  2536. }
  2537. if (candidate_count == 1) {
  2538. /* Only one completion possibility */
  2539. redraw_statusbar();
  2540. /* Fill out the rest of the command */
  2541. char * cstart = (buffer) + (start - buf);
  2542. for (unsigned int i = 0; i < strlen(candidates[0]); ++i) {
  2543. *cstart = candidates[0][i];
  2544. cstart++;
  2545. }
  2546. *cstart = '\0';
  2547. } else {
  2548. /* Print candidates in status bar */
  2549. char tmp[global_config.term_width+1];
  2550. memset(tmp, 0, global_config.term_width+1);
  2551. int offset = 0;
  2552. for (int i = 0; i < candidate_count; ++i) {
  2553. if (offset + 1 + (signed)strlen(candidates[i]) > global_config.term_width - 5) {
  2554. strcat(tmp, "...");
  2555. break;
  2556. }
  2557. if (offset > 0) {
  2558. strcat(tmp, " ");
  2559. offset++;
  2560. }
  2561. strcat(tmp, candidates[i]);
  2562. offset += strlen(candidates[i]);
  2563. }
  2564. render_status_message("%s", tmp);
  2565. /* Complete to longest common substring */
  2566. char * cstart = (buffer) + (start - buf);
  2567. for (int i = 0; i < 1023 /* max length of command */; i++) {
  2568. for (int j = 1; j < candidate_count; ++j) {
  2569. if (candidates[0][i] != candidates[j][i]) goto _reject;
  2570. }
  2571. *cstart = candidates[0][i];
  2572. cstart++;
  2573. }
  2574. /* End of longest common substring */
  2575. _reject:
  2576. *cstart = '\0';
  2577. }
  2578. /* Free candidates */
  2579. for (int i = 0; i < candidate_count; ++i) {
  2580. free(candidates[i]);
  2581. }
  2582. /* Redraw command line */
  2583. done:
  2584. redraw_commandline();
  2585. printf(":%s", buffer);
  2586. free(candidates);
  2587. free(buf);
  2588. }
  2589. /**
  2590. * Command mode
  2591. *
  2592. * Accept a user command and then process it and
  2593. * return to normal mode.
  2594. *
  2595. * TODO: We only have basic line editing here; it might be
  2596. * nice to add more advanced line editing, like cursor
  2597. * movement, tab completion, etc. This is easier than
  2598. * with the shell since we have a lot more control over
  2599. * where the command input bar is rendered.
  2600. */
  2601. void command_mode(void) {
  2602. char c;
  2603. char buffer[1024] = {0};
  2604. int buffer_len = 0;
  2605. redraw_commandline();
  2606. printf(":");
  2607. show_cursor();
  2608. while ((c = bim_getch())) {
  2609. if (c == -1) {
  2610. /* Time out */
  2611. continue;
  2612. }
  2613. if (c == '\033') {
  2614. /* Escape, cancel command */
  2615. break;
  2616. } else if (c == ENTER_KEY) {
  2617. /* Enter, run command */
  2618. process_command(buffer);
  2619. break;
  2620. } else if (c == '\t') {
  2621. /* Handle tab completion */
  2622. command_tab_complete(buffer);
  2623. buffer_len = strlen(buffer);
  2624. } else if (c == BACKSPACE_KEY || c == DELETE_KEY) {
  2625. /* Backspace, delete last character in command buffer */
  2626. if (buffer_len > 0) {
  2627. buffer_len -= 1;
  2628. buffer[buffer_len] = '\0';
  2629. redraw_commandline();
  2630. printf(":%s", buffer);
  2631. } else {
  2632. /* If backspaced through entire command, cancel command mode */
  2633. redraw_commandline();
  2634. break;
  2635. }
  2636. } else {
  2637. /* Regular character */
  2638. buffer[buffer_len] = c;
  2639. buffer_len++;
  2640. printf("%c", c);
  2641. }
  2642. show_cursor();
  2643. }
  2644. }
  2645. /**
  2646. * Search forward from the given cursor position
  2647. * to find a basic search match.
  2648. *
  2649. * This could be more complicated...
  2650. */
  2651. void find_match(int from_line, int from_col, int * out_line, int * out_col, char * str) {
  2652. int col = from_col;
  2653. for (int i = from_line; i <= env->line_count; ++i) {
  2654. line_t * line = env->lines[i - 1];
  2655. int j = col - 1;
  2656. while (j < line->actual + 1) {
  2657. int k = j;
  2658. char * match = str;
  2659. while (k < line->actual + 1) {
  2660. if (*match == '\0') {
  2661. *out_line = i;
  2662. *out_col = j + 1;
  2663. return;
  2664. }
  2665. /* TODO search for UTF-8 sequences? */
  2666. if (*match != line->text[k].codepoint) break;
  2667. match++;
  2668. k++;
  2669. }
  2670. j++;
  2671. }
  2672. col = 0;
  2673. }
  2674. }
  2675. /**
  2676. * Draw the matched search result.
  2677. */
  2678. void draw_search_match(int line, char * buffer, int redraw_buffer) {
  2679. place_cursor_actual();
  2680. redraw_text();
  2681. if (line != -1) {
  2682. /*
  2683. * TODO this should probably mark the relevant
  2684. * regions so that redraw_text can hilight it
  2685. */
  2686. set_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG);
  2687. place_cursor_actual();
  2688. printf("%s", buffer);
  2689. }
  2690. redraw_statusbar();
  2691. redraw_commandline();
  2692. if (redraw_buffer) {
  2693. printf("/%s", buffer);
  2694. }
  2695. }
  2696. /**
  2697. * Search mode
  2698. *
  2699. * Search text for substring match.
  2700. */
  2701. void search_mode(void) {
  2702. char c;
  2703. char buffer[1024] = {0};
  2704. int buffer_len = 0;
  2705. /* Remember where the cursor is so we can cancel */
  2706. int prev_line = env->line_no;
  2707. int prev_col = env->col_no;
  2708. int prev_coffset = env->coffset;
  2709. int prev_offset = env->offset;
  2710. redraw_commandline();
  2711. printf("/");
  2712. show_cursor();
  2713. while ((c = bim_getch())) {
  2714. if (c == -1) {
  2715. /* Time out */
  2716. continue;
  2717. }
  2718. if (c == '\033') {
  2719. /* Cancel search */
  2720. env->line_no = prev_line;
  2721. env->col_no = prev_col;
  2722. redraw_all();
  2723. break;
  2724. } else if (c == ENTER_KEY) {
  2725. /* Exit search */
  2726. if (env->search) {
  2727. free(env->search);
  2728. }
  2729. env->search = strdup(buffer);
  2730. break;
  2731. } else if (c == BACKSPACE_KEY || c == DELETE_KEY) {
  2732. /* Backspace, delete last character in search buffer */
  2733. if (buffer_len > 0) {
  2734. buffer_len -= 1;
  2735. buffer[buffer_len] = '\0';
  2736. /* Search from beginning to find first match */
  2737. int line = -1, col = -1;
  2738. find_match(prev_line, prev_col, &line, &col, buffer);
  2739. if (line != -1) {
  2740. env->coffset = 0;
  2741. env->offset = line - 1;
  2742. env->col_no = col;
  2743. env->line_no = line;
  2744. }
  2745. draw_search_match(line, buffer, 1);
  2746. } else {
  2747. /* If backspaced through entire search term, cancel search */
  2748. redraw_commandline();
  2749. env->coffset = prev_coffset;
  2750. env->offset = prev_offset;
  2751. env->col_no = prev_col;
  2752. env->line_no = prev_line;
  2753. redraw_all();
  2754. break;
  2755. }
  2756. } else {
  2757. /* Regular character */
  2758. buffer[buffer_len] = c;
  2759. buffer_len++;
  2760. buffer[buffer_len] = '\0';
  2761. printf("%c", c);
  2762. /* Find the next search match */
  2763. int line = -1, col = -1;
  2764. find_match(prev_line, prev_col, &line, &col, buffer);
  2765. if (line != -1) {
  2766. env->coffset = 0;
  2767. env->offset = line - 1;
  2768. env->col_no = col;
  2769. env->line_no = line;
  2770. } else {
  2771. env->coffset = prev_coffset;
  2772. env->offset = prev_offset;
  2773. env->col_no = prev_col;
  2774. env->line_no = prev_line;
  2775. }
  2776. draw_search_match(line, buffer, 1);
  2777. }
  2778. show_cursor();
  2779. }
  2780. }
  2781. /**
  2782. * Find the next search result, or loop back around if at the end.
  2783. */
  2784. void search_next(void) {
  2785. if (!env->search) return;
  2786. int line = -1, col = -1;
  2787. find_match(env->line_no, env->col_no+1, &line, &col, env->search);
  2788. if (line == -1) {
  2789. find_match(1,1, &line, &col, env->search);
  2790. if (line == -1) return;
  2791. }
  2792. env->coffset = 0;
  2793. env->offset = line - 1;
  2794. env->col_no = col;
  2795. env->line_no = line;
  2796. draw_search_match(line, env->search, 0);
  2797. }
  2798. /**
  2799. * Handle mouse event
  2800. */
  2801. void handle_mouse(void) {
  2802. int buttons = bim_getch() - 32;
  2803. int x = bim_getch() - 32;
  2804. int y = bim_getch() - 32;
  2805. if (buttons == 64) {
  2806. /* Scroll up */
  2807. for (int i = 0; i < 5; ++i) {
  2808. cursor_up();
  2809. }
  2810. return;
  2811. } else if (buttons == 65) {
  2812. /* Scroll down */
  2813. for (int i = 0; i < 5; ++i) {
  2814. cursor_down();
  2815. }
  2816. return;
  2817. } else if (buttons == 3) {
  2818. /* Move cursor to position */
  2819. if (x < 0) return;
  2820. if (y < 0) return;
  2821. if (y == 1) {
  2822. /* Pick from tabs */
  2823. int _x = 0;
  2824. for (int i = 0; i < buffers_len; i++) {
  2825. buffer_t * _env = buffers[i];
  2826. if (_env->modified) {
  2827. _x += 2;
  2828. }
  2829. if (_env->file_name) {
  2830. _x += 2 + strlen(_env->file_name);
  2831. } else {
  2832. _x += strlen(" [No Name] ");
  2833. }
  2834. if (_x > x) {
  2835. env = buffers[i];
  2836. redraw_all();
  2837. return;
  2838. }
  2839. }
  2840. return;
  2841. }
  2842. /* Figure out y coordinate */
  2843. int line_no = y + env->offset - 1;
  2844. int col_no = -1;
  2845. if (line_no > env->line_count) {
  2846. line_no = env->line_count;
  2847. }
  2848. /* Account for the left hand gutter */
  2849. int num_size = num_width() + 3;
  2850. int _x = num_size - (line_no == env->line_no ? env->coffset : 0);
  2851. /* Determine where the cursor is physically */
  2852. for (int i = 0; i < env->lines[line_no-1]->actual; ++i) {
  2853. char_t * c = &env->lines[line_no-1]->text[i];
  2854. _x += c->display_width;
  2855. if (_x > x) {
  2856. col_no = i;
  2857. break;
  2858. }
  2859. }
  2860. if (col_no == -1 || col_no > env->lines[line_no-1]->actual) {
  2861. col_no = env->lines[line_no-1]->actual;
  2862. }
  2863. env->line_no = line_no;
  2864. env->col_no = col_no;
  2865. place_cursor_actual();
  2866. }
  2867. return;
  2868. }
  2869. /**
  2870. * Append a character at the current cursor point.
  2871. */
  2872. void insert_char(unsigned int c) {
  2873. char_t _c;
  2874. _c.codepoint = c;
  2875. _c.flags = 0;
  2876. _c.display_width = codepoint_width(c);
  2877. line_t * line = env->lines[env->line_no - 1];
  2878. line_t * nline = line_insert(line, _c, env->col_no - 1, env->line_no - 1);
  2879. if (line != nline) {
  2880. env->lines[env->line_no - 1] = nline;
  2881. }
  2882. redraw_line(env->line_no - env->offset - 1, env->line_no-1);
  2883. env->col_no += 1;
  2884. set_modified();
  2885. }
  2886. /**
  2887. * Move the cursor the start of the previous word.
  2888. */
  2889. void word_left(void) {
  2890. int line_no = env->line_no;
  2891. int col_no = env->col_no;
  2892. do {
  2893. col_no--;
  2894. if (col_no == 0) {
  2895. line_no--;
  2896. if (line_no == 0) {
  2897. goto_line(1);
  2898. return;
  2899. }
  2900. col_no = env->lines[line_no-1]->actual + 1;
  2901. }
  2902. } while (isspace(env->lines[line_no-1]->text[col_no-1].codepoint));
  2903. do {
  2904. col_no--;
  2905. if (col_no == 0) {
  2906. line_no--;
  2907. if (line_no == 0) {
  2908. goto_line(1);
  2909. return;
  2910. }
  2911. col_no = env->lines[line_no-1]->actual + 1;
  2912. }
  2913. if (col_no == 1) {
  2914. env->col_no = 1;
  2915. env->line_no = line_no;
  2916. redraw_statusbar();
  2917. place_cursor_actual();
  2918. return;
  2919. }
  2920. } while (!isspace(env->lines[line_no-1]->text[col_no-1].codepoint));
  2921. env->col_no = col_no;
  2922. env->line_no = line_no;
  2923. cursor_right();
  2924. }
  2925. /**
  2926. * Word right
  2927. */
  2928. void word_right(void) {
  2929. int line_no = env->line_no;
  2930. int col_no = env->col_no;
  2931. do {
  2932. col_no++;
  2933. if (col_no >= env->lines[line_no-1]->actual + 1) {
  2934. line_no++;
  2935. if (line_no >= env->line_count) {
  2936. env->col_no = env->lines[env->line_count-1]->actual;
  2937. env->line_no = env->line_count;
  2938. redraw_statusbar();
  2939. place_cursor_actual();
  2940. return;
  2941. }
  2942. col_no = 0;
  2943. break;
  2944. }
  2945. } while (!isspace(env->lines[line_no-1]->text[col_no-1].codepoint));
  2946. do {
  2947. col_no++;
  2948. if (col_no >= env->lines[line_no-1]->actual + 1) {
  2949. line_no++;
  2950. if (line_no >= env->line_count) {
  2951. env->col_no = env->lines[env->line_count-1]->actual;
  2952. env->line_no = env->line_count;
  2953. redraw_statusbar();
  2954. place_cursor_actual();
  2955. return;
  2956. }
  2957. col_no = 1;
  2958. break;
  2959. }
  2960. } while (isspace(env->lines[line_no-1]->text[col_no-1].codepoint));
  2961. env->col_no = col_no;
  2962. env->line_no = line_no;
  2963. redraw_statusbar();
  2964. place_cursor_actual();
  2965. return;
  2966. }
  2967. int handle_escape(int * this_buf, int * timeout, int c) {
  2968. if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '\033') {
  2969. this_buf[*timeout] = c;
  2970. (*timeout)++;
  2971. return 1;
  2972. }
  2973. if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '[') {
  2974. *timeout = 1;
  2975. this_buf[*timeout] = c;
  2976. (*timeout)++;
  2977. return 0;
  2978. }
  2979. if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[' &&
  2980. (isdigit(c) || c == ';')) {
  2981. this_buf[*timeout] = c;
  2982. (*timeout)++;
  2983. return 0;
  2984. }
  2985. if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[') {
  2986. switch (c) {
  2987. case 'M':
  2988. handle_mouse();
  2989. break;
  2990. case 'A': // up
  2991. cursor_up();
  2992. break;
  2993. case 'B': // down
  2994. cursor_down();
  2995. break;
  2996. case 'C': // right
  2997. if (this_buf[*timeout-1] == '5') {
  2998. word_right();
  2999. } else {
  3000. cursor_right();
  3001. }
  3002. break;
  3003. case 'D': // left
  3004. if (this_buf[*timeout-1] == '5') {
  3005. word_left();
  3006. } else {
  3007. cursor_left();
  3008. }
  3009. break;
  3010. case 'H': // home
  3011. cursor_home();
  3012. break;
  3013. case 'F': // end
  3014. cursor_end();
  3015. break;
  3016. case '~':
  3017. switch (this_buf[*timeout-1]) {
  3018. case '6':
  3019. goto_line(env->line_no + global_config.term_height - 6);
  3020. break;
  3021. case '5':
  3022. goto_line(env->line_no - (global_config.term_height - 6));
  3023. break;
  3024. case '3':
  3025. if (env->mode == MODE_INSERT) {
  3026. if (env->col_no < env->lines[env->line_no - 1]->actual + 1) {
  3027. line_delete(env->lines[env->line_no - 1], env->col_no, env->line_no - 1);
  3028. redraw_line(env->line_no - env->offset - 1, env->line_no-1);
  3029. set_modified();
  3030. redraw_statusbar();
  3031. place_cursor_actual();
  3032. } else if (env->line_no < env->line_count) {
  3033. merge_lines(env->lines, env->line_no);
  3034. redraw_text();
  3035. set_modified();
  3036. redraw_statusbar();
  3037. place_cursor_actual();
  3038. }
  3039. }
  3040. break;
  3041. }
  3042. break;
  3043. default:
  3044. render_error("Unrecognized escape sequence identifier: %c", c);
  3045. break;
  3046. }
  3047. *timeout = 0;
  3048. return 0;
  3049. }
  3050. *timeout = 0;
  3051. return 0;
  3052. }
  3053. /**
  3054. * Yank lines between line start and line end (which may be in either order)
  3055. */
  3056. void yank_lines(int start, int end) {
  3057. if (global_config.yanks) {
  3058. for (unsigned int i = 0; i < global_config.yank_count; ++i) {
  3059. free(global_config.yanks[i]);
  3060. }
  3061. free(global_config.yanks);
  3062. }
  3063. int lines_to_yank;
  3064. int start_point;
  3065. if (start <= end) {
  3066. lines_to_yank = end - start + 1;
  3067. start_point = start - 1;
  3068. } else {
  3069. lines_to_yank = start - end + 1;
  3070. start_point = end - 1;
  3071. }
  3072. global_config.yanks = malloc(sizeof(line_t *) * lines_to_yank);
  3073. global_config.yank_count = lines_to_yank;
  3074. for (int i = 0; i < lines_to_yank; ++i) {
  3075. global_config.yanks[i] = malloc(sizeof(line_t) + sizeof(char_t) * (env->lines[start_point+i]->available));
  3076. global_config.yanks[i]->available = env->lines[start_point+i]->available;
  3077. global_config.yanks[i]->actual = env->lines[start_point+i]->actual;
  3078. global_config.yanks[i]->istate = 0;
  3079. memcpy(&global_config.yanks[i]->text, &env->lines[start_point+i]->text, sizeof(char_t) * (env->lines[start_point+i]->actual));
  3080. for (int j = 0; j < global_config.yanks[i]->actual; ++j) {
  3081. global_config.yanks[i]->text[j].flags = 0;
  3082. }
  3083. }
  3084. }
  3085. /**
  3086. * LINE SELECTION mode
  3087. *
  3088. * Equivalent to visual line in vim; selects lines of texts.
  3089. */
  3090. void line_selection_mode(void) {
  3091. int start_line = env->line_no;
  3092. int prev_line = start_line;
  3093. env->mode = MODE_LINE_SELECTION;
  3094. redraw_commandline();
  3095. int c;
  3096. int timeout = 0;
  3097. int this_buf[20];
  3098. for (int j = 0; j < env->lines[env->line_no-1]->actual; ++j) {
  3099. env->lines[env->line_no-1]->text[j].flags = FLAG_SELECT;
  3100. }
  3101. redraw_line(env->line_no - env->offset - 1, env->line_no-1);
  3102. void _redraw_line(int line) {
  3103. if (line == start_line) return;
  3104. if ((env->line_no < start_line && line < env->line_no) ||
  3105. (env->line_no > start_line && line > env->line_no) ||
  3106. (env->line_no == start_line && line != start_line)) {
  3107. recalculate_syntax(env->lines[line-1],line-1);
  3108. } else {
  3109. for (int j = 0; j < env->lines[line-1]->actual; ++j) {
  3110. env->lines[line-1]->text[j].flags = FLAG_SELECT;
  3111. }
  3112. }
  3113. if (line - env->offset + 1 > 1 &&
  3114. line - env->offset - 1< global_config.term_height - global_config.bottom_size - 1) {
  3115. redraw_line(line - env->offset - 1, line-1);
  3116. }
  3117. }
  3118. while ((c = bim_getch())) {
  3119. if (c == -1) {
  3120. if (timeout && this_buf[timeout-1] == '\033') {
  3121. goto _leave_select_line;
  3122. }
  3123. timeout = 0;
  3124. continue;
  3125. } else {
  3126. if (timeout == 0) {
  3127. switch (c) {
  3128. case '\033':
  3129. if (timeout == 0) {
  3130. this_buf[timeout] = c;
  3131. timeout++;
  3132. }
  3133. break;
  3134. case DELETE_KEY:
  3135. case BACKSPACE_KEY:
  3136. cursor_left();
  3137. break;
  3138. case ':':
  3139. /* Switch to command mode */
  3140. command_mode();
  3141. break;
  3142. case '/':
  3143. /* Switch to search mode */
  3144. search_mode();
  3145. break;
  3146. case 'V':
  3147. goto _leave_select_line;
  3148. case 'n':
  3149. search_next();
  3150. break;
  3151. case 'j':
  3152. cursor_down();
  3153. break;
  3154. case 'k':
  3155. cursor_up();
  3156. break;
  3157. case 'h':
  3158. cursor_left();
  3159. break;
  3160. case 'l':
  3161. cursor_right();
  3162. break;
  3163. case 'y':
  3164. yank_lines(start_line, env->line_no);
  3165. goto _leave_select_line;
  3166. case 'D':
  3167. case 'd':
  3168. yank_lines(start_line, env->line_no);
  3169. if (start_line <= env->line_no) {
  3170. int lines_to_delete = env->line_no - start_line + 1;
  3171. for (int i = 0; i < lines_to_delete; ++i) {
  3172. remove_line(env->lines, start_line-1);
  3173. }
  3174. env->line_no = start_line;
  3175. } else {
  3176. int lines_to_delete = start_line - env->line_no + 1;
  3177. for (int i = 0; i < lines_to_delete; ++i) {
  3178. remove_line(env->lines, env->line_no-1);
  3179. }
  3180. }
  3181. set_modified();
  3182. goto _leave_select_line;
  3183. case ' ':
  3184. goto_line(env->line_no + global_config.term_height - 6);
  3185. break;
  3186. case '$':
  3187. env->col_no = env->lines[env->line_no-1]->actual+1;
  3188. break;
  3189. case '0':
  3190. env->col_no = 1;
  3191. break;
  3192. }
  3193. } else {
  3194. if (handle_escape(this_buf,&timeout,c)) {
  3195. bim_unget(c);
  3196. goto _leave_select_line;
  3197. }
  3198. }
  3199. /* Mark current line */
  3200. _redraw_line(env->line_no);
  3201. /* Properly mark everything in the span we just moved through */
  3202. if (prev_line < env->line_no) {
  3203. for (int i = prev_line; i < env->line_no; ++i) {
  3204. _redraw_line(i);
  3205. }
  3206. prev_line = env->line_no;
  3207. } else if (prev_line > env->line_no) {
  3208. for (int i = env->line_no + 1; i <= prev_line; ++i) {
  3209. _redraw_line(i);
  3210. }
  3211. prev_line = env->line_no;
  3212. }
  3213. place_cursor_actual();
  3214. }
  3215. }
  3216. _leave_select_line:
  3217. env->mode = MODE_NORMAL;
  3218. for (int i = 0; i < env->line_count; ++i) {
  3219. recalculate_syntax(env->lines[i],i);
  3220. }
  3221. redraw_all();
  3222. }
  3223. /**
  3224. * INSERT mode
  3225. *
  3226. * Accept input into the text buffer.
  3227. */
  3228. void insert_mode(void) {
  3229. int cin;
  3230. uint32_t c;
  3231. /* Set mode line */
  3232. env->mode = MODE_INSERT;
  3233. redraw_commandline();
  3234. /* Place the cursor in the text area */
  3235. place_cursor_actual();
  3236. int timeout = 0;
  3237. int this_buf[20];
  3238. uint32_t istate = 0;
  3239. while ((cin = bim_getch())) {
  3240. if (cin == -1) {
  3241. if (timeout && this_buf[timeout-1] == '\033') {
  3242. leave_insert();
  3243. return;
  3244. }
  3245. timeout = 0;
  3246. continue;
  3247. }
  3248. if (!decode(&istate, &c, cin)) {
  3249. if (timeout == 0) {
  3250. switch (c) {
  3251. case '\033':
  3252. if (timeout == 0) {
  3253. this_buf[timeout] = c;
  3254. timeout++;
  3255. }
  3256. break;
  3257. case DELETE_KEY:
  3258. case BACKSPACE_KEY:
  3259. if (env->col_no > 1) {
  3260. line_delete(env->lines[env->line_no - 1], env->col_no - 1, env->line_no - 1);
  3261. env->col_no -= 1;
  3262. redraw_line(env->line_no - env->offset - 1, env->line_no-1);
  3263. set_modified();
  3264. redraw_statusbar();
  3265. place_cursor_actual();
  3266. } else if (env->line_no > 1) {
  3267. int tmp = env->lines[env->line_no - 2]->actual;
  3268. merge_lines(env->lines, env->line_no - 1);
  3269. env->line_no -= 1;
  3270. env->col_no = tmp+1;
  3271. redraw_text();
  3272. set_modified();
  3273. redraw_statusbar();
  3274. place_cursor_actual();
  3275. }
  3276. break;
  3277. case ENTER_KEY:
  3278. if (env->col_no == env->lines[env->line_no - 1]->actual + 1) {
  3279. env->lines = add_line(env->lines, env->line_no);
  3280. } else {
  3281. /* oh oh god we're all gonna die */
  3282. env->lines = split_line(env->lines, env->line_no, env->col_no - 1);
  3283. }
  3284. env->col_no = 1;
  3285. env->line_no += 1;
  3286. if (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - 1) {
  3287. env->offset += 1;
  3288. }
  3289. redraw_text();
  3290. set_modified();
  3291. redraw_statusbar();
  3292. place_cursor_actual();
  3293. break;
  3294. case '\t':
  3295. if (env->tabs) {
  3296. insert_char('\t');
  3297. } else {
  3298. for (int i = 0; i < env->tabstop; ++i) {
  3299. insert_char(' ');
  3300. }
  3301. }
  3302. redraw_statusbar();
  3303. place_cursor_actual();
  3304. break;
  3305. default:
  3306. insert_char(c);
  3307. redraw_statusbar();
  3308. place_cursor_actual();
  3309. break;
  3310. }
  3311. } else {
  3312. if (handle_escape(this_buf,&timeout,c)) {
  3313. bim_unget(c);
  3314. leave_insert();
  3315. return;
  3316. }
  3317. }
  3318. } else if (istate == UTF8_REJECT) {
  3319. istate = 0;
  3320. }
  3321. }
  3322. }
  3323. static void show_usage(char * argv[]) {
  3324. printf(
  3325. "bim - Text editor\n"
  3326. "\n"
  3327. "usage: %s [-s] [path]\n"
  3328. "\n"
  3329. " -s \033[3mdisable automatic syntax highlighting\033[0m\n"
  3330. " -? \033[3mshow this help text\033[0m\n"
  3331. "\n", argv[0]);
  3332. }
  3333. int main(int argc, char * argv[]) {
  3334. int opt;
  3335. while ((opt = getopt(argc, argv, "?sR")) != -1) {
  3336. switch (opt) {
  3337. case 's':
  3338. global_config.hilight_on_open = 0;
  3339. break;
  3340. case 'R':
  3341. global_config.initial_file_is_read_only = 1;
  3342. break;
  3343. case '?':
  3344. show_usage(argv);
  3345. return 0;
  3346. }
  3347. }
  3348. initialize();
  3349. if (argc > optind) {
  3350. open_file(argv[optind]);
  3351. if (global_config.initial_file_is_read_only) {
  3352. env->readonly = 1;
  3353. }
  3354. } else {
  3355. env = buffer_new();
  3356. update_title();
  3357. setup_buffer(env);
  3358. }
  3359. redraw_all();
  3360. while (1) {
  3361. place_cursor_actual();
  3362. int c;
  3363. int timeout = 0;
  3364. int this_buf[20];
  3365. while ((c = bim_getch())) {
  3366. if (timeout == 0) {
  3367. switch (c) {
  3368. case '\033':
  3369. if (timeout == 0) {
  3370. this_buf[timeout] = c;
  3371. timeout++;
  3372. }
  3373. break;
  3374. case DELETE_KEY:
  3375. case BACKSPACE_KEY:
  3376. cursor_left();
  3377. break;
  3378. case ':':
  3379. /* Switch to command mode */
  3380. command_mode();
  3381. break;
  3382. case '/':
  3383. /* Switch to search mode */
  3384. search_mode();
  3385. break;
  3386. case 'V':
  3387. line_selection_mode();
  3388. break;
  3389. case 'n':
  3390. search_next();
  3391. break;
  3392. case 'j':
  3393. cursor_down();
  3394. break;
  3395. case 'k':
  3396. cursor_up();
  3397. break;
  3398. case 'h':
  3399. cursor_left();
  3400. break;
  3401. case 'l':
  3402. cursor_right();
  3403. break;
  3404. case 'd':
  3405. remove_line(env->lines, env->line_no-1);
  3406. env->col_no = 1;
  3407. if (env->line_no > env->line_count) {
  3408. env->line_no--;
  3409. }
  3410. redraw_text();
  3411. set_modified();
  3412. place_cursor_actual();
  3413. break;
  3414. case ' ':
  3415. goto_line(env->line_no + global_config.term_height - 6);
  3416. break;
  3417. case 'O':
  3418. {
  3419. if (env->readonly) goto _readonly;
  3420. env->lines = add_line(env->lines, env->line_no-1);
  3421. env->col_no = 1;
  3422. redraw_text();
  3423. set_modified();
  3424. place_cursor_actual();
  3425. goto _insert;
  3426. }
  3427. case 'o':
  3428. {
  3429. if (env->readonly) goto _readonly;
  3430. env->lines = add_line(env->lines, env->line_no);
  3431. env->col_no = 1;
  3432. env->line_no += 1;
  3433. if (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - 1) {
  3434. env->offset += 1;
  3435. }
  3436. redraw_text();
  3437. set_modified();
  3438. place_cursor_actual();
  3439. goto _insert;
  3440. }
  3441. case 'a':
  3442. if (env->col_no < env->lines[env->line_no-1]->actual + 1) {
  3443. env->col_no += 1;
  3444. }
  3445. goto _insert;
  3446. case 'P':
  3447. case 'p':
  3448. if (global_config.yanks) {
  3449. for (unsigned int i = 0; i < global_config.yank_count; ++i) {
  3450. env->lines = add_line(env->lines, env->line_no - (c == 'P' ? 1 : 0));
  3451. }
  3452. for (unsigned int i = 0; i < global_config.yank_count; ++i) {
  3453. replace_line(env->lines, env->line_no - (c == 'P' ? 1 : 0) + i, global_config.yanks[i]);
  3454. }
  3455. for (int i = 0; i < env->line_count; ++i) {
  3456. env->lines[i]->istate = 0;
  3457. }
  3458. for (int i = 0; i < env->line_count; ++i) {
  3459. recalculate_syntax(env->lines[i],i);
  3460. }
  3461. redraw_all();
  3462. }
  3463. break;
  3464. case '$':
  3465. env->col_no = env->lines[env->line_no-1]->actual+1;
  3466. break;
  3467. case '0':
  3468. env->col_no = 1;
  3469. break;
  3470. case 'i':
  3471. _insert:
  3472. if (env->readonly) goto _readonly;
  3473. insert_mode();
  3474. redraw_statusbar();
  3475. redraw_commandline();
  3476. timeout = 0;
  3477. break;
  3478. _readonly:
  3479. render_error("Buffer is read-only");
  3480. break;
  3481. case 12:
  3482. redraw_all();
  3483. break;
  3484. }
  3485. } else {
  3486. handle_escape(this_buf,&timeout,c);
  3487. }
  3488. place_cursor_actual();
  3489. }
  3490. }
  3491. return 0;
  3492. }