doc_text.pp 9.8 KB

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