editor.mod 18 KB

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