shedit.pp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. {
  2. "SHEdit" - Text editor with syntax highlighting
  3. Copyright (C) 1999-2000 by Sebastian Guenther ([email protected])
  4. See the file COPYING.FPC, included in this distribution,
  5. for details about the copyright.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  9. }
  10. // ===================================================================
  11. // Generic text editor widget with syntax highlighting capabilities
  12. // ===================================================================
  13. {$MODE objfpc}
  14. {$H+}
  15. unit shedit;
  16. interface
  17. uses
  18. Classes, doc_text;
  19. type
  20. TSHTextEdit = class;
  21. TSHTextEditClass = class of TSHTextEdit;
  22. // -------------------------------------------------------------------
  23. // Keyboard/action assignment handling
  24. // -------------------------------------------------------------------
  25. TKeyboardActionProc = procedure of object;
  26. TSelectionAction = (selNothing,selExtend,selClear);
  27. TKeyboardActionDescr = class(TCollectionItem)
  28. public
  29. Descr: String; // Human readable description
  30. Method: TKeyboardActionProc;
  31. SelectionAction : TSelectionAction;
  32. end;
  33. TShortcut = class(TCollectionItem)
  34. public
  35. KeyCode: Integer;
  36. ShiftState: TShiftState;
  37. Action: TKeyboardActionDescr;
  38. end;
  39. // -------------------------------------------------------------------
  40. // Undo/redo buffer stuff
  41. // -------------------------------------------------------------------
  42. TUndoInfo = class;
  43. TUndoInfo = class
  44. Prev, Next: TUndoInfo;
  45. CursorX, CursorY: Integer;
  46. function Merge(AEdit: TSHTextEdit; AInfo: TUndoInfo): Boolean; virtual;
  47. procedure DoUndo(AEdit: TSHTextEdit); virtual; abstract;
  48. end;
  49. TUndoEdit = class(TUndoInfo)
  50. NumOfChars: Integer;
  51. constructor Create;
  52. constructor Create(ANumOfChars: Integer);
  53. function Merge(AEdit: TSHTextEdit; AInfo: TUndoInfo): Boolean; override;
  54. procedure DoUndo(AEdit: TSHTextEdit); override;
  55. end;
  56. TUndoDelLeft = class(TUndoInfo)
  57. DeletedString: String;
  58. constructor Create(const ADeletedString: String);
  59. function Merge(AEdit: TSHTextEdit; AInfo: TUndoInfo): Boolean; override;
  60. procedure DoUndo(AEdit: TSHTextEdit); override;
  61. end;
  62. TUndoDelRight = class(TUndoDelLeft)
  63. procedure DoUndo(AEdit: TSHTextEdit); override;
  64. end;
  65. // -------------------------------------------------------------------
  66. // Selection support
  67. // -------------------------------------------------------------------
  68. TSelection = class
  69. public
  70. constructor Create;
  71. procedure Clear;
  72. StartX, StartY, EndX, EndY: Integer;
  73. function IsValid: Boolean;
  74. function IsEmpty: Boolean;
  75. // Ordered coordinates: swaps start and end if necessary
  76. function OStartX: Integer;
  77. function OStartY: Integer;
  78. function OEndX: Integer;
  79. function OEndY: Integer;
  80. end;
  81. // -------------------------------------------------------------------
  82. // SHWidget interface
  83. // -------------------------------------------------------------------
  84. ISHWidget = class
  85. // Drawing
  86. procedure InvalidateRect(x, y, w, h: Integer); virtual; abstract;
  87. procedure ClearRect(x, y, w, h: Integer); virtual; abstract;
  88. procedure DrawTextLine(x1, x2, y: Integer; s: PChar); virtual; abstract;
  89. // Cursor placement
  90. procedure ShowCursor(x, y: Integer); virtual; abstract;
  91. procedure HideCursor(x, y: Integer); virtual; abstract;
  92. // Scrolling support
  93. function GetHorzPos: Integer; virtual; abstract;
  94. procedure SetHorzPos(x: Integer); virtual; abstract;
  95. function GetVertPos: Integer; virtual; abstract;
  96. procedure SetVertPos(y: Integer); virtual; abstract;
  97. function GetPageWidth: Integer; virtual; abstract;
  98. function GetPageHeight: Integer; virtual; abstract;
  99. function GetLineWidth: Integer; virtual; abstract;
  100. procedure SetLineWidth(count: Integer); virtual; abstract;
  101. function GetLineCount: Integer; virtual; abstract;
  102. procedure SetLineCount(count: Integer); virtual; abstract;
  103. // Clipboard support
  104. function GetClipboard: String; virtual; abstract;
  105. procedure SetClipboard(Content: String); virtual; abstract;
  106. property HorzPos: Integer read GetHorzPos write SetHorzPos;
  107. property VertPos: Integer read GetVertPos write SetVertPos;
  108. property PageWidth: Integer read GetPageWidth;
  109. property PageHeight: Integer read GetPageHeight;
  110. property LineWidth: Integer read GetLineWidth write SetLineWidth;
  111. property LineCount: Integer read GetLineCount write SetLineCount;
  112. property Clipboard: String read GetClipboard write SetClipboard;
  113. end;
  114. // -------------------------------------------------------------------
  115. // SHTextEdit: The main editor class
  116. // -------------------------------------------------------------------
  117. TShortcutEvent = procedure of object;
  118. TEditLineEvent = procedure(Sender: TSHTextEdit; Line: Integer) of object;
  119. TSHTextEdit = class
  120. protected
  121. // ===== Internally used stuff
  122. ViewInfo: TViewInfo; // Connection to document
  123. CursorVisible: Integer;
  124. OverwriteMode: Boolean;
  125. LastUndoInfo, LastRedoInfo: TUndoInfo; // tails of double linked lists
  126. FSel: TSelection;
  127. OldSelValid: Boolean;
  128. OldSelStartX, OldSelStartY, OldSelEndX, OldSelEndY: Integer;
  129. // OnKeyPressed saves the cursor position before calling key handlers
  130. LastCursorX, LastCursorY: Integer;
  131. function CalcSHFlags(FlagsIn: Byte; source: String): Byte;
  132. procedure ChangeInLine(line: Integer); // Redraws screen where necessary
  133. procedure AddUndoInfo(AInfo: TUndoInfo; CanMerge: Boolean);
  134. // The default implementation does not perform any syntax highlighting:
  135. procedure DoHighlighting(var flags: Byte; source, dest: PChar); virtual;
  136. // ===== Properties
  137. FDoc: TTextDoc; // Document object for text
  138. FCursorX, FCursorY: Integer; // 0/0 = upper left corner
  139. FOnModifiedChange: TNotifyEvent;
  140. FOnLineInsert, FOnLineRemove: TEditLineEvent;
  141. FWidget: ISHWidget;
  142. procedure SetCursorX(NewCursorX: Integer);
  143. procedure SetCursorY(NewCursorY: Integer);
  144. procedure DocumentCleared(Sender: TObject);
  145. procedure ModifiedChanged(Sender: TObject);
  146. procedure LineInserted(Sender: TTextDoc; Line: Integer); virtual;
  147. procedure LineRemoved(Sender: TTextDoc; Line: Integer); virtual;
  148. procedure LineChanged(Sender: TTextDoc; Line: Integer); virtual;
  149. function ExecKey(Key: Char; BlockMode: Boolean): Boolean;
  150. procedure ExecKeys(Keys: String; BlockMode: Boolean);
  151. procedure MultiDelLeft(Count: Integer);
  152. public
  153. // Keyboard command handlers
  154. // Cursor movement
  155. procedure AdjustCursorToRange;
  156. procedure AdjustRangeToCursor;
  157. procedure CursorUp;
  158. procedure CursorDown;
  159. procedure CursorLeft;
  160. procedure CursorRight;
  161. procedure CursorHome;
  162. procedure CursorEnd;
  163. procedure CursorDocBegin;
  164. procedure CursorDocEnd;
  165. procedure CursorPageUp;
  166. procedure CursorPageDown;
  167. // Misc
  168. procedure ToggleOverwriteMode;
  169. procedure EditDelLeft;
  170. procedure EditDelRight;
  171. procedure EditDelLine;
  172. procedure EditUndo;
  173. procedure EditRedo;
  174. procedure ClipboardCut;
  175. procedure ClipboardCopy;
  176. procedure ClipboardPaste;
  177. // Customizable keyboard handlers
  178. procedure KeyReturn; virtual;
  179. public
  180. constructor Create(ADoc: TTextDoc; AWidget: ISHWidget); virtual;
  181. destructor Destroy; override;
  182. function AddKeyboardAction(AMethod: TKeyboardActionProc;ASelectionAction:TSelectionAction;ADescr: String): TKeyboardActionDescr;
  183. function AddKeyboardAssignment(AKeyCode: Integer; AShiftState: TShiftState;
  184. AAction: TKeyboardActionDescr): TShortcut;
  185. procedure AddKeyDef(AMethod: TKeyboardActionProc; ASelectionAction:TSelectionAction; ADescr: String; AKeyCode: Integer; AShiftState: TShiftState);
  186. procedure HideCursor;
  187. procedure ShowCursor;
  188. procedure FocusIn;
  189. procedure FocusOut;
  190. procedure DrawContent(x, y, w, h: Integer);
  191. // Return value: True=Key has been pressed, False=Key has not been processed
  192. function KeyPressed(KeyCode: LongWord; ShiftState: TShiftState): Boolean; virtual;
  193. procedure StartSelectionChange;
  194. procedure EndSelectionChange;
  195. KeyboardActions: TCollection;
  196. Shortcuts: TCollection;
  197. shDefault, shSelected: Integer;
  198. property Doc: TTextDoc read FDoc;
  199. property CursorX: Integer read FCursorX write SetCursorX;
  200. property CursorY: Integer read FCursorY write SetCursorY;
  201. property Selection: TSelection read FSel write FSel;
  202. property OnModifiedChange: TNotifyEvent read FOnModifiedChange write FOnModifiedChange;
  203. property OnLineInsert: TEditLineEvent read FOnLineInsert write FOnLineInsert;
  204. property OnLineRemove: TEditLineEvent read FOnLineRemove write FOnLineRemove;
  205. property Widget: ISHWidget read FWidget;
  206. end;
  207. implementation
  208. uses
  209. SysUtils;
  210. {$INCLUDE undo.inc}
  211. {$INCLUDE keys.inc}
  212. {$INCLUDE drawing.inc}
  213. constructor TSelection.Create;
  214. begin
  215. inherited Create;
  216. Clear;
  217. end;
  218. function TSelection.IsValid: Boolean;
  219. begin
  220. Result := StartX <> -1;
  221. end;
  222. function TSelection.IsEmpty: Boolean;
  223. begin
  224. Result := (StartX = EndX) and (StartY = EndY);
  225. end;
  226. function TSelection.OStartX: Integer;
  227. begin
  228. if (StartY > EndY) or ((StartY = EndY) and (StartX > EndX)) then
  229. Result := EndX
  230. else
  231. Result := StartX;
  232. end;
  233. function TSelection.OStartY: Integer;
  234. begin
  235. if (StartY > EndY) or ((StartY = EndY) and (StartX > EndX)) then
  236. Result := EndY
  237. else
  238. Result := StartY;
  239. end;
  240. function TSelection.OEndX: Integer;
  241. begin
  242. if (StartY > EndY) or ((StartY = EndY) and (StartX > EndX)) then
  243. Result := StartX
  244. else
  245. Result := EndX;
  246. end;
  247. function TSelection.OEndY: Integer;
  248. begin
  249. if (StartY > EndY) or ((StartY = EndY) and (StartX > EndX)) then
  250. Result := StartY
  251. else
  252. Result := EndY;
  253. end;
  254. procedure TSelection.Clear;
  255. begin
  256. StartX := -1;
  257. StartY := -1;
  258. EndX := -1;
  259. EndY := -1;
  260. end;
  261. constructor TSHTextEdit.Create(ADoc: TTextDoc; AWidget: ISHWidget);
  262. var
  263. i: Integer;
  264. begin
  265. ASSERT(Assigned(ADoc) and Assigned(AWidget));
  266. FDoc := ADoc;
  267. ViewInfo := TViewInfo(FDoc.ViewInfos.Add);
  268. ViewInfo.OnLineInsert := @LineInserted;
  269. ViewInfo.OnLineRemove := @LineRemoved;
  270. ViewInfo.OnLineChange := @LineChanged;
  271. ViewInfo.OnModifiedChange := @ModifiedChanged;
  272. ViewInfo.OnClearDocument := @DocumentCleared;
  273. FWidget := AWidget;
  274. FSel := TSelection.Create;
  275. KeyboardActions := TCollection.Create(TKeyboardActionDescr);
  276. Shortcuts := TCollection.Create(TShortcut);
  277. Widget.LineCount := FDoc.LineCount;
  278. Widget.LineWidth := FDoc.LineWidth;
  279. CursorX:=0;
  280. CursorY:=0;
  281. end;
  282. destructor TSHTextEdit.Destroy;
  283. var
  284. buf, prev: TUndoInfo;
  285. begin
  286. ViewInfo.Free;
  287. FDoc.Release;
  288. KeyboardActions.Free;
  289. Shortcuts.Free;
  290. FSel.Free;
  291. buf := LastUndoInfo;
  292. while Assigned(buf) do begin
  293. prev := buf.prev;
  294. buf.Free;
  295. buf := prev;
  296. end;
  297. buf := LastRedoInfo;
  298. while Assigned(buf) do begin
  299. prev := buf.prev;
  300. buf.Free;
  301. buf := prev;
  302. end;
  303. inherited Destroy;
  304. end;
  305. procedure TSHTextEdit.DocumentCleared(Sender: TObject);
  306. begin
  307. FCursorX := 0;
  308. FCursorY := 0;
  309. FSel.Clear;
  310. AdjustRangeToCursor;
  311. Widget.ClearRect(0, 0, Widget.PageWidth, Widget.PageHeight);
  312. end;
  313. procedure TSHTextEdit.ModifiedChanged(Sender: TObject);
  314. begin
  315. if Assigned(OnModifiedChange) then
  316. OnModifiedChange(Self);
  317. end;
  318. procedure TSHTextEdit.FocusIn;
  319. begin
  320. CursorVisible := 0;
  321. ShowCursor;
  322. end;
  323. procedure TSHTextEdit.FocusOut;
  324. begin
  325. HideCursor;
  326. end;
  327. procedure TSHTextEdit.SetCursorX(NewCursorX: Integer);
  328. begin
  329. HideCursor;
  330. if NewCursorX >= 0 then
  331. FCursorX := NewCursorX
  332. else
  333. FCursorX := 0;
  334. if FCursorX > FDoc.LineWidth then
  335. Widget.LineWidth := FCursorX + 4
  336. else
  337. Widget.LineWidth := FDoc.LineWidth + 4;
  338. ShowCursor;
  339. AdjustRangeToCursor;
  340. end;
  341. procedure TSHTextEdit.SetCursorY(NewCursorY: Integer);
  342. begin
  343. HideCursor;
  344. if NewCursorY >= 0 then
  345. FCursorY := NewCursorY
  346. else
  347. FCursorY := 0;
  348. ShowCursor;
  349. AdjustRangeToCursor;
  350. end;
  351. procedure TSHTextEdit.LineInserted(Sender: TTextDoc; Line: Integer);
  352. begin
  353. Widget.LineCount := FDoc.LineCount;
  354. if FCursorX > FDoc.LineWidth then
  355. Widget.LineWidth := FCursorX + 4
  356. else
  357. Widget.LineWidth := FDoc.LineWidth + 4;
  358. if Assigned(FOnLineInsert) then
  359. FOnLineInsert(Self, Line);
  360. ChangeInLine(Line);
  361. end;
  362. procedure TSHTextEdit.LineRemoved(Sender: TTextDoc; Line: Integer);
  363. begin
  364. Widget.LineCount := FDoc.LineCount;
  365. if FCursorX > FDoc.LineWidth then
  366. Widget.LineWidth := FCursorX + 4
  367. else
  368. Widget.LineWidth := FDoc.LineWidth + 4;
  369. if Assigned(FOnLineRemove) then
  370. FOnLineRemove(Self, Line);
  371. ChangeInLine(Line);
  372. end;
  373. procedure TSHTextEdit.LineChanged(Sender: TTextDoc; Line: Integer);
  374. begin
  375. if FCursorX > FDoc.LineWidth then
  376. Widget.LineWidth := FCursorX + 4
  377. else
  378. Widget.LineWidth := FDoc.LineWidth + 4;
  379. ChangeInLine(Line);
  380. end;
  381. procedure TSHTextEdit.StartSelectionChange;
  382. begin
  383. HideCursor;
  384. LastCursorX := FCursorX;
  385. LastCursorY := FCursorY;
  386. OldSelValid := FSel.IsValid;
  387. if OldSelValid then begin
  388. OldSelStartX := FSel.OStartX;
  389. OldSelStartY := FSel.OStartY;
  390. OldSelEndX := FSel.OEndX;
  391. OldSelEndY := FSel.OEndY;
  392. end;
  393. end;
  394. procedure TSHTextEdit.EndSelectionChange;
  395. procedure RedrawArea(x1, y1, x2, y2: Integer);
  396. begin
  397. //WriteLn('Redraw: ', x1, '/', y1, ' - ', x2, '/', y2);
  398. if y1 = y2 then
  399. FWidget.InvalidateRect(x1, y1, (x2 - x1) + 1, (y2 - y1) + 1)
  400. else begin
  401. FWidget.InvalidateRect(x1, y1, FWidget.PageWidth + FWidget.HorzPos, 1);
  402. if y1 < y2 - 1 then
  403. FWidget.InvalidateRect(0, y1 + 1, FWidget.PageWidth + FWidget.HorzPos, (y2 - y1) - 1);
  404. FWidget.InvalidateRect(0, y2, x2, 1);
  405. end;
  406. end;
  407. begin
  408. //WriteLn('=> TSHTextEdit.EndSelectionChange');
  409. if not OldSelValid then begin
  410. if FSel.IsValid then
  411. RedrawArea(FSel.OStartX, FSel.OStartY, FSel.OEndX, FSel.OEndY);
  412. end else begin
  413. //WriteLn('Old selection: ', OldSelStartX, '/', OldSelStartY, ' - ', OldSelEndX, '/', OldSelEndY);
  414. if not FSel.IsValid then begin
  415. //WriteLn('No new selection');
  416. RedrawArea(OldSelStartX, OldSelStartY, OldSelEndX, OldSelEndY);
  417. end else begin
  418. //WriteLn('New selection: ', FSel.OStartX, '/', FSel.OStartY, ' - ', FSel.OEndX, '/', FSel.OEndY);
  419. // Do OldSel and FSel intersect?
  420. if (OldSelEndY < FSel.OStartY) or (OldSelStartY > FSel.OEndY) or
  421. ((OldSelEndY = FSel.OStartY) and (OldSelEndX <= FSel.OStartX)) or
  422. ((OldSelStartY = FSel.OEndY) and (OldSelStartX >= FSel.OEndX)) then
  423. begin
  424. RedrawArea(OldSelStartX, OldSelStartY, OldSelEndX, OldSelEndY);
  425. RedrawArea(FSel.OStartX, FSel.OStartY, FSel.OEndX, FSel.OEndY);
  426. end else begin
  427. // Intersection => determine smallest possible area(s) to redraw
  428. // 1. Check if the start position has changed
  429. if (OldSelStartX <> FSel.OStartX) or (OldSelStartY <> FSel.OStartY) then
  430. if (OldSelStartY < FSel.OStartY) or ((OldSelStartY = FSel.OStartY) and
  431. (OldSelStartX < FSel.OStartX)) then
  432. RedrawArea(OldSelStartX, OldSelStartY, FSel.OStartX, FSel.OStartY)
  433. else
  434. RedrawArea(FSel.OStartX, FSel.OStartY, OldSelStartX, OldSelStartY);
  435. // 2. Check if end position has changed
  436. if (OldSelEndX <> FSel.OEndX) or (OldSelEndY <> FSel.OEndY) then
  437. if (OldSelEndY < FSel.OEndY) or ((OldSelEndY = FSel.OEndY) and
  438. (OldSelEndX < FSel.OEndX)) then
  439. RedrawArea(OldSelEndX, OldSelEndY, FSel.OEndX, FSel.OEndY)
  440. else
  441. RedrawArea(FSel.OEndX, FSel.OEndY, OldSelEndX, OldSelEndY);
  442. end;
  443. end;
  444. end;
  445. ShowCursor;
  446. end;
  447. end.