📄 syneditwordwrap.pas
字号:
{-------------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
the specific language governing rights and limitations under the License.
The Original Code is SynEditWordWrap.pas by Fl醰io Etrusco, released 2003-12-11.
All Rights Reserved.
Contributors to the SynEdit and mwEdit projects are listed in the
Contributors.txt file.
Alternatively, the contents of this file may be used under the terms of the
GNU General Public License Version 2 or later (the "GPL"), in which case
the provisions of the GPL are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms
of the GPL and not to allow others to use your version of this file
under the MPL, indicate your decision by deleting the provisions above and
replace them with the notice and other provisions required by the GPL.
If you do not delete the provisions above, a recipient may use your version
of this file under either the MPL or the GPL.
$Id: SynEditWordWrap.pas,v 1.3 2005/01/08 17:04:29 specu Exp $
You may retrieve the latest version of this file at the SynEdit home page,
located at http://SynEdit.SourceForge.net
Known Issues:
-------------------------------------------------------------------------------}
//todo: Use a single implementation of ReWrapLines that takes starting line and number of lines to rewrap
//todo: Tweak code to try finding better wrapping points. Some support by the highlighters will be needed, probably.
//todo: Document the code
//todo: The length of the last Row of a Line could be calculated from the Line length instead of being stored.
{$IFNDEF QSYNEDITWORDWRAP}
unit SynEditWordWrap;
{$ENDIF}
{$I SynEdit.inc}
interface
uses
{$IFDEF SYN_CLX}
QSynEditTypes,
QSynEditTextBuffer,
QSynEdit,
{$ELSE}
SynEditTypes,
SynEditTextBuffer,
SynEdit,
{$ENDIF}
SysUtils,
Classes;
type
TLineIndex = 0..MaxListSize;
TRowIndex = 0..MaxListSize;
TRowLength = byte;
TRowIndexArray = array [TLineIndex] of TRowIndex;
PRowIndexArray = ^TRowIndexArray;
TRowLengthArray = array [TRowIndex] of TRowLength;
PRowLengthArray = ^TRowLengthArray;
{$IFNDEF SYN_COMPILER_4_UP}
TSysCharSet = set of Char;
{$ENDIF}
// For clarity, I'll refer to buffer coordinates as 'Line' and
// 'Char' and to display (wrapped) coordinates as 'Row' and 'Column'.
// fLineOffsets[n] is the index of the first row of the [n+1]th line.
// e.g. Starting row of first line (0) is 0. Starting row of second line (1)
// is fLineOffsets[0]. Clear?
TSynWordWrapPlugin = class(TInterfacedObject, ISynEditBufferPlugin)
private
fLineOffsets: PRowIndexArray;
fRowLengths: PRowLengthArray;
fLineCapacity: integer;
fRowCapacity: integer;
fLineCount: integer;
//
fEditor: TCustomSynEdit;
fMinRowLength: TRowLength;
fMaxRowLength: TRowLength;
fBreakChars: TSysCharSet;
procedure GrowLines(aMinSize: integer);
procedure MoveLines(aStart: TLineIndex; aMoveBy: integer);
procedure GrowRows(aMinSize: integer);
procedure MoveRows(aStart: TRowIndex; aMoveBy: integer);
procedure SetEmpty;
protected
procedure WrapLines;
function ReWrapLine(aIndex: TLineIndex): integer;
procedure TrimArrays;
property LineOffsets: PRowIndexArray read fLineOffsets;
property RowLengths: PRowLengthArray read fRowLengths;
property Editor: TCustomSynEdit read fEditor;
public
constructor Create(aOwner: TCustomSynEdit);
destructor Destroy; override;
property LineCount: integer read fLineCount;
{ ISynEditBufferPlugin }
function BufferToDisplayPos(const aPos: TBufferCoord): TDisplayCoord;
function DisplayToBufferPos(const aPos: TDisplayCoord): TBufferCoord;
function RowCount: integer;
function GetRowLength(aRow: integer): integer;
function LinesInserted(aIndex: integer; aCount: integer): integer;
function LinesDeleted(aIndex: integer; aCount: integer): integer;
function LinesPutted(aIndex: integer; aCount: integer): integer;
procedure Reset;
procedure DisplayChanged;
end;
implementation
uses
{$IFDEF SYN_COMPILER_6_UP}
RTLConsts,
{$ELSE}
{$IFDEF SYN_CLX}
QConsts;
{$ELSE}
Consts,
{$ENDIF}
{$ENDIF}
{$IFNDEF SYN_COMPILER_4_UP}
SynEditMiscProcs,
{$ENDIF}
Math;
{ TSynWordWrapPlugin }
function TSynWordWrapPlugin.BufferToDisplayPos(
const aPos: TBufferCoord): TDisplayCoord;
var
vStartRow: integer; // first row of the line
cRow: integer;
vRowLen: integer;
begin
Assert( aPos.Char > 0 );
Assert( aPos.Line > 0 );
if LineCount < aPos.Line then
begin
// beyond EOF
Result.Column := aPos.Char;
Result.Row := RowCount + ( aPos.Line - LineCount );
Exit;
end;
if aPos.Line = 1 then
vStartRow := 0
else
vStartRow := fLineOffsets[ aPos.Line -2 ];
vRowLen := 0;
for cRow := vStartRow to fLineOffsets[ aPos.Line -1 ] -1 do
begin
Inc( vRowLen, fRowLengths[cRow] );
if aPos.Char <= vRowLen then
begin
Result.Column := aPos.Char - vRowLen + fRowLengths[cRow];
Result.Row := cRow +1;
Exit;
end;
end;
// beyond EOL
Result.Column := aPos.Char - vRowLen + fRowLengths[ fLineOffsets[ aPos.Line -1 ] -1 ];
Result.Row := fLineOffsets[ aPos.Line -1 ];
end;
constructor TSynWordWrapPlugin.Create(aOwner: TCustomSynEdit);
begin
inherited Create; // just to work as reminder in case I revert it to a TComponent...
if aOwner = nil then
raise Exception.Create( 'TSynWordWrapPlugin must be owned by TCustomSynEdit' );
fEditor := aOwner;
Reset;
end;
destructor TSynWordWrapPlugin.Destroy;
begin
inherited;
FreeMem( fLineOffsets );
FreeMem( fRowLengths );
end;
procedure TSynWordWrapPlugin.DisplayChanged;
begin
if Editor.CharsInWindow <> fMaxRowLength then
Reset;
end;
function TSynWordWrapPlugin.DisplayToBufferPos(
const aPos: TDisplayCoord): TBufferCoord;
var
cLine: integer;
cRow: integer;
begin
Assert( aPos.Column > 0 );
Assert( aPos.Row > 0 );
if aPos.Row > RowCount then
begin
// beyond EOF
Result.Char := aPos.Column;
Result.Line := aPos.Row - RowCount + LineCount;
Exit;
end;
//todo: use a binary search or something smarter
for cLine := LineCount -2 downto 0 do
if aPos.Row > fLineOffsets[cLine] then
begin
Result.Line := cLine +2;
if aPos.Row = fLineOffsets[cLine + 1] then //last row of line
Result.Char := Min(aPos.Column, fMaxRowLength + 1)
else
Result.Char := Min(aPos.Column, fRowLengths[aPos.Row - 1] + 1);
for cRow := fLineOffsets[cLine] to aPos.Row -2 do
Inc(Result.Char, fRowLengths[cRow]);
Exit;
end;
// first line
Result.Line := 1;
if aPos.Row = fLineOffsets[0] then //last row of line
Result.Char := Min(aPos.Column, fMaxRowLength + 1)
else
Result.Char := Min(aPos.Column, fRowLengths[aPos.Row - 1] + 1);
for cRow := 0 to aPos.Row - 2 do
Inc(Result.Char, fRowLengths[cRow]);
end;
function TSynWordWrapPlugin.GetRowLength(aRow: integer): integer;
// aRow is 1-based...
begin
if (aRow <= 0) or (aRow > RowCount) then
TList.Error( SListIndexError, aRow );
Result := fRowLengths[ aRow -1 ];
end;
procedure TSynWordWrapPlugin.GrowLines(aMinSize: integer);
const
vStepSize = 256;
begin
Assert( aMinSize > 0 );
if aMinSize > fLineCapacity then
begin
aMinSize := aMinSize + vStepSize - (aMinSize mod vStepSize);
ReallocMem( fLineOffsets, aMinSize * SizeOf(TRowIndex) );
fLineCapacity := aMinSize;
end;
end;
procedure TSynWordWrapPlugin.GrowRows(aMinSize: integer);
const
vStepSize = 512;
begin
Assert( aMinSize > 0 );
if aMinSize > fRowCapacity then
begin
aMinSize := aMinSize + vStepSize - (aMinSize mod vStepSize);
ReallocMem( fRowLengths, aMinSize * SizeOf(TRowLength) );
fRowCapacity := aMinSize;
end;
end;
function TSynWordWrapPlugin.LinesDeleted(aIndex: integer; aCount: integer): integer;
var
vStartRow: integer;
vEndRow: integer;
cLine: integer;
begin
if fMaxRowLength = 0 then
begin
Result := 0;
Exit;
end;
Assert( aIndex >= 0 );
Assert( aCount >= 1 );
Assert( aIndex + aCount <= LineCount );
//
if aIndex = 0 then
vStartRow := 0
else
vStartRow := fLineOffsets[ aIndex -1 ];
vEndRow := fLineOffsets[ aIndex + aCount -1 ];
Result := vEndRow - vStartRow;
// resize fRowLengths
if vEndRow < RowCount then
MoveRows( vEndRow, - Result );
// resize fLineOffsets
MoveLines( aIndex + aCount, - aCount );
Dec( fLineCount, aCount );
// update offsets
for cLine := aIndex to LineCount -1 do
Dec( fLineOffsets[cLine], Result );
end;
function TSynWordWrapPlugin.LinesInserted(aIndex: integer; aCount: integer): integer;
var
vPrevOffset: TRowIndex;
cLine: integer;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -