From 877072dc4896f8df9e36ef604c188fb506a40f01 Mon Sep 17 00:00:00 2001 From: Basile Burg Date: Fri, 29 Mar 2024 17:22:18 +0100 Subject: [PATCH] Add initial support for git blame --- CHANGELOG.md | 1 + lazproj/dexed.lpi | 9 +- lazproj/dexed.lpr | 2 +- src/u_blame.lfm | 241 +++++++++++++++++++++ src/u_blame.pas | 501 +++++++++++++++++++++++++++++++++++++++++++ src/u_controls.pas | 17 +- src/u_editor.pas | 6 + src/u_interfaces.pas | 2 + src/u_main.pas | 6 +- src/u_synmemo.pas | 16 ++ 10 files changed, 796 insertions(+), 5 deletions(-) create mode 100644 src/u_blame.lfm create mode 100644 src/u_blame.pas 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;