editor.mod 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. MODULE editor;
  2. (* based on the kilo editor building course*)
  3. (* Step 61 : we are going to jump to step 64 , first compilable c version*)
  4. (* and adapted modula-2 version where strict data types is obligatory *)
  5. (*** IMPORTS ***)
  6. IMPORT IO, termios, FIO, libc, CharClass, NumberIO, ASCII ;
  7. IMPORT STextIO, Strings, SYSTEM, SplitV1, Storage, MemUtils, ProgramArgs;
  8. FROM SeqFile IMPORT OpenRead, OpenWrite, OpenResults, Close, ChanId, read, write;
  9. FROM IOResult IMPORT ReadResults, ReadResult;
  10. FROM TextIO IMPORT ReadString, ReadRestLine, SkipLine, ReadToken;
  11. (*** data ***)
  12. TYPE
  13. abufType = RECORD
  14. b : SYSTEM.ADDRESS;
  15. len : CARDINAL;
  16. END;
  17. abufTypePtr = POINTER TO abufType;
  18. erow = RECORD
  19. size : CARDINAL;
  20. chars : SYSTEM.ADDRESS;
  21. END;
  22. CONST
  23. KILO_VERSION = "0.0.1";
  24. (* escape sequences length *)
  25. clearScreenStrLen = 4;
  26. cursorHomeStrLen = 3;
  27. tildeStrLen = 3;
  28. requestCursorPosStrLen = 4;
  29. cursorOnStrLen = 6;
  30. cursorOffStrLen = 6;
  31. eraseLineStrLen = 3;
  32. nextLineDownPos1Len = 4;
  33. (* fct key mapping*)
  34. ARROW_LEFT = 1000;
  35. ARROW_RIGHT = 1001;
  36. ARROW_UP = 1002;
  37. ARROW_DOWN = 1003;
  38. HOME_KEY = 1004;
  39. END_KEY = 1005;
  40. PAGE_UP = 1006;
  41. PAGE_DOWN = 1007;
  42. DEL_KEY = 1008;
  43. VAR
  44. editorConfig : RECORD
  45. screenrows : CARDINAL;
  46. screencols : CARDINAL;
  47. posX : CARDINAL;
  48. posY : CARDINAL;
  49. theTermios : termios.TERMIOS;
  50. row : erow;
  51. numrows : CARDINAL;
  52. END;
  53. c : CHAR;
  54. TCSAFLUSH : INTEGER;
  55. theStructure: SplitV1.Structure;
  56. i : CARDINAL;
  57. (* escape sequences *)
  58. clearScreenStr : ARRAY [0..3] OF CHAR;
  59. cursorHomeStr : ARRAY [0..2] OF CHAR;
  60. tildeStr : ARRAY [0..2] OF CHAR;
  61. requestCursorPosStr : ARRAY [0..3] OF CHAR;
  62. cursorposStr : ARRAY [0..15] OF CHAR;
  63. cursorOnStr : ARRAY [0..5] OF CHAR;
  64. cursorOffStr : ARRAY [0..5] OF CHAR;
  65. eraseLineStr : ARRAY [0..2] OF CHAR;
  66. nextLineDownPos1 : ARRAY [0..3] OF CHAR;
  67. (* temporary variables *)
  68. tmpString : ARRAY [0..15] OF CHAR;
  69. tmpString1 : ARRAY [0..15] OF CHAR;
  70. tmpString2 : Strings.String1;
  71. cc : Strings.String1;
  72. (* error and information message string *)
  73. message : ARRAY [0..79] OF CHAR;
  74. (* cli arguments *)
  75. theArgChanId : ChanId;
  76. theArgument : ARRAY [0..20] OF CHAR;
  77. theFileName : ARRAY [0..20] OF CHAR;
  78. argumentNumber : CARDINAL;
  79. (******************** Utilities *****************)
  80. PROCEDURE Card2Str ( x : CARDINAL; VAR str : ARRAY OF CHAR);
  81. BEGIN
  82. IF x < 10 THEN
  83. NumberIO.CardToStr(x,1,str);
  84. ELSIF x < 100 THEN
  85. NumberIO.CardToStr(x,2,str);
  86. ELSE
  87. NumberIO.CardToStr(x,3,str);
  88. END;
  89. END Card2Str;
  90. (******************** Buffer ******************)
  91. PROCEDURE abufInit( VAR ptr : abufTypePtr);
  92. BEGIN
  93. Storage.ALLOCATE(ptr,SYSTEM.TSIZE(abufType) );
  94. ptr^.b := NIL;
  95. ptr^.len := 0;
  96. END abufInit;
  97. PROCEDURE abAppend (VAR ptr : abufTypePtr; theSourceAdress: SYSTEM.ADDRESS; theSourceLen : CARDINAL );
  98. BEGIN
  99. IF ptr <> NIL THEN
  100. IF ptr^.b = NIL THEN
  101. Storage.ALLOCATE(ptr^.b,theSourceLen);
  102. ELSE
  103. Storage.REALLOCATE( ptr^.b,ptr^.len + theSourceLen);
  104. END;
  105. MemUtils.MemCopy(theSourceAdress,theSourceLen, SYSTEM.ADDADR(ptr^.b,ptr^.len));
  106. ptr^.len := ptr^.len + theSourceLen;
  107. END;
  108. END abAppend;
  109. PROCEDURE abFree (VAR ptr : abufTypePtr);
  110. BEGIN
  111. Storage.DEALLOCATE(ptr^.b, ptr^.len);
  112. Storage.DEALLOCATE(ptr,SYSTEM.TSIZE(abufType));
  113. END abFree;
  114. (*** Terminal ***)
  115. PROCEDURE CtrlKey(c: CHAR) : CARDINAL;
  116. VAR
  117. n : BITSET;
  118. BEGIN
  119. n := BITSET(ORD(c));
  120. n := n * BITSET(1FH);
  121. RETURN CARDINAL(n)
  122. END CtrlKey;
  123. PROCEDURE die (s : ARRAY OF CHAR);
  124. BEGIN
  125. libc.write(FIO.StdOut,SYSTEM.ADR(cursorHomeStr), cursorHomeStrLen);
  126. libc.write(FIO.StdOut,SYSTEM.ADR(clearScreenStr), clearScreenStrLen);
  127. libc.perror(s);
  128. FOR i := 0 TO Strings.Length(s) DO
  129. IO.Write(s[i])
  130. END;
  131. HALT
  132. END die;
  133. PROCEDURE disablerawMode() : INTEGER;
  134. VAR
  135. result : INTEGER;
  136. BEGIN
  137. IO.BufferedMode(0,TRUE);
  138. IO.BufferedMode(1,TRUE);
  139. result := termios.tcsetattr(FIO.StdIn, TCSAFLUSH, editorConfig.theTermios);
  140. IF result = -1 THEN
  141. editorConfig.theTermios := termios.KillTermios(editorConfig.theTermios);
  142. die("tcsetattr")
  143. ELSE
  144. RETURN result
  145. END;
  146. END disablerawMode;
  147. PROCEDURE enablerawMode;
  148. BEGIN
  149. libc.atexit(disablerawMode);
  150. editorConfig.theTermios := termios.InitTermios();
  151. IF termios.tcgetattr(FIO.StdIn, editorConfig.theTermios) = -1 THEN
  152. die("tcgetattr")
  153. END;
  154. IO.UnBufferedMode(0,TRUE);
  155. IO.UnBufferedMode(1,TRUE);
  156. END enablerawMode;
  157. PROCEDURE editorReadKey() : CARDINAL;
  158. VAR
  159. c : CHAR;
  160. d : CHAR;
  161. num : CARDINAL;
  162. (*
  163. The Home key could be sent as <esc>[1~, <esc>[7~, <esc>[H, or
  164. <esc>OH (this is the letter O followed by H).
  165. Similarly, the End key could be sent as <esc>[4~, <esc>[8~,
  166. <esc>[F, or <esc>OF (this is the letter O followed by F).
  167. *)
  168. BEGIN
  169. IO.Read(c);
  170. IF c = ASCII.esc THEN
  171. IO.Read(c);
  172. (* mode := command; *)
  173. IF c = "[" THEN
  174. IO.Read(c);
  175. IF (c > "0") AND (c <"9") THEN
  176. IO.Read(d);
  177. IF d = "~" THEN
  178. CASE c OF
  179. "4", "8" : num := END_KEY; |
  180. "1", "7" : num := HOME_KEY; |
  181. "5" : num := PAGE_UP; |
  182. "6" : num := PAGE_DOWN;|
  183. "3" : num := DEL_KEY
  184. END;
  185. END;
  186. ELSIF (c = "A") OR (c = "B") OR (c = "C") OR (c = "D") OR (c = "F") OR (c = "H") THEN
  187. CASE c OF
  188. "A" : num := ARROW_UP; |
  189. "B" : num := ARROW_DOWN ; |
  190. "C" : num := ARROW_RIGHT; |
  191. "D" : num := ARROW_LEFT; |
  192. "F" : num := END_KEY; |
  193. "H" : num := HOME_KEY;
  194. END;
  195. ELSIF c = "O" THEN
  196. IO.Read(c);
  197. CASE c OF
  198. "F" : num := END_KEY; |
  199. "H" : num := HOME_KEY;
  200. END;
  201. ELSE
  202. message := ("key not recognized");
  203. END;
  204. (* mode := move; *)
  205. (* <esc>OF <esc>OH à voir !*)
  206. END;
  207. RETURN num;
  208. ELSE
  209. (* mode := edit; *)
  210. RETURN ORD(c)
  211. END;
  212. END editorReadKey;
  213. (******************** file I/O ***************)
  214. PROCEDURE editorOpen( fileName : ARRAY OF CHAR);
  215. VAR
  216. theChanId : ChanId;
  217. theResult : OpenResults;
  218. line : ARRAY[0..255] OF CHAR;
  219. BEGIN
  220. (* PROCEDURE OpenRead (VAR cid: ChanId; name: ARRAY OF CHAR; flags: FlagSet; VAR res: OpenResults); *)
  221. OpenRead(theChanId, fileName, read, theResult);
  222. (* test du résultat*)
  223. IF theResult = noSuchFile THEN
  224. die("No such file")
  225. ELSIF theResult=opened THEN (* reading the lines ... *)
  226. (* first line *)
  227. line := "";
  228. ReadString(theChanId, line);
  229. editorConfig.row.size := Strings.Length(line);
  230. Storage.ALLOCATE(editorConfig.row.chars, editorConfig.row.size);
  231. MemUtils.MemCopy(SYSTEM.ADR(line),editorConfig.row.size,editorConfig.row.chars );
  232. INC(editorConfig.numrows);
  233. (* STextIO.WriteString("**");STextIO.WriteString(line); STextIO.WriteString("**") *)
  234. (* testing the result *)
  235. (*WHILE ReadResult(theChanId) <> endOfInput DO
  236. (* testing end of line *)
  237. IF ReadResult(theChanId) = endOfLine THEN
  238. (* we have to 'skip' the endOfLine ... so strange *)
  239. SkipLine(theChanId);
  240. END;
  241. editorConfig.row.size := Strings.Length(line);
  242. Storage.ALLOCATE(editorConfig.row.chars, editorConfig.row.size);
  243. INC(editorConfig.numrows);
  244. (* reading next line *)
  245. ReadString(theChanId, line);
  246. END;*)
  247. ELSE
  248. die("Error opening the file");
  249. END;
  250. Close(theChanId);
  251. END editorOpen;
  252. PROCEDURE editorMoveCursor(c : CARDINAL);
  253. BEGIN
  254. WITH editorConfig DO
  255. CASE c OF
  256. ARROW_UP, ORD("e") : IF posY > 1 THEN DEC(posY) END; |
  257. ARROW_DOWN, ORD("x") : IF posY < (screenrows - 1) THEN INC(posY) END; |
  258. ARROW_LEFT, ORD("s") : IF posX > 1 THEN DEC(posX) END; |
  259. ARROW_RIGHT, ORD("d") : IF posX < (screencols - 1) THEN INC(posX) END; |
  260. PAGE_UP : posY := 1; |
  261. PAGE_DOWN : posY := screenrows; |
  262. HOME_KEY : posX := 1; |
  263. END_KEY : posX := screencols;
  264. END;
  265. END
  266. END editorMoveCursor;
  267. PROCEDURE editorProcessKeypress;
  268. VAR
  269. c : CARDINAL;
  270. BEGIN
  271. c := editorReadKey();
  272. CASE c OF
  273. 17 : libc.write(FIO.StdOut,SYSTEM.ADR(cursorHomeStr), cursorHomeStrLen);
  274. libc.write(FIO.StdOut,SYSTEM.ADR(clearScreenStr), clearScreenStrLen);
  275. HALT; |
  276. ORD("e"),
  277. ORD("x"),
  278. ORD("s"),
  279. ORD("d"),
  280. HOME_KEY,
  281. END_KEY,
  282. PAGE_UP,
  283. PAGE_DOWN,
  284. ARROW_UP,
  285. ARROW_DOWN,
  286. ARROW_LEFT,
  287. ARROW_RIGHT : editorMoveCursor(c);
  288. END
  289. END editorProcessKeypress;
  290. (*** Output ***)
  291. PROCEDURE editorDrawRows(VAR ptr : abufTypePtr);
  292. CONST
  293. tilde = "~";
  294. VAR
  295. y : CARDINAL;
  296. welcome : ARRAY [0..79] OF CHAR;
  297. welcomeLen : CARDINAL;
  298. blanks : CARDINAL;
  299. blanksStr : ARRAY[0..79] OF CHAR;
  300. blankStrLength : CARDINAL;
  301. len : CARDINAL;
  302. BEGIN
  303. (* Creation of the about text *)
  304. welcome := "Kilo editor -- version ";
  305. Strings.Concat(welcome,KILO_VERSION,welcome);
  306. welcomeLen := Strings.Length(welcome);
  307. Strings.Concat(welcome,nextLineDownPos1,welcome);
  308. (* centering the welcome text *)
  309. blanks := (editorConfig.screencols - welcomeLen) /2;
  310. cc[0] := " ";
  311. blanksStr := "";
  312. FOR i := 0 TO blanks DO
  313. Strings.Concat(blanksStr,cc,blanksStr);
  314. END;
  315. blankStrLength := Strings.Length(blanksStr);
  316. (* display of the page *)
  317. FOR y := 1 TO editorConfig.screenrows -1 DO
  318. (* for all lines except the last one *)
  319. IF y > editorConfig.numrows THEN
  320. IF ((y = (editorConfig.screenrows / 3)) AND (editorConfig.screencols > welcomeLen)) AND (editorConfig.numrows = 0) THEN
  321. abAppend(ptr,SYSTEM.ADR(blanksStr), blankStrLength);
  322. abAppend(ptr,SYSTEM.ADR(welcome),welcomeLen );
  323. abAppend(ptr,SYSTEM.ADR(eraseLineStr),eraseLineStrLen );
  324. abAppend(ptr,SYSTEM.ADR(nextLineDownPos1),nextLineDownPos1Len );
  325. ELSE
  326. abAppend(ptr,SYSTEM.ADR(tilde), 1);
  327. abAppend(ptr,SYSTEM.ADR(eraseLineStr),eraseLineStrLen );
  328. abAppend(ptr,SYSTEM.ADR(nextLineDownPos1),nextLineDownPos1Len );
  329. END;
  330. ELSE
  331. len := editorConfig.row.size;
  332. IF len > editorConfig.screencols THEN
  333. len := editorConfig.screencols
  334. END;
  335. abAppend(ptr,editorConfig.row.chars,len);
  336. abAppend(ptr,SYSTEM.ADR(nextLineDownPos1),nextLineDownPos1Len );
  337. END;
  338. END;
  339. abAppend(ptr,SYSTEM.ADR(tilde), 1);
  340. abAppend(ptr,SYSTEM.ADR(eraseLineStr),eraseLineStrLen );
  341. abAppend(ptr,SYSTEM.ADR(nextLineDownPos1),nextLineDownPos1Len );
  342. END editorDrawRows;
  343. PROCEDURE editorRefreshScreen;
  344. VAR
  345. ptr : abufTypePtr;
  346. BEGIN
  347. abufInit(ptr);
  348. abAppend(ptr,SYSTEM.ADR(cursorOffStr), cursorOffStrLen);
  349. abAppend(ptr,SYSTEM.ADR(cursorHomeStr), cursorHomeStrLen);
  350. editorDrawRows(ptr);
  351. (* cursorpos(1,1,FALSE); *)
  352. cursorpos(editorConfig.posX,editorConfig.posY,FALSE);
  353. abAppend(ptr,SYSTEM.ADR(cursorposStr), Strings.Length(cursorposStr));
  354. abAppend(ptr,SYSTEM.ADR(cursorOnStr), cursorOnStrLen);
  355. libc.write(FIO.StdOut,ptr^.b,ptr^.len);
  356. abFree(ptr);
  357. END editorRefreshScreen;
  358. PROCEDURE requestCursorPosition(VAR x,y : CARDINAL);
  359. VAR
  360. c : CHAR;
  361. str : ARRAY [0..15] OF CHAR;
  362. BEGIN
  363. str := "";
  364. SplitV1.InitStructure(theStructure);
  365. libc.write(FIO.StdOut,SYSTEM.ADR(requestCursorPosStr),requestCursorPosStrLen);
  366. (* getting answer *)
  367. str := "";
  368. REPEAT
  369. IO.Read(c);
  370. cc[0] := c;
  371. Strings.Concat(str,cc,str);
  372. UNTIL c = "R";
  373. Strings.Delete(str,0,2);
  374. Strings.Delete(str,Strings.Length(str) -1 ,1);
  375. SplitV1.SplitStr(str, ";", theStructure);
  376. NumberIO.StrToCard(theStructure[0].element,y);
  377. NumberIO.StrToCard(theStructure[1].element,x);
  378. END requestCursorPosition;
  379. PROCEDURE cursorpos(x,y : CARDINAL; emit : BOOLEAN);
  380. BEGIN
  381. cursorposStr := "";
  382. Strings.Concat(cursorposStr, ASCII.esc,cursorposStr);
  383. Strings.Concat(cursorposStr, "[",cursorposStr);
  384. tmpString := "";
  385. Strings.Concat(tmpString,cursorposStr, tmpString);
  386. tmpString1 := "";
  387. Card2Str(y,tmpString1);
  388. Strings.Concat(tmpString,tmpString1, tmpString);
  389. tmpString2[0] := ";";
  390. Strings.Concat(tmpString, tmpString2,tmpString);
  391. tmpString1 := "";
  392. Card2Str(x,tmpString1);
  393. Strings.Concat(tmpString,tmpString1, tmpString);
  394. tmpString2[0] := "H";
  395. Strings.Concat(tmpString, tmpString2,tmpString);
  396. cursorposStr := tmpString;
  397. editorConfig.posX := x;
  398. editorConfig.posY := y;
  399. IF emit THEN
  400. libc.write(FIO.StdOut,SYSTEM.ADR(tmpString),Strings.Length(tmpString));
  401. END;
  402. END cursorpos;
  403. PROCEDURE getWindowSize(VAR y : CARDINAL; VAR x: CARDINAL);
  404. BEGIN
  405. cursorpos(999,999, TRUE);
  406. requestCursorPosition(x,y);
  407. END getWindowSize;
  408. (*** Input ***)
  409. (*** Init ***)
  410. PROCEDURE InitScreenEscapes;
  411. VAR temp : ARRAY[0..10] OF CHAR;
  412. BEGIN
  413. cursorHomeStr := "";
  414. Strings.Concat(cursorHomeStr,ASCII.esc,cursorHomeStr);
  415. Strings.Concat(cursorHomeStr,"[H",cursorHomeStr);
  416. clearScreenStr := "";
  417. Strings.Concat(clearScreenStr,ASCII.esc,clearScreenStr);
  418. Strings.Concat(clearScreenStr,"[2J",clearScreenStr);
  419. tildeStr[0] := "~";
  420. tildeStr[1] := CHR(10);
  421. tildeStr[2] := CHR(13);
  422. cursorposStr := "";
  423. Strings.Concat(cursorposStr,ASCII.esc,cursorposStr);
  424. Strings.Concat(cursorposStr,"[",cursorposStr);
  425. requestCursorPosStr := "";
  426. Strings.Concat(requestCursorPosStr,ASCII.esc,requestCursorPosStr);
  427. Strings.Concat(requestCursorPosStr,"[6n",requestCursorPosStr);
  428. cursorOnStr := "";
  429. Strings.Concat(cursorOnStr,ASCII.esc,cursorOnStr);
  430. Strings.Concat(cursorOnStr,"[?25h",cursorOnStr);
  431. cursorOffStr := "";
  432. Strings.Concat(cursorOffStr,ASCII.esc,cursorOffStr);
  433. Strings.Concat(cursorOffStr,"[?25l",cursorOffStr);
  434. eraseLineStr := "";
  435. Strings.Concat(eraseLineStr,ASCII.esc,eraseLineStr);
  436. Strings.Concat(eraseLineStr,"[K",eraseLineStr);
  437. (* ESC[#E moves cursor to beginning of next line, # lines down *)
  438. nextLineDownPos1 := "";
  439. Strings.Concat(nextLineDownPos1,ASCII.esc,nextLineDownPos1);
  440. Strings.Concat(nextLineDownPos1,"[1E",nextLineDownPos1);
  441. END InitScreenEscapes;
  442. PROCEDURE InitEditor;
  443. BEGIN
  444. TCSAFLUSH := termios.tcsflush ();
  445. InitScreenEscapes;
  446. editorConfig.posX := 1;
  447. editorConfig.posY := 1;
  448. getWindowSize(editorConfig.screenrows, editorConfig.screencols);
  449. editorConfig.posX := 1;
  450. editorConfig.posY := 1;
  451. editorConfig.numrows := 0;
  452. editorConfig.row.size := 0;
  453. editorConfig.row.chars := NIL;
  454. libc.write(FIO.StdOut,SYSTEM.ADR(clearScreenStr),4);
  455. END InitEditor;
  456. BEGIN
  457. enablerawMode;
  458. InitEditor;
  459. editorRefreshScreen;
  460. (* fetching the 2nd argument (fileName) *)
  461. theFileName := "";
  462. theArgChanId := ProgramArgs.ArgChan ();
  463. argumentNumber := 0;
  464. WHILE ProgramArgs.IsArgPresent() DO
  465. INC(argumentNumber);
  466. ReadToken(ProgramArgs.ArgChan(), theArgument);
  467. IF argumentNumber = 2 THEN
  468. theFileName := theArgument;
  469. END;
  470. ProgramArgs.NextArg ();
  471. END;
  472. IF argumentNumber >= 2 THEN
  473. editorOpen(theFileName);
  474. END;
  475. LOOP
  476. editorRefreshScreen;
  477. editorProcessKeypress;
  478. END;
  479. END editor.