shedit.pp 16 KB

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