doc_text.pp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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. // Generic text document class
  12. unit doc_text;
  13. {$MODE objfpc}
  14. {$H+}
  15. interface
  16. uses Classes;
  17. type
  18. PLine = ^TLine;
  19. TLine = record
  20. info: Pointer;
  21. flags: LongWord;
  22. s: AnsiString;
  23. end;
  24. PLineArray = ^TLineArray;
  25. TLineArray = array[0..MaxInt div SizeOf(TLine) - 1] of TLine;
  26. const
  27. {TLine.flags Syntax Highlighting Flags}
  28. LF_SH_Valid = $01;
  29. LF_SH_Multiline1 = $02;
  30. LF_SH_Multiline2 = $04;
  31. LF_SH_Multiline3 = $08;
  32. LF_SH_Multiline4 = $10;
  33. LF_SH_Multiline5 = $20;
  34. LF_SH_Multiline6 = $40;
  35. LF_SH_Multiline7 = $80;
  36. {Escape character for syntax highlighting (marks start of sh sequence,
  37. next character is color/sh element number, beginning at #1}
  38. LF_Escape = #10;
  39. type
  40. TTextDoc = class;
  41. TDocLineEvent = procedure(Sender: TTextDoc; Line: Integer) of object;
  42. TViewInfo = class(TCollectionItem)
  43. public
  44. OnLineInsert, OnLineRemove, OnLineChange: TDocLineEvent;
  45. OnClearDocument, OnModifiedChange: TNotifyEvent;
  46. end;
  47. TTextDoc = class
  48. protected
  49. RefCount: LongInt;
  50. FLineEnding: String;
  51. FModified: Boolean;
  52. FLineWidth,
  53. FLineCount: LongInt;
  54. FLines: PLineArray;
  55. FViewInfos: TCollection;
  56. procedure SetModified(AModified: Boolean);
  57. function GetLineText(LineNumber: Integer): String;
  58. procedure SetLineText(LineNumber: Integer; const NewText: String);
  59. function GetLineLen(LineNumber: Integer): Integer;
  60. function GetLineFlags(LineNumber: Integer): Byte;
  61. procedure SetLineFlags(LineNumber: Integer; NewFlags: Byte);
  62. public
  63. constructor Create;
  64. destructor Destroy; override;
  65. procedure AddRef;
  66. procedure Release;
  67. procedure Clear;
  68. procedure LoadFromStream(AStream: TStream);
  69. procedure LoadFromFile(const filename: String);
  70. procedure SaveToStream(AStream: TStream);
  71. procedure SaveToFile(const filename: String);
  72. procedure InsertLine(BeforeLine: Integer; const s: String);
  73. procedure AddLine(const s: String);
  74. procedure RemoveLine(LineNumber: Integer);
  75. property LineEnding: String read FLineEnding write FLineEnding;
  76. property Modified: Boolean read FModified write SetModified;
  77. property LineWidth: Integer read FLineWidth;
  78. property LineCount: Integer read FLineCount;
  79. property LineText[LineNumber: Integer]: String read GetLineText write SetLineText;
  80. property LineLen[LineNumber: Integer]: Integer read GetLineLen;
  81. property LineFlags[LineNumber: Integer]: Byte read GetLineFlags write SetLineFlags;
  82. property ViewInfos: TCollection read FViewInfos;
  83. end;
  84. implementation
  85. uses Strings;
  86. constructor TTextDoc.Create;
  87. begin
  88. FModified := False;
  89. {$IFDEF Unix}
  90. LineEnding := #10;
  91. {$ELSE}
  92. LineEnding := #13#10;
  93. {$ENDIF}
  94. FLines := nil;
  95. FLineCount := 0;
  96. FLineWidth := 0;
  97. FViewInfos := TCollection.Create(TViewInfo);
  98. RefCount := 1;
  99. end;
  100. destructor TTextDoc.Destroy;
  101. var
  102. i: Integer;
  103. begin
  104. if Assigned(FLines) then
  105. begin
  106. for i := 0 to FLineCount - 1 do
  107. SetLength(FLines^[i].s, 0);
  108. FreeMem(FLines);
  109. end;
  110. FViewInfos.Free;
  111. inherited Destroy;
  112. end;
  113. procedure TTextDoc.AddRef;
  114. begin
  115. Inc(RefCount);
  116. end;
  117. procedure TTextDoc.Release;
  118. begin
  119. ASSERT(RefCount > 0);
  120. Dec(RefCount);
  121. if RefCount = 0 then
  122. Self.Free;
  123. end;
  124. procedure TTextDoc.Clear;
  125. var
  126. i: Integer;
  127. begin
  128. if Assigned(FLines) then
  129. begin
  130. for i := 0 to FLineCount - 1 do
  131. SetLength(FLines^[i].s, 0);
  132. FreeMem(FLines);
  133. FLineCount:=0;
  134. end;
  135. FLineWidth:=0;
  136. for i := 0 to FViewInfos.Count - 1 do
  137. if Assigned(TViewInfo(FViewInfos.Items[i]).OnClearDocument) then
  138. TViewInfo(FViewInfos.Items[i]).OnClearDocument(Self);
  139. end;
  140. procedure TTextDoc.InsertLine(BeforeLine: Integer; const s: String);
  141. var
  142. l: PLine;
  143. i: Integer;
  144. begin
  145. if (BeforeLine < 0) or (BeforeLine > FLineCount) then
  146. exit; // !!!: throw an exception
  147. ReAllocMem(FLines, (FLineCount + 1) * SizeOf(TLine));
  148. Move(FLines^[BeforeLine], FLines^[BeforeLine + 1], (FLineCount - BeforeLine) * SizeOf(TLine));
  149. l := @FLines^[BeforeLine];
  150. FillChar(l^, SizeOf(TLine), 0);
  151. l^.s := s;
  152. Inc(FLineCount);
  153. if Length(s) > FLineWidth then
  154. FLineWidth := Length(s);
  155. for i := 0 to FViewInfos.Count - 1 do
  156. if Assigned(TViewInfo(FViewInfos.Items[i]).OnLineInsert) then
  157. TViewInfo(FViewInfos.Items[i]).OnLineInsert(Self, BeforeLine);
  158. end;
  159. procedure TTextDoc.AddLine(const s: String);
  160. begin
  161. InsertLine(FLineCount, s);
  162. end;
  163. procedure TTextDoc.RemoveLine(LineNumber: Integer);
  164. var
  165. i: Integer;
  166. begin
  167. SetLength(FLines^[LineNumber].s, 0); // Free the string for this line
  168. ReAllocMem(FLines, (FLineCount - 1) * SizeOf(TLine));
  169. if LineNumber < FLineCount - 1 then
  170. Move(FLines^[LineNumber + 1], FLines^[LineNumber],(FLineCount - LineNumber - 1) * SizeOf(TLine));
  171. Dec(FLineCount);
  172. for i := 0 to FViewInfos.Count - 1 do
  173. if Assigned(TViewInfo(FViewInfos.Items[i]).OnLineRemove) then
  174. TViewInfo(FViewInfos.Items[i]).OnLineRemove(Self, LineNumber);
  175. Modified := True;
  176. end;
  177. procedure TTextDoc.LoadFromStream(AStream: TStream);
  178. procedure ProcessLine(const s: String);
  179. var
  180. s2: String;
  181. i: Integer;
  182. begin
  183. // Expand tabs to spaces
  184. s2 := '';
  185. for i := 1 to Length(s) do
  186. if s[i] = #9 then
  187. begin
  188. repeat
  189. s2 := s2 + ' '
  190. until (Length(s2) mod 8) = 0;
  191. end else
  192. s2 := s2 + s[i];
  193. AddLine(s2);
  194. end;
  195. var
  196. NewData: array[0..1023] of Byte;
  197. buffer, p: PChar;
  198. BytesInBuffer, BytesRead, OldBufSize, LastEndOfLine, i, LineLength: Integer;
  199. line: String;
  200. begin
  201. Clear;
  202. SetLength(line, 0);
  203. BytesInBuffer := 0;
  204. buffer := nil;
  205. while True do
  206. begin
  207. BytesRead := AStream.Read(NewData, SizeOf(NewData));
  208. if BytesRead <= 0 then
  209. break;
  210. OldBufSize := BytesInBuffer;
  211. // Append the new received data to the read buffer
  212. Inc(BytesInBuffer, BytesRead);
  213. ReallocMem(buffer, BytesInBuffer);
  214. Move(NewData, buffer[OldBufSize], BytesRead);
  215. LastEndOfLine := 0;
  216. if OldBufSize > 0 then
  217. i := OldBufSize - 1
  218. else
  219. i := 0;
  220. while i <= BytesInBuffer - 2 do
  221. begin
  222. if (buffer[i] = #13) or (buffer[i] = #10) then
  223. begin
  224. LineLength := i - LastEndOfLine;
  225. SetLength(line, LineLength);
  226. if LineLength > 0 then
  227. Move(buffer[LastEndOfLine], line[1], LineLength);
  228. ProcessLine(line);
  229. if ((buffer[i] = #13) and (buffer[i + 1] = #10)) or
  230. ((buffer[i] = #10) and (buffer[i + 1] = #13)) then
  231. Inc(i);
  232. LastEndOfLine := i + 1;
  233. end;
  234. Inc(i);
  235. end;
  236. if LastEndOfLine > 0 then
  237. begin
  238. // Remove all processed lines from the buffer
  239. Dec(BytesInBuffer, LastEndOfLine);
  240. GetMem(p, BytesInBuffer);
  241. Move(buffer[LastEndOfLine], p^, BytesInBuffer);
  242. FreeMem(buffer);
  243. buffer := p;
  244. end;
  245. end;
  246. if BytesInBuffer > 0 then
  247. if buffer[BytesInBuffer - 1] in [#13, #10] then
  248. begin
  249. SetLength(line, BytesInBuffer - 1);
  250. if BytesInBuffer > 1 then
  251. Move(buffer^, line[1], BytesInBuffer - 1);
  252. ProcessLine(line);
  253. ProcessLine('');
  254. end else
  255. begin
  256. SetLength(line, BytesInBuffer);
  257. if BytesInBuffer > 1 then
  258. Move(buffer^, line[1], BytesInBuffer);
  259. ProcessLine(line);
  260. end;
  261. if Assigned(buffer) then
  262. FreeMem(buffer);
  263. end;
  264. procedure TTextDoc.LoadFromFile(const filename: String);
  265. var
  266. stream: TFileStream;
  267. begin
  268. stream := TFileStream.Create(filename, fmOpenRead);
  269. LoadFromStream(stream);
  270. stream.Free;
  271. end;
  272. procedure TTextDoc.SaveToStream(AStream: TStream);
  273. var
  274. i: Integer;
  275. begin
  276. for i := 0 to FLineCount - 2 do
  277. begin
  278. AStream.Write(FLines^[i].s, Length(FLines^[i].s));
  279. AStream.Write(FLineEnding, Length(FLineEnding));
  280. end;
  281. if FLineCount > 0 then
  282. AStream.Write(FLines^[FLineCount - 1].s, Length(FLines^[FLineCount - 1].s));
  283. end;
  284. procedure TTextDoc.SaveToFile(const filename: String);
  285. var
  286. stream: TFileStream;
  287. begin
  288. stream := TFileStream.Create(filename, fmCreate);
  289. SaveToStream(stream);
  290. stream.Free;
  291. end;
  292. procedure TTextDoc.SetModified(AModified: Boolean);
  293. var
  294. i: Integer;
  295. begin
  296. if AModified = FModified then
  297. exit;
  298. FModified := AModified;
  299. for i := 0 to FViewInfos.Count - 1 do
  300. if Assigned(TViewInfo(FViewInfos.Items[i]).OnModifiedChange) then
  301. TViewInfo(FViewInfos.Items[i]).OnModifiedChange(Self);
  302. end;
  303. function TTextDoc.GetLineText(LineNumber: Integer): String;
  304. begin
  305. if (LineNumber < 0) or (LineNumber >= FLineCount) then
  306. Result := ''
  307. else
  308. Result := FLines^[LineNumber].s;
  309. end;
  310. procedure TTextDoc.SetLineText(LineNumber: Integer; const NewText: String);
  311. var
  312. i: Integer;
  313. begin
  314. if FLines^[LineNumber].s <> NewText then
  315. begin
  316. FLines^[LineNumber].s := NewText;
  317. if Length(NewText) > FLineWidth then
  318. FLineWidth := Length(NewText);
  319. Modified := True;
  320. for i := 0 to FViewInfos.Count - 1 do
  321. if Assigned(TViewInfo(FViewInfos.Items[i]).OnLineChange) then
  322. TViewInfo(FViewInfos.Items[i]).OnLineChange(Self, LineNumber);
  323. end;
  324. end;
  325. function TTextDoc.GetLineLen(LineNumber: Integer): Integer;
  326. begin
  327. if (LineNumber < 0) or (LineNumber >= FLineCount) then
  328. Result := 0
  329. else
  330. Result := Length(FLines^[LineNumber].s);
  331. end;
  332. function TTextDoc.GetLineFlags(LineNumber: Integer): Byte;
  333. begin
  334. if (LineNumber < 0) or (LineNumber >= FLineCount) then
  335. Result := 0
  336. else
  337. Result := FLines^[LineNumber].flags;
  338. end;
  339. procedure TTextDoc.SetLineFlags(LineNumber: Integer; NewFlags: Byte);
  340. begin
  341. FLines^[LineNumber].flags := NewFlags;
  342. end;
  343. end.
  344. {
  345. $Log$
  346. Revision 1.3 2000-11-13 15:46:55 marco
  347. * Unix renamefest for defines.
  348. Revision 1.2 2000/07/13 11:33:02 michael
  349. + removed logs
  350. }