Skip to content

Commit a004883

Browse files
committed
File Encoding Dialogs upgrade.
1 parent cb4a4a5 commit a004883

File tree

10 files changed

+252
-138
lines changed

10 files changed

+252
-138
lines changed

Source/Editor.pas

Lines changed: 172 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ interface
2525
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, CodeCompletion, CppParser, SynExportTeX,
2626
SynEditExport, SynExportRTF, Menus, ImgList, ComCtrls, StdCtrls, ExtCtrls, SynEdit, SynEditKeyCmds, version,
2727
SynEditCodeFolding, SynExportHTML, SynEditTextBuffer, Math, StrUtils, SynEditTypes, SynEditHighlighter, DateUtils,
28-
CodeToolTip, CBUtils, System.UITypes, System.Contnrs, SynEditPrint;
28+
CodeToolTip, CBUtils, System.UITypes, System.Contnrs, SynEditPrint, Vcl.ExtDlgs;
2929

3030
type
3131
TCloseTabSheet = class(TTabSheet)
@@ -198,9 +198,9 @@ TEditor = class(TObject)
198198
procedure DebugAfterPaint(ACanvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer);
199199
function GetPageControl: TPageControl;
200200
procedure SetPageControl(Value: TPageControl);
201-
procedure UpdateEncoding(const FileName: string);
201+
function UpdateEncoding(const FileName: string; AEncoding: TEncoding = nil): TEncoding;
202202
public
203-
constructor Create(const Filename: String; InProject, NewFile: boolean; ParentPageControl: TPageControl);
203+
constructor Create(const Filename: String; InProject, NewFile: boolean; ParentPageControl: TPageControl; AEncoding: TEncoding);
204204
destructor Destroy; override;
205205
function Save: boolean;
206206
function SaveAs: boolean;
@@ -235,6 +235,8 @@ TEditor = class(TObject)
235235
property PageControl: TPageControl read GetPageControl write SetPageControl;
236236
property EncodingCorrection: TSymbolCorrectionEncoding read fEncodingCorrection write fEncodingCorrection;
237237
end;
238+
const
239+
CTAB_PADDING = ' ';
238240

239241
implementation
240242

@@ -342,13 +344,118 @@ procedure TDebugGutter.LinesDeleted(FirstLine, Count: integer);
342344
LinesDeletedList(MainForm.FindOutput.Items);
343345
end;
344346

347+
{ Encoding }
348+
349+
function IsStrUTF8(const Tex : AnsiString): boolean;
350+
begin
351+
result := (Tex <> '') and (UTF8Decode(Tex) <> '');
352+
end;
353+
354+
function UTF8FileBOM(const FileName: string): boolean;
355+
var
356+
txt: file;
357+
bytes: array[0..2] of byte;
358+
amt: integer;
359+
begin
360+
361+
FileMode := fmOpenRead;
362+
AssignFile(txt, FileName);
363+
Reset(txt, 1);
364+
365+
try
366+
BlockRead(txt, bytes, 3, amt);
367+
result := (amt=3) and (bytes[0] = $EF) and (bytes[1] = $BB) and (bytes[2] = $BF);
368+
finally
369+
CloseFile(txt);
370+
end;
371+
372+
end;
373+
374+
function FileMayBeUTF8(FileName: WideString): Boolean;
375+
var
376+
Stream: TMemoryStream;
377+
BytesRead: integer;
378+
ArrayBuff: array[0..127] of byte;
379+
PreviousByte: byte;
380+
i: integer;
381+
YesSequences, NoSequences: integer;
382+
383+
begin
384+
if not FileExists(FileName) then
385+
Exit;
386+
YesSequences := 0;
387+
NoSequences := 0;
388+
Stream := TMemoryStream.Create;
389+
try
390+
Stream.LoadFromFile(FileName);
391+
repeat
392+
393+
{read from the TMemoryStream}
394+
395+
BytesRead := Stream.Read(ArrayBuff, High(ArrayBuff) + 1);
396+
{Do the work on the bytes in the buffer}
397+
if BytesRead > 1 then
398+
begin
399+
for i := 1 to BytesRead-1 do
400+
begin
401+
PreviousByte := ArrayBuff[i-1];
402+
if ((ArrayBuff[i] and $c0) = $80) then
403+
begin
404+
if ((PreviousByte and $c0) = $c0) then
405+
begin
406+
inc(YesSequences)
407+
end
408+
else
409+
begin
410+
if ((PreviousByte and $80) = $0) then
411+
inc(NoSequences);
412+
end;
413+
end;
414+
end;
415+
end;
416+
until (BytesRead < (High(ArrayBuff) + 1));
417+
//Below, >= makes ASCII files = UTF-8, which is no problem.
418+
//Simple > would catch only UTF-8;
419+
Result := (YesSequences >= NoSequences);
420+
421+
finally
422+
Stream.Free;
423+
end;
424+
end;
425+
426+
427+
type
428+
TSynEditStringListEx = class(TSynEditStringList);
429+
430+
// similar functions "IsWideCharMappableToAnsi, IsUnicodeStringMappableToAnsi)
431+
// are placed in the module SynUnicode.pas. But those functions, without WC_NO_BEST_FIT_CHARS flag,
432+
// return incorrect results sometimes
433+
function _IsWideCharMappableToAnsi(const WC: WideChar): Boolean;
434+
var
435+
UsedDefaultChar: BOOL;
436+
begin
437+
WideCharToMultiByte(DefaultSystemCodePage, WC_NO_BEST_FIT_CHARS, PWideChar(@WC), 1, nil, 0, nil,
438+
@UsedDefaultChar);
439+
Result := not UsedDefaultChar;
440+
end;
441+
442+
function _IsUnicodeStringMappableToAnsi(const WS: UnicodeString): Boolean;
443+
var
444+
UsedDefaultChar: BOOL;
445+
begin
446+
WideCharToMultiByte(DefaultSystemCodePage, WC_NO_BEST_FIT_CHARS, PWideChar(WS), Length(WS), nil, 0,
447+
nil, @UsedDefaultChar);
448+
Result := not UsedDefaultChar;
449+
end;
450+
451+
345452
{ TEditor }
346453

347454
type
348455
TSynEditPluginInternal = class(TSynEditPlugin)
349456
end;
350457

351-
constructor TEditor.Create(const Filename: String; InProject, NewFile: boolean; ParentPageControl: TPageControl);
458+
constructor TEditor.Create(const Filename: String; InProject, NewFile: boolean; ParentPageControl: TPageControl; AEncoding: TEncoding);
352459
var
353460
s: String;
354461
I: integer;
@@ -374,7 +481,7 @@ constructor TEditor.Create(const Filename: String; InProject, NewFile: boolean;
374481

375482
// Create a new tab
376483
fTabSheet := TCloseTabSheet.Create(ParentPageControl);
377-
fTabSheet.Caption := ExtractFileName(fFilename); // UntitlexX or main.cpp
484+
fTabSheet.Caption := ExtractFileName(fFilename) + CTAB_PADDING; // UntitlexX or main.cpp
378485
fTabSheet.PageControl := ParentPageControl;
379486
fTabSheet.Tag := integer(Self); // Define an index for each tab
380487
fTabSheet.TabVisible := True;
@@ -385,7 +492,22 @@ constructor TEditor.Create(const Filename: String; InProject, NewFile: boolean;
385492

386493
// Load the file using Lines
387494
if not NewFile and FileExists(FileName) then begin
388-
fText.Lines.LoadFromFile(FileName);
495+
496+
if (AEncoding = nil) then
497+
begin
498+
var IsUTF8 := UTF8FileBOM(FileName);
499+
500+
if not IsUTF8 then
501+
IsUTF8 := FileMayBeUTF8(FileName);
502+
503+
if IsUTF8 then
504+
begin
505+
AEncoding := TEncoding.UTF8;
506+
//TSynEditStringListEx(fText.Lines).SetEncoding(AEncoding);
507+
end;
508+
end;
509+
510+
fText.Lines.LoadFromFile(FileName, AEncoding);
389511
fNew := False;
390512

391513
// Save main.cpp as main.123456789.cpp
@@ -958,61 +1080,40 @@ procedure TEditor.UpdateCaption(const NewCaption: String);
9581080
begin
9591081
if Assigned(fTabSheet) then begin
9601082
if NewCaption <> fTabSheet.Caption then begin
961-
fTabSheet.Caption := NewCaption;
1083+
fTabSheet.Caption := NewCaption + CTAB_PADDING;
9621084
end;
9631085
end;
9641086
end;
9651087

966-
type
967-
TSynEditStringListEx = class(TSynEditStringList);
968-
969-
// similar functions "IsWideCharMappableToAnsi, IsUnicodeStringMappableToAnsi)
970-
// are placed in the module SynUnicode.pas. But those functions, without WC_NO_BEST_FIT_CHARS flag,
971-
// return incorrect results sometimes
972-
function _IsWideCharMappableToAnsi(const WC: WideChar): Boolean;
1088+
function TEditor.UpdateEncoding(const FileName: string; AEncoding: TEncoding = nil): TEncoding;
9731089
var
974-
UsedDefaultChar: BOOL;
1090+
LLines: TSynEditStringListEx;
1091+
I: Integer;
9751092
begin
976-
WideCharToMultiByte(DefaultSystemCodePage, WC_NO_BEST_FIT_CHARS, PWideChar(@WC), 1, nil, 0, nil,
977-
@UsedDefaultChar);
978-
Result := not UsedDefaultChar;
979-
end;
980-
981-
function _IsUnicodeStringMappableToAnsi(const WS: UnicodeString): Boolean;
982-
var
983-
UsedDefaultChar: BOOL;
984-
begin
985-
WideCharToMultiByte(DefaultSystemCodePage, WC_NO_BEST_FIT_CHARS, PWideChar(WS), Length(WS), nil, 0,
986-
nil, @UsedDefaultChar);
987-
Result := not UsedDefaultChar;
988-
end;
989-
990-
991-
procedure TEditor.UpdateEncoding(const FileName: string);
992-
var
993-
lines: TSynEditStringListEx;
994-
i: Integer;
995-
begin
996-
lines := TSynEditStringListEx(fText.Lines);
1093+
Result := AEncoding;
1094+
LLines := TSynEditStringListEx(fText.Lines);
9971095

9981096
if (fEncodingCorrection = sceNever)
9991097
or not fText.Modified
1000-
or (lines.Encoding.CodePage = CP_UTF8) then exit;
1098+
or (LLines.Encoding.CodePage = CP_UTF8) then Exit;
10011099

10021100
if fEncodingCorrection = sceFixedUTF8 then
10031101
begin
1004-
lines.SetEncoding(TEncoding.UTF8);
1005-
exit;
1102+
LLines.SetEncoding(TEncoding.UTF8);
1103+
Result := TEncoding.UTF8;
1104+
Exit;
10061105
end;
10071106

1008-
for I := 0 to Lines.Count-1 do
1009-
if _IsUnicodeStringMappableToAnsi(lines[i]) then
1107+
for I := 0 to LLines.Count-1 do
1108+
if IsStrUTF8(LLines[i]) then
1109+
//if _IsUnicodeStringMappableToAnsi(LLines[i]) then
10101110
begin
10111111
if (fEncodingCorrection = sceAuto)
10121112
or (MessageDlg(Format(Lang[ID_MSG_FILESAVEENCODING], [FileName]),
10131113
mtConfirmation, [mbYes, mbNo], 0) = mrYes) then
1014-
lines.SetEncoding(TEncoding.UTF8);
1015-
break;
1114+
LLines.SetEncoding(TEncoding.UTF8);
1115+
Result := TEncoding.UTF8;
1116+
Break;
10161117
end;
10171118
end;
10181119

@@ -1673,7 +1774,7 @@ procedure TEditor.EditorClick(Sender: TObject);
16731774
(fTripleClickMousePos.Line = fDblClickMousePos.Line) then begin
16741775

16751776
// Don't let the editor change the caret
1676-
fText.StateFlags := fText.StateFlags - [sfWaitForDragging];
1777+
fText.StateFlags := fText.StateFlags - [sfOleDragSource];
16771778

16781779
// Select the current line
16791780
if fText.CaretY < fText.Lines.Count then begin
@@ -2036,13 +2137,28 @@ function TEditor.SaveAs: boolean;
20362137
var
20372138
UnitIndex: integer;
20382139
SaveFileName: String;
2140+
LEncIndex: Integer;
2141+
LEncoding: TEncoding;
20392142
begin
20402143
Result := True;
2041-
with TSaveDialog.Create(nil) do try
2144+
with TSaveTextFileDialog.Create(nil) do try
20422145
Title := Lang[ID_NV_SAVEAS];
20432146
Filter := BuildFilter([FLT_CS, FLT_CPPS, FLT_HEADS, FLT_RES]);
20442147
Options := Options + [ofOverwritePrompt];
20452148

2149+
if fText.Lines.Encoding=TEncoding.UTF8 then
2150+
EncodingIndex := 4;
2151+
if fText.Lines.Encoding=TEncoding.UTF7 then
2152+
EncodingIndex := 5;
2153+
if fText.Lines.Encoding=TEncoding.BigEndianUnicode then
2154+
EncodingIndex := 3;
2155+
if fText.Lines.Encoding=TEncoding.Unicode then
2156+
EncodingIndex := 2;
2157+
if fText.Lines.Encoding=TEncoding.ASCII then
2158+
EncodingIndex := 1;
2159+
if fText.Lines.Encoding=TEncoding.ANSI then
2160+
EncodingIndex := 0;
2161+
20462162
// select appropriate filter
20472163
if GetFileTyp(fFileName) in [utcHead, utcppHead] then begin
20482164
FilterIndex := 4; // .h
@@ -2072,8 +2188,14 @@ function TEditor.SaveAs: boolean;
20722188

20732189
// Open the save box
20742190
if Execute then
2191+
begin
2192+
LEncIndex := EncodingIndex;
2193+
LEncoding := StandardEncodingFromName(Encodings[LEncIndex]);
2194+
20752195
SaveFileName := FileName // prevent collision between TEditor.FileName and Dialog.FileName
2076-
else begin
2196+
end
2197+
else
2198+
begin
20772199
Result := False;
20782200
Exit;
20792201
end;
@@ -2086,8 +2208,7 @@ function TEditor.SaveAs: boolean;
20862208

20872209
// Try to save to disk
20882210
try
2089-
UpdateEncoding(fFileName);
2090-
fText.Lines.SaveToFile(SaveFileName);
2211+
fText.Lines.SaveToFile(SaveFileName, UpdateEncoding(SaveFileName, LEncoding));
20912212
fText.Modified := False;
20922213
fNew := False;
20932214
except
@@ -2104,7 +2225,7 @@ function TEditor.SaveAs: boolean;
21042225
if UnitIndex <> -1 then
21052226
MainForm.Project.SaveUnitAs(UnitIndex, SaveFileName); // save as new filename
21062227
end else
2107-
fTabSheet.Caption := ExtractFileName(SaveFileName);
2228+
fTabSheet.Caption := ExtractFileName(SaveFileName) + CTAB_PADDING;
21082229

21092230
// Update window captions
21102231
MainForm.UpdateAppTitle;
@@ -2336,7 +2457,7 @@ procedure TSynEditEx.ExecuteCommand(Command: TSynEditorCommand; AChar: WideChar;
23362457
OrigBlockEnd,
23372458
'',
23382459
smNormal);
2339-
UndoList.AddChange(crDragDropInsert,
2460+
UndoList.AddChange(crInsert, //crDragDropInsert,
23402461
MoveDelim, // put at start of line me moved down
23412462
BlockEnd, // modified
23422463
SelText + #13#10 + S,
@@ -2380,7 +2501,7 @@ procedure TSynEditEx.ExecuteCommand(Command: TSynEditorCommand; AChar: WideChar;
23802501
try
23812502
// backup original selection
23822503
UndoList.AddChange(crSelection, OrigBlockBegin, OrigBlockEnd, '', smNormal);
2383-
UndoList.AddChange(crDragDropInsert,
2504+
UndoList.AddChange(crInsert, //crDragDropInsert,
23842505
BlockBegin, // modified
23852506
MoveDelim, // put at end of line me moved up
23862507
S + #13#10 + SelText,

0 commit comments

Comments
 (0)