mirror of https://gitlab.com/basile.b/dexed.git
Add initial support for git blame
This commit is contained in:
parent
ad9f8ca2d4
commit
877072dc48
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
|
- Added the _Git Blame_ widget.
|
||||||
- Search & Replace, add visual feedback in the editor.
|
- Search & Replace, add visual feedback in the editor.
|
||||||
|
|
||||||
## Bugs fixed
|
## Bugs fixed
|
||||||
|
|
|
||||||
|
|
@ -553,7 +553,7 @@
|
||||||
<PackageName Value="LCL"/>
|
<PackageName Value="LCL"/>
|
||||||
</Item8>
|
</Item8>
|
||||||
</RequiredPackages>
|
</RequiredPackages>
|
||||||
<Units Count="64">
|
<Units Count="65">
|
||||||
<Unit0>
|
<Unit0>
|
||||||
<Filename Value="dexed.lpr"/>
|
<Filename Value="dexed.lpr"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
|
@ -886,6 +886,13 @@
|
||||||
<Filename Value="..\src\u_makeproject.pas"/>
|
<Filename Value="..\src\u_makeproject.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
</Unit63>
|
</Unit63>
|
||||||
|
<Unit64>
|
||||||
|
<Filename Value="..\src\u_blame.pas"/>
|
||||||
|
<IsPartOfProject Value="True"/>
|
||||||
|
<ComponentName Value="BlameWidget"/>
|
||||||
|
<HasResources Value="True"/>
|
||||||
|
<ResourceBaseClass Value="Form"/>
|
||||||
|
</Unit64>
|
||||||
</Units>
|
</Units>
|
||||||
</ProjectOptions>
|
</ProjectOptions>
|
||||||
<CompilerOptions>
|
<CompilerOptions>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ uses
|
||||||
u_lcldragdrop, u_stringrange, u_dlangmaps, u_projgroup, u_projutils,
|
u_lcldragdrop, u_stringrange, u_dlangmaps, u_projgroup, u_projutils,
|
||||||
u_d2synpresets, u_dbgitf, u_ddemangle, u_dubproject, LCLVersion,
|
u_d2synpresets, u_dbgitf, u_ddemangle, u_dubproject, LCLVersion,
|
||||||
u_halstead, u_diff, u_profileviewer, u_semver, u_term, u_simpleget,
|
u_halstead, u_diff, u_profileviewer, u_semver, u_term, u_simpleget,
|
||||||
u_makeproject;
|
u_makeproject, u_blame;
|
||||||
|
|
||||||
{$if lcl_fullversion < 2020000}
|
{$if lcl_fullversion < 2020000}
|
||||||
{$ERROR Lazarus version >= 2.2 required}
|
{$ERROR Lazarus version >= 2.2 required}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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<TDexedMemo, TEditorData, TObjectHash>;
|
||||||
|
|
||||||
|
{ 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, '<blame-only view>');
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
@ -22,9 +22,12 @@ type
|
||||||
private
|
private
|
||||||
function getIndex: integer;
|
function getIndex: integer;
|
||||||
protected
|
protected
|
||||||
|
fFixedCaption: string;
|
||||||
procedure realSetText(const Value: TCaption); override;
|
procedure realSetText(const Value: TCaption); override;
|
||||||
|
procedure setFixedCaption(const value: string);
|
||||||
public
|
public
|
||||||
property index: integer read getIndex;
|
property index: integer read getIndex;
|
||||||
|
property fixedCaption: string read fFixedCaption write setFixedCaption;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TPageControlOption = (poPageHistory, poBottomHeader, poFlatButtons);
|
TPageControlOption = (poPageHistory, poBottomHeader, poFlatButtons);
|
||||||
|
|
@ -171,16 +174,26 @@ procedure TDexedPage.RealSetText(const Value: TCaption);
|
||||||
var
|
var
|
||||||
i: integer;
|
i: integer;
|
||||||
ctrl: TDexedPageControl;
|
ctrl: TDexedPageControl;
|
||||||
|
v : string;
|
||||||
begin
|
begin
|
||||||
inherited;
|
v := value;
|
||||||
|
if fFixedCaption.isNotEmpty then
|
||||||
|
v := fFixedCaption;
|
||||||
|
inherited RealSetText(v);
|
||||||
ctrl := TDexedPageControl(owner);
|
ctrl := TDexedPageControl(owner);
|
||||||
i := ctrl.getPageIndex(self);
|
i := ctrl.getPageIndex(self);
|
||||||
ctrl.fTabs.BeginUpdate;
|
ctrl.fTabs.BeginUpdate;
|
||||||
if i <> -1 then
|
if i <> -1 then
|
||||||
ctrl.fTabs.Tabs[i] := value;
|
ctrl.fTabs.Tabs[i] := v;
|
||||||
ctrl.fTabs.EndUpdate;
|
ctrl.fTabs.EndUpdate;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TDexedPage.setFixedCaption(const value: string);
|
||||||
|
begin
|
||||||
|
fFixedCaption:= value;
|
||||||
|
caption := value;
|
||||||
|
end;
|
||||||
|
|
||||||
constructor TDexedPageControl.Create(aowner: TComponent);
|
constructor TDexedPageControl.Create(aowner: TComponent);
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,7 @@ type
|
||||||
procedure openDocument(const fname: string);
|
procedure openDocument(const fname: string);
|
||||||
function closeDocument(index: Integer;promptOnChanged: boolean = true): boolean;
|
function closeDocument(index: Integer;promptOnChanged: boolean = true): boolean;
|
||||||
function closeDocument(doc: TDexedMemo;promptOnChanged: boolean = true): boolean;
|
function closeDocument(doc: TDexedMemo;promptOnChanged: boolean = true): boolean;
|
||||||
|
procedure forceCaption(doc: TDexedMemo; value: string);
|
||||||
public
|
public
|
||||||
constructor create(aOwner: TComponent); override;
|
constructor create(aOwner: TComponent); override;
|
||||||
destructor destroy; override;
|
destructor destroy; override;
|
||||||
|
|
@ -669,6 +670,11 @@ begin
|
||||||
exit(false);
|
exit(false);
|
||||||
exit(closeDocument(page.index, promptOnChanged));
|
exit(closeDocument(page.index, promptOnChanged));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TEditorWidget.forceCaption(doc: TDexedMemo; value: string);
|
||||||
|
begin
|
||||||
|
TDexedPage(doc.Parent).fixedCaption := value;
|
||||||
|
end;
|
||||||
{$ENDREGION}
|
{$ENDREGION}
|
||||||
|
|
||||||
{$REGION PageControl/Editor things ---------------------------------------------}
|
{$REGION PageControl/Editor things ---------------------------------------------}
|
||||||
|
|
|
||||||
|
|
@ -320,6 +320,8 @@ type
|
||||||
function closeDocument(doc: TDexedMemo; promptOnChanged: boolean = true): boolean;
|
function closeDocument(doc: TDexedMemo; promptOnChanged: boolean = true): boolean;
|
||||||
// conveniance property.
|
// conveniance property.
|
||||||
property document[index: integer]: TDexedMemo read getDocument;
|
property document[index: integer]: TDexedMemo read getDocument;
|
||||||
|
// force page caption
|
||||||
|
procedure forceCaption(doc: TDexedMemo; value: string);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ uses
|
||||||
u_toolseditor, u_procinput, u_optionseditor, u_symlist, u_mru, u_processes,
|
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_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_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
|
type
|
||||||
|
|
||||||
|
|
@ -437,6 +438,7 @@ type
|
||||||
fDubProjWidg: TDubProjectEditorWidget;
|
fDubProjWidg: TDubProjectEditorWidget;
|
||||||
fPrjGrpWidg: TProjectGroupWidget;
|
fPrjGrpWidg: TProjectGroupWidget;
|
||||||
fGdbWidg: TGdbWidget;
|
fGdbWidg: TGdbWidget;
|
||||||
|
fBlameWidg: TBlameWidget;
|
||||||
{$IFDEF UNIX}
|
{$IFDEF UNIX}
|
||||||
fTermWidg: TTermWidget;
|
fTermWidg: TTermWidget;
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
|
|
@ -1662,6 +1664,7 @@ begin
|
||||||
fPrjGrpWidg := TProjectGroupWidget.create(self);
|
fPrjGrpWidg := TProjectGroupWidget.create(self);
|
||||||
fProfWidg := TProfileViewerWidget.create(self);
|
fProfWidg := TProfileViewerWidget.create(self);
|
||||||
fGdbWidg := TGdbWidget.create(self);
|
fGdbWidg := TGdbWidget.create(self);
|
||||||
|
fBlameWidg := TBlameWidget.create(self);
|
||||||
{$IFDEF UNIX}
|
{$IFDEF UNIX}
|
||||||
fTermWidg := TTermWidget.create(self);
|
fTermWidg := TTermWidget.create(self);
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
|
|
@ -1687,6 +1690,7 @@ begin
|
||||||
fWidgList.addWidget(@fPrjGrpWidg);
|
fWidgList.addWidget(@fPrjGrpWidg);
|
||||||
fWidgList.addWidget(@fProfWidg);
|
fWidgList.addWidget(@fProfWidg);
|
||||||
fWidgList.addWidget(@fGdbWidg);
|
fWidgList.addWidget(@fGdbWidg);
|
||||||
|
fWidgList.addWidget(@fBlameWidg);
|
||||||
{$IFDEF UNIX}
|
{$IFDEF UNIX}
|
||||||
fWidgList.addWidget(@fTermWidg);
|
fWidgList.addWidget(@fTermWidg);
|
||||||
{$ENDIF}
|
{$ENDIF}
|
||||||
|
|
|
||||||
|
|
@ -385,6 +385,7 @@ type
|
||||||
procedure redoAll();
|
procedure redoAll();
|
||||||
procedure undoAll();
|
procedure undoAll();
|
||||||
procedure scrollCentered(down: boolean);
|
procedure scrollCentered(down: boolean);
|
||||||
|
procedure setHighligtherFrom(other: TDexedMemo);
|
||||||
//
|
//
|
||||||
property IdentifierMatchOptions: TIdentifierMatchOptions read fMatchOpts write setMatchOpts;
|
property IdentifierMatchOptions: TIdentifierMatchOptions read fMatchOpts write setMatchOpts;
|
||||||
property HighlightedIdent: string read fHighlightedIdent write setHighligthedIdent;
|
property HighlightedIdent: string read fHighlightedIdent write setHighligthedIdent;
|
||||||
|
|
@ -2169,6 +2170,21 @@ begin
|
||||||
end;
|
end;
|
||||||
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;
|
procedure TDexedMemo.ShowPhobosDoc;
|
||||||
var
|
var
|
||||||
str: string;
|
str: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue