diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e6c758e..ef8020d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## Enhancements
+- Added the _Git Blame_ widget.
- Search & Replace, add visual feedback in the editor.
## Bugs fixed
diff --git a/lazproj/dexed.lpi b/lazproj/dexed.lpi
index 5ee3cf78..036c3b82 100644
--- a/lazproj/dexed.lpi
+++ b/lazproj/dexed.lpi
@@ -553,7 +553,7 @@
-
+
@@ -886,6 +886,13 @@
+
+
+
+
+
+
+
diff --git a/lazproj/dexed.lpr b/lazproj/dexed.lpr
index 2432ccd8..8c224d65 100644
--- a/lazproj/dexed.lpr
+++ b/lazproj/dexed.lpr
@@ -14,7 +14,7 @@ uses
u_lcldragdrop, u_stringrange, u_dlangmaps, u_projgroup, u_projutils,
u_d2synpresets, u_dbgitf, u_ddemangle, u_dubproject, LCLVersion,
u_halstead, u_diff, u_profileviewer, u_semver, u_term, u_simpleget,
- u_makeproject;
+ u_makeproject, u_blame;
{$if lcl_fullversion < 2020000}
{$ERROR Lazarus version >= 2.2 required}
diff --git a/src/u_blame.lfm b/src/u_blame.lfm
new file mode 100644
index 00000000..0be6ccb2
--- /dev/null
+++ b/src/u_blame.lfm
@@ -0,0 +1,241 @@
+inherited BlameWidget: TBlameWidget
+ Left = 1275
+ Height = 282
+ Top = 652
+ Width = 563
+ Caption = 'GIT Blame'
+ ClientHeight = 282
+ ClientWidth = 563
+ inherited Content: TPanel
+ Height = 246
+ Top = 36
+ Width = 563
+ ClientHeight = 246
+ ClientWidth = 563
+ object btnBlame: TButton[0]
+ Left = 8
+ Height = 32
+ Top = 206
+ Width = 547
+ Align = alClient
+ BorderSpacing.Around = 8
+ Caption = 'Blame'
+ TabOrder = 0
+ OnClick = btnBlameClick
+ end
+ object grpGen: TGroupBox[1]
+ Left = 4
+ Height = 95
+ Top = 4
+ Width = 555
+ Align = alTop
+ AutoSize = True
+ BorderSpacing.Around = 4
+ Caption = 'General'
+ ClientHeight = 76
+ ClientWidth = 553
+ TabOrder = 1
+ object Panel1: TPanel
+ Left = 4
+ Height = 32
+ Top = 4
+ Width = 545
+ Align = alTop
+ BorderSpacing.Around = 4
+ BevelColor = clNone
+ BevelOuter = bvNone
+ ClientHeight = 32
+ ClientWidth = 545
+ TabOrder = 0
+ object Label1: TLabel
+ Left = 4
+ Height = 24
+ Top = 4
+ Width = 118
+ Align = alLeft
+ Alignment = taCenter
+ BorderSpacing.Around = 4
+ Caption = 'Displayed Revision '
+ end
+ object lblDispRev: TStaticText
+ Left = 126
+ Height = 24
+ Top = 4
+ Width = 365
+ Align = alClient
+ AutoSize = True
+ BorderSpacing.Around = 4
+ BorderStyle = sbsSingle
+ Font.Style = [fsBold]
+ ParentFont = False
+ TabOrder = 0
+ end
+ object btnCpyCurrHash: TSpeedButton
+ Left = 495
+ Height = 32
+ Hint = 'copy current commit hash'
+ Top = 0
+ Width = 25
+ Align = alRight
+ OnClick = btnCpyCurrHashClick
+ end
+ object btnLogCurr: TSpeedButton
+ Left = 520
+ Height = 32
+ Hint = 'show current commit message'
+ Top = 0
+ Width = 25
+ Align = alRight
+ OnClick = btnLogCurrClick
+ end
+ end
+ object Panel4: TPanel
+ Left = 4
+ Height = 32
+ Top = 40
+ Width = 545
+ Align = alTop
+ BorderSpacing.Around = 4
+ BevelColor = clNone
+ BevelOuter = bvNone
+ ClientHeight = 32
+ ClientWidth = 545
+ TabOrder = 1
+ object Label4: TLabel
+ Left = 4
+ Height = 24
+ Top = 4
+ Width = 60
+ Align = alLeft
+ Alignment = taCenter
+ BorderSpacing.Around = 4
+ Caption = 'Filename '
+ end
+ object lblFname: TStaticText
+ Left = 68
+ Height = 24
+ Top = 4
+ Width = 473
+ Align = alClient
+ AutoSize = True
+ BorderSpacing.Around = 4
+ BorderStyle = sbsSingle
+ Font.Style = [fsBold]
+ ParentFont = False
+ TabOrder = 0
+ end
+ end
+ end
+ object grpLine: TGroupBox[2]
+ Left = 4
+ Height = 95
+ Top = 103
+ Width = 555
+ Align = alTop
+ AutoSize = True
+ BorderSpacing.Around = 4
+ Caption = 'Current line'
+ ClientHeight = 76
+ ClientWidth = 553
+ TabOrder = 2
+ object Panel2: TPanel
+ Left = 4
+ Height = 32
+ Top = 4
+ Width = 545
+ Align = alTop
+ BorderSpacing.Around = 4
+ BevelColor = clNone
+ BevelOuter = bvNone
+ ClientHeight = 32
+ ClientWidth = 545
+ TabOrder = 0
+ object Label2: TLabel
+ Left = 4
+ Height = 24
+ Top = 4
+ Width = 111
+ Align = alLeft
+ Alignment = taCenter
+ BorderSpacing.Around = 4
+ Caption = 'Previous Revision '
+ end
+ object lblPrevRev: TStaticText
+ Left = 119
+ Height = 24
+ Top = 4
+ Width = 372
+ Align = alClient
+ AutoSize = True
+ BorderSpacing.Around = 4
+ BorderStyle = sbsSingle
+ Font.Style = [fsBold]
+ ParentFont = False
+ TabOrder = 0
+ end
+ object btnCpyPrevHash: TSpeedButton
+ Left = 495
+ Height = 32
+ Hint = 'copy prior commit hash'
+ Top = 0
+ Width = 25
+ Align = alRight
+ OnClick = btnCpyPrevHashClick
+ end
+ object btnLogPrev: TSpeedButton
+ Left = 520
+ Height = 32
+ Hint = 'show prior commit message'
+ Top = 0
+ Width = 25
+ Align = alRight
+ OnClick = btnLogPrevClick
+ end
+ end
+ object Panel3: TPanel
+ Left = 4
+ Height = 32
+ Top = 40
+ Width = 545
+ Align = alTop
+ BorderSpacing.Around = 4
+ BevelColor = clNone
+ BevelOuter = bvNone
+ ClientHeight = 32
+ ClientWidth = 545
+ TabOrder = 1
+ object Label3: TLabel
+ Left = 4
+ Height = 24
+ Top = 4
+ Width = 106
+ Align = alLeft
+ Alignment = taCenter
+ BorderSpacing.Around = 4
+ Caption = 'Author and Date '
+ end
+ object lblAuthDate: TStaticText
+ Left = 114
+ Height = 24
+ Top = 4
+ Width = 427
+ Align = alClient
+ AutoSize = True
+ BorderSpacing.Around = 4
+ BorderStyle = sbsSingle
+ Font.Style = [fsBold]
+ ParentFont = False
+ TabOrder = 0
+ end
+ end
+ end
+ end
+ inherited toolbar: TDexedToolBar
+ Width = 555
+ end
+ object Timer1: TTimer[3]
+ Enabled = False
+ OnTimer = Timer1Timer
+ Left = 32
+ end
+end
diff --git a/src/u_blame.pas b/src/u_blame.pas
new file mode 100644
index 00000000..4d2c7b74
--- /dev/null
+++ b/src/u_blame.pas
@@ -0,0 +1,501 @@
+unit u_blame;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls,
+ Buttons, ghashmap, process, Clipbrd,
+ u_common, u_interfaces, u_observer, u_widget,
+ u_synmemo, u_stringrange, u_sharedres;
+
+type
+
+ TLineData = record
+ // the previous git hash
+ hash: string;
+ // if file has ever moved
+ filename: string;
+ // data, author or prev
+ info: string;
+ end;
+
+ TBlameDataKind = (
+ bdkNone, // no yet computed
+ bdkFirst, // initial blame on HEAD
+ bdkBlame // display in a dedicated editor
+ );
+
+ TEditorData = class
+ private
+ // if this is a standard editor or one opened to start blaming
+ kind: TBlameDataKind;
+ // the filename in git
+ filename: string;
+ // current revision
+ currHash: string;
+ // previous revision and fname for each line
+ lineData: array of TLineData;
+ end;
+
+ TEditorToData = specialize THashmap;
+
+ { TBlameWidget }
+
+ TBlameWidget = class(TDexedWidget, IDocumentObserver, IProjectObserver)
+ btnBlame: TButton;
+ btnCpyPrevHash: TSpeedButton;
+ btnLogCurr: TSpeedButton;
+ btnLogPrev: TSpeedButton;
+ grpGen: TGroupBox;
+ Panel4: TPanel;
+ grpLine: TGroupBox;
+ Label1: TLabel;
+ Label2: TLabel;
+ Label3: TLabel;
+ Panel1: TPanel;
+ Label4: TLabel;
+ lblFname: TStaticText;
+ Panel2: TPanel;
+ Panel3: TPanel;
+ lblDispRev: TStaticText;
+ lblPrevRev: TStaticText;
+ lblAuthDate: TStaticText;
+ btnCpyCurrHash: TSpeedButton;
+ Timer1: TTimer;
+ procedure btnBlameClick(Sender: TObject);
+ procedure btnCpyCurrHashClick(Sender: TObject);
+ procedure btnCpyPrevHashClick(Sender: TObject);
+ procedure btnLogCurrClick(Sender: TObject);
+ procedure btnLogPrevClick(Sender: TObject);
+ procedure Timer1Timer(Sender: TObject);
+ private
+ fEditors: TEditorToData;
+ fDocData: TEditorData;
+ fDoc: TDExedMemo;
+ fProj: ICommonProject;
+ function getGitCwd(): string;
+ procedure showLog(const hash: string);
+ function checkTool(var exename: string): boolean;
+ protected
+ procedure setVisible(Value: Boolean); override;
+ procedure setToolBarFlat(value: boolean); override;
+ public
+ constructor create(aOwner: TComponent); override;
+ destructor destroy(); override;
+
+ procedure docNew(document: TDexedMemo);
+ procedure docFocused(document: TDexedMemo);
+ procedure docChanged(document: TDexedMemo);
+ procedure docClosing(document: TDexedMemo);
+
+ procedure projNew(project: ICommonProject);
+ procedure projChanged(project: ICommonProject);
+ procedure projClosing(project: ICommonProject);
+ procedure projFocused(project: ICommonProject);
+ procedure projCompiling(project: ICommonProject);
+ procedure projCompiled(project: ICommonProject; success: boolean);
+
+ procedure updateView();
+
+ procedure blameToData(gitLines: TStrings; blameLines: TStrings);
+ procedure blameBegin();
+ procedure blameContinue();
+
+ end;
+
+implementation
+{$R *.lfm}
+
+constructor TBlameWidget.create(aOwner: TComponent);
+begin
+ inherited;
+ fEditors := TEditorToData.create();
+ toolbarVisible:= false;
+ case GetIconScaledSize of
+ iss16:
+ begin
+ AssignPng(btnCpyCurrHash, 'COPY');
+ AssignPng(btnCpyPrevHash, 'COPY');
+ AssignPng(btnLogCurr, 'GIT');
+ AssignPng(btnLogPrev, 'GIT');
+ end;
+ iss24:
+ begin
+ AssignPng(btnCpyCurrHash, 'COPY24');
+ AssignPng(btnCpyPrevHash, 'COPY24');
+ AssignPng(btnLogCurr, 'GIT24');
+ AssignPng(btnLogPrev, 'GIT24');
+ end;
+ iss32:
+ begin
+ AssignPng(btnCpyCurrHash, 'COPY32');
+ AssignPng(btnCpyPrevHash, 'COPY32');
+ AssignPng(btnLogCurr, 'GIT32');
+ AssignPng(btnLogPrev, 'GIT32');
+ end;
+ end;
+ EntitiesConnector.addObserver(self);
+end;
+
+destructor TBlameWidget.destroy();
+begin
+ fEditors.free();
+ inherited;
+end;
+
+procedure TBlameWidget.setVisible(Value: Boolean);
+begin
+ inherited SetVisible(value);
+ if Timer1.isAssigned then
+ Timer1.Enabled := value;
+end;
+
+procedure TBlameWidget.setToolBarFlat(value: boolean);
+begin
+ inherited;
+ btnLogCurr.Flat := value;
+ btnCpyCurrHash.Flat := value;
+ btnLogPrev.Flat := value;
+ btnCpyPrevHash.Flat := value;
+end;
+
+procedure TBlameWidget.docNew(document: TDexedMemo);
+begin
+end;
+
+procedure TBlameWidget.docFocused(document: TDexedMemo);
+begin
+ fDoc := document;
+ if fEditors.contains(document) then
+ begin
+ fDocData := fEditors[document];
+ end else
+ begin
+ fDocData := TEditorData.Create;
+ fEditors.insert(fDoc, fDocData);
+ end;
+end;
+
+procedure TBlameWidget.docChanged(document: TDexedMemo);
+begin
+end;
+
+procedure TBlameWidget.docClosing(document: TDexedMemo);
+var
+ closingData: TEditorData = nil;
+begin
+ if fEditors.contains(document) then
+ begin
+ closingData := fEditors[document];
+ closingData.Free();
+ fEditors.delete(document);
+ end;
+ if fDoc = document then
+ fDoc := nil;
+end;
+
+procedure TBlameWidget.projNew(project: ICommonProject);
+begin
+end;
+
+procedure TBlameWidget.projChanged(project: ICommonProject);
+begin
+end;
+
+procedure TBlameWidget.projClosing(project: ICommonProject);
+begin
+ if project = fProj then
+ fProj := nil;
+end;
+
+procedure TBlameWidget.projFocused(project: ICommonProject);
+begin
+ fProj := project;
+end;
+
+procedure TBlameWidget.projCompiling(project: ICommonProject);
+begin
+end;
+
+procedure TBlameWidget.projCompiled(project: ICommonProject; success: boolean);
+begin
+end;
+
+procedure TBlameWidget.blameToData(gitLines: TStrings; blameLines: TStrings);
+var
+ i : integer;
+ line: string;
+ rng : TStringRange = (ptr:nil; pos:0; len:0);
+ tmp : string;
+begin
+ setLength(fDocData.lineData, gitLines.Count);
+ for i := 0 to gitLines.count-1 do
+ begin
+ line:= gitLines[i];
+ rng := TStringRange.create(line);
+ // hash
+ tmp := rng.takeUntil(' ').yield().ToUpper;
+ fDocData.lineData[i].hash := tmp;
+ rng.popFront();
+ // optional filename
+ if rng.front() <> '(' then
+ begin
+ tmp := rng.takeUntil('(').yield();
+ tmp := TrimRight(tmp);
+ fDocData.lineData[i].filename := tmp;
+ rng.popFront();
+ end;
+ // date, author
+ tmp := rng.takeUntil(')').yield();
+ fDocData.lineData[i].info := tmp;
+ rng.popFront;
+ rng.popFront;
+ // code
+ if blameLines.isAssigned() then
+ begin
+ tmp := rng.takeUntil([#13,#10]).yield();
+ blameLines.Add(tmp);
+ end;
+ end;
+end;
+
+function TBlameWidget.getGitCwd():string;
+var
+ old: string = '';
+begin
+ result := '';
+ if assigned(fProj) then
+ begin
+ result := fProj.filename.extractFileDir;
+ end
+ else
+ if fDoc.isAssigned then
+ begin
+ result := fDoc.fileName;
+ while true do
+ begin
+ result := result.extractFileDir;
+ if result = old then
+ exit;
+ if (result + DirectorySeparator + '.git').dirExists then
+ exit;
+ old := result;
+ end;
+ end;
+end;
+
+function TBlameWidget.checkTool(var exename: string): boolean;
+begin
+ exename := exeFullName('git' + exeExt);
+ result := exename.fileExists();
+ if not result then
+ getMessageDisplay().message('cannot locate the `git` executable', nil, amcApp, amkErr);
+end;
+
+procedure TBlameWidget.blameBegin();
+var
+ p: TProcess = nil;
+ s: TStringList = nil;
+ d: IMessagesDisplay = nil;
+ i: integer;
+ g: string = '';
+begin
+ if fDoc.isNotAssigned or not checkTool(g) then
+ exit;
+ p := TProcess.create(nil);
+ s := TStringList.Create();
+ try
+ p.Executable:= g;
+ p.Options := [poUsePipes, poNoConsole];
+ p.ShowWindow:= swoHIDE;
+ p.CurrentDirectory:= getGitCwd();
+ p.Parameters.AddStrings([ 'blame', fDoc.fileName]);
+ p.execute();
+ processOutputToStrings(p,s);
+ while p.Running do ;
+ if p.ExitCode = 0 then
+ begin
+ fDocData.filename := fDoc.fileName;
+ blameToData(s,nil);
+ fDocData.kind := bdkFirst;
+ end else
+ begin
+ d := getMessageDisplay();
+ s.LoadFromStream(p.Stderr);
+ for i := 0 to s.Count-1 do
+ d.message(s[i], fProj, amcMisc, amkAuto);
+ end;
+ finally
+ p.free();
+ s.free();
+ end;
+end;
+
+procedure TBlameWidget.blameContinue();
+var
+ newDoc: TDexedMemo = nil;
+ oldDoc: TDexedMemo = nil;
+ p: TProcess = nil;
+ s: TStringList = nil;
+ d: TLineData;
+ m: IMessagesDisplay = nil;
+ n: string = '';
+ h: string;
+ i: integer;
+ g: string = '';
+begin
+ if fDoc.isNotAssigned or not checkTool(g) then
+ exit;
+ oldDoc := fDoc;
+ d := fDocData.lineData[fDoc.CaretY-1];
+ h := d.hash;
+ if d.filename.isNotEmpty then
+ n := d.filename
+ else if fDocData.kind = bdkFirst then
+ n := fDoc.fileName
+ else
+ n := fDocData.filename;
+
+ p := TProcess.create(nil);
+ s := TStringList.Create();
+ try
+ p.Executable := exeFullName('git' + exeExt);
+ p.Options := [poUsePipes, poNoConsole];
+ p.ShowWindow := swoHIDE;
+ p.CurrentDirectory:= getGitCwd();
+ p.Parameters.AddStrings([ 'blame', n, h]);
+ p.execute();
+ processOutputToStrings(p,s);
+ while p.Running do ;
+ if p.ExitCode = 0 then
+ begin
+ newDoc := TDexedMemo.Create(nil);
+ blameToData(s,newDoc.Lines);
+ fDocData.kind := bdkBlame;
+ fDocData.currHash := h;
+ fDocData.filename := n;
+ newDoc.ReadOnly := true;
+ newDoc.setHighligtherFrom(oldDoc);
+ getMultiDocHandler().forceCaption(newDoc, '');
+ end else
+ begin
+ m := getMessageDisplay();
+ s.LoadFromStream(p.Stderr);
+ for i := 0 to s.Count-1 do
+ m.message(s[i], fProj, amcMisc, amkAuto);
+ end;
+ finally
+ p.free();
+ s.free();
+ end;
+end;
+
+procedure TBlameWidget.updateView();
+var
+ d: TLineData;
+begin
+ if fDocData.isNotAssigned or (not visible) or fDoc.isNotAssigned then
+ exit;
+ if fDocData.kind = bdkNone then
+ begin
+ lblFname.Caption := fDoc.fileName;
+ lblDispRev.Caption := 'HEAD';
+ lblPrevRev.Caption := 'N/A';
+ lblAuthDate.Caption:= 'N/A';
+ btnBlame.Caption := 'Collect initial data';
+ end else
+ begin
+ if fDocData.kind = bdkFirst then
+ begin
+ lblFname.Caption := fDoc.fileName;
+ lblDispRev.Caption:= 'HEAD';
+ end else
+ begin
+ lblFname.Caption := fDocData.fileName;
+ lblDispRev.Caption:= fDocData.currHash;
+ end;
+ d := fDocData.lineData[fDoc.CaretY-1];
+ lblAuthDate.Caption := d.info;
+ if d.hash <> fDocData.currHash then
+ begin
+ btnBlame.Enabled := true;
+ btnBlame.Caption := format('Open blame view for %s', [d.hash]);
+ lblPrevRev.Caption := d.hash;
+ end else
+ begin
+ btnBlame.Enabled := false;
+ btnBlame.Caption := 'Open blame';
+ lblPrevRev.Caption := 'N/A (initial commit)';
+ end;
+ end;
+end;
+
+procedure TBlameWidget.btnBlameClick(Sender: TObject);
+begin
+ if fDocData.isNotAssigned then
+ exit;
+ if fDocData.kind = bdkNone then
+ blameBegin()
+ else
+ blameContinue();
+end;
+
+procedure TBlameWidget.btnCpyCurrHashClick(Sender: TObject);
+begin
+ if fDocData.isAssigned then
+ Clipboard.AsText := fDocData.currHash;
+end;
+
+procedure TBlameWidget.btnCpyPrevHashClick(Sender: TObject);
+begin
+ if fDocData.isAssigned and fDoc.isAssigned then
+ Clipboard.AsText := fDocData.lineData[fDoc.CaretY-1].hash;
+end;
+
+procedure TBlameWidget.btnLogCurrClick(Sender: TObject);
+begin
+ if fDocData.isAssigned then
+ showLog(fDocData.currHash);
+end;
+
+procedure TBlameWidget.btnLogPrevClick(Sender: TObject);
+begin
+ if fDocData.isAssigned and fDoc.isAssigned then
+ showLog(fDocData.lineData[fDoc.CaretY-1].hash);
+end;
+
+procedure TBlameWidget.showLog(const hash: string);
+var
+ p: TProcess;
+ g: TStringList;
+begin
+ p := TProcess.Create(nil);
+ g := TStringList.Create;
+ try
+ p.Executable := exeFullName('git' + exeExt);
+ p.Options := [poUsePipes, poNoConsole];
+ p.ShowWindow := swoHIDE;
+ p.CurrentDirectory:= getGitCwd();
+ p.Parameters.AddStrings([ 'log', hash, '-n1', '--pretty=full']);
+ p.execute();
+ processOutputToStrings(p,g);
+ while p.Running do ;
+ if p.ExitCode = 0 then
+ begin
+ showMessage(g.Text);
+ end;
+ finally
+ p.free;
+ g.free;
+ end;
+end;
+
+procedure TBlameWidget.Timer1Timer(Sender: TObject);
+begin
+ updateView;
+end;
+
+end.
+
diff --git a/src/u_controls.pas b/src/u_controls.pas
index 01029891..615518df 100644
--- a/src/u_controls.pas
+++ b/src/u_controls.pas
@@ -22,9 +22,12 @@ type
private
function getIndex: integer;
protected
+ fFixedCaption: string;
procedure realSetText(const Value: TCaption); override;
+ procedure setFixedCaption(const value: string);
public
property index: integer read getIndex;
+ property fixedCaption: string read fFixedCaption write setFixedCaption;
end;
TPageControlOption = (poPageHistory, poBottomHeader, poFlatButtons);
@@ -171,16 +174,26 @@ procedure TDexedPage.RealSetText(const Value: TCaption);
var
i: integer;
ctrl: TDexedPageControl;
+ v : string;
begin
- inherited;
+ v := value;
+ if fFixedCaption.isNotEmpty then
+ v := fFixedCaption;
+ inherited RealSetText(v);
ctrl := TDexedPageControl(owner);
i := ctrl.getPageIndex(self);
ctrl.fTabs.BeginUpdate;
if i <> -1 then
- ctrl.fTabs.Tabs[i] := value;
+ ctrl.fTabs.Tabs[i] := v;
ctrl.fTabs.EndUpdate;
end;
+procedure TDexedPage.setFixedCaption(const value: string);
+begin
+ fFixedCaption:= value;
+ caption := value;
+end;
+
constructor TDexedPageControl.Create(aowner: TComponent);
begin
inherited;
diff --git a/src/u_editor.pas b/src/u_editor.pas
index ea906bd5..fae78531 100644
--- a/src/u_editor.pas
+++ b/src/u_editor.pas
@@ -205,6 +205,7 @@ type
procedure openDocument(const fname: string);
function closeDocument(index: Integer;promptOnChanged: boolean = true): boolean;
function closeDocument(doc: TDexedMemo;promptOnChanged: boolean = true): boolean;
+ procedure forceCaption(doc: TDexedMemo; value: string);
public
constructor create(aOwner: TComponent); override;
destructor destroy; override;
@@ -669,6 +670,11 @@ begin
exit(false);
exit(closeDocument(page.index, promptOnChanged));
end;
+
+procedure TEditorWidget.forceCaption(doc: TDexedMemo; value: string);
+begin
+ TDexedPage(doc.Parent).fixedCaption := value;
+end;
{$ENDREGION}
{$REGION PageControl/Editor things ---------------------------------------------}
diff --git a/src/u_interfaces.pas b/src/u_interfaces.pas
index f3a4e88b..3810c482 100644
--- a/src/u_interfaces.pas
+++ b/src/u_interfaces.pas
@@ -320,6 +320,8 @@ type
function closeDocument(doc: TDexedMemo; promptOnChanged: boolean = true): boolean;
// conveniance property.
property document[index: integer]: TDexedMemo read getDocument;
+ // force page caption
+ procedure forceCaption(doc: TDexedMemo; value: string);
end;
diff --git a/src/u_main.pas b/src/u_main.pas
index 5c8414da..4d262d2e 100644
--- a/src/u_main.pas
+++ b/src/u_main.pas
@@ -16,7 +16,8 @@ uses
u_toolseditor, u_procinput, u_optionseditor, u_symlist, u_mru, u_processes,
u_infos, u_dubproject, u_dialogs, u_dubprojeditor, u_gdb, u_makeproject,
u_dfmt, u_lcldragdrop, u_projgroup, u_projutils, u_stringrange, u_dexed_d,
- u_halstead, u_profileviewer, u_semver, u_dsgncontrols, u_term, u_newdubproj;
+ u_halstead, u_profileviewer, u_semver, u_dsgncontrols, u_term, u_newdubproj,
+ u_blame;
type
@@ -437,6 +438,7 @@ type
fDubProjWidg: TDubProjectEditorWidget;
fPrjGrpWidg: TProjectGroupWidget;
fGdbWidg: TGdbWidget;
+ fBlameWidg: TBlameWidget;
{$IFDEF UNIX}
fTermWidg: TTermWidget;
{$ENDIF}
@@ -1662,6 +1664,7 @@ begin
fPrjGrpWidg := TProjectGroupWidget.create(self);
fProfWidg := TProfileViewerWidget.create(self);
fGdbWidg := TGdbWidget.create(self);
+ fBlameWidg := TBlameWidget.create(self);
{$IFDEF UNIX}
fTermWidg := TTermWidget.create(self);
{$ENDIF}
@@ -1687,6 +1690,7 @@ begin
fWidgList.addWidget(@fPrjGrpWidg);
fWidgList.addWidget(@fProfWidg);
fWidgList.addWidget(@fGdbWidg);
+ fWidgList.addWidget(@fBlameWidg);
{$IFDEF UNIX}
fWidgList.addWidget(@fTermWidg);
{$ENDIF}
diff --git a/src/u_synmemo.pas b/src/u_synmemo.pas
index 3c08a2ce..8de9e873 100644
--- a/src/u_synmemo.pas
+++ b/src/u_synmemo.pas
@@ -385,6 +385,7 @@ type
procedure redoAll();
procedure undoAll();
procedure scrollCentered(down: boolean);
+ procedure setHighligtherFrom(other: TDexedMemo);
//
property IdentifierMatchOptions: TIdentifierMatchOptions read fMatchOpts write setMatchOpts;
property HighlightedIdent: string read fHighlightedIdent write setHighligthedIdent;
@@ -2169,6 +2170,21 @@ begin
end;
end;
+procedure TDexedMemo.setHighligtherFrom(other: TDexedMemo);
+begin
+ if other.Highlighter = other.TxtHighlighter then
+ Highlighter := TxtHighlighter
+ else if other.Highlighter = other.D2Highlighter then
+ Highlighter := D2Highlighter
+ else if other.Highlighter = other.SxHighlighter then
+ Highlighter := SxHighlighter
+ else if other.Highlighter = other.CppHighlighter then
+ Highlighter := CppHighlighter
+ // LFM, JSON, etc. are shared instances
+ else
+ Highlighter := other.Highlighter;
+end;
+
procedure TDexedMemo.ShowPhobosDoc;
var
str: string;