unit ce_messages; {$I ce_defines.inc} interface uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, ComCtrls, EditBtn, lcltype, ce_widget, ActnList, Menus, clipbrd, AnchorDocking, math, TreeFilterEdit, Buttons, GraphType, fgl, strutils, ce_ddemangle, ce_writableComponent, ce_common, ce_synmemo, ce_interfaces, ce_observer, ce_sharedres, ce_stringrange, ce_dsgncontrols; type TCEEditorMessagePos = class(specialize TFPGMap<string,integer>); (** * the struct linked to a log message. allow to be filtered. *) PMessageData = ^TMessageData; TMessageData = record ctxt: TCEAppMessageCtxt; data: Pointer; end; TCEMessagesOptions = class(TWritableLfmTextComponent) private fFastDisplay: boolean; fMaxCount: Integer; fAutoSelect: boolean; fSingleClick: boolean; fAutoDemangle: boolean; fAlwaysFilter: boolean; fFont: TFont; fMsgColors: array[TCEAppMessageKind] of TColor; procedure setFont(value: TFont); published property alwaysFilter: boolean read fAlwaysFilter write fAlwaysFilter; property fastDisplay: boolean read fFastDisplay write fFastDisplay; property maxMessageCount: integer read fMaxCount write fMaxCount; property autoSelect: boolean read fAutoSelect write fAutoSelect; property autoDemangle: boolean read fAutoDemangle write fAutoDemangle; property singleMessageClick: boolean read fSingleClick write fSingleClick; property font: TFont read fFont write setFont; property colorBuble: TColor read fMsgColors[amkBub] write fMsgColors[amkBub]; property colorInfo: TColor read fMsgColors[amkInf] write fMsgColors[amkInf]; property colorHint: TColor read fMsgColors[amkHint] write fMsgColors[amkHint]; property colorWarning: TColor read fMsgColors[amkWarn] write fMsgColors[amkWarn]; property colorError: TColor read fMsgColors[amkErr] write fMsgColors[amkErr]; public constructor Create(AOwner: TComponent); override; destructor destroy; override; procedure assign(source: TPersistent); override; procedure AssignTo(target: TPersistent); override; end; // gives access to protected fields that should be actually public // (scroll position needed for custom draw !) TTreeHack = class(TTreeView) end; { TCEMessagesWidget } TCEMessagesWidget = class(TCEWidget, ICEEditableOptions, ICEDocumentObserver, ICEProjectObserver, ICEMessagesDisplay) btnClearCat: TCEToolButton; sepCat: TCEToolButton; btnSelAll: TCEToolButton; btnSelApp: TCEToolButton; btnSelEdit: TCEToolButton; btnSelMisc: TCEToolButton; btnSelProj: TCEToolButton; sep: TCEToolButton; button2: TCEToolButton; button4: TCEToolButton; button6: TCEToolButton; button8: TCEToolButton; List: TTreeView; TreeFilterEdit1: TTreeFilterEdit; procedure ListCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean); procedure ListKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure TreeFilterEdit1AfterFilter(Sender: TObject); procedure TreeFilterEdit1ButtonClick(Sender: TObject); private fImages: TImageList; fEditorMessagePos: TCEEditorMessagePos; fMsgColors: array[TCEAppMessageKind] of TColor; fProjCompile: boolean; fActAutoSel: TAction; fActClearAll: TAction; fActClearCurCat: TAction; fActSaveMsg: TAction; fActCopyMsg: TAction; fActSelAll: TAction; fActDemangle: TAction; fMaxMessCnt: Integer; fAlwaysFilter: boolean; fProj: ICECommonProject; fDoc: TCESynMemo; fCtxt: TCEAppMessageCtxt; fAutoSelect: boolean; fAutoDemangle: boolean; fSingleClick: boolean; fastDisplay: boolean; fOptions: TCEMessagesOptions; fOptionsBackup: TCEMessagesOptions; fBtns: array[TCEAppMessageCtxt] of TToolButton; fFiltering: boolean; function itemShouldBeVisible(item: TTreeNode; aCtxt: TCEAppMessageCtxt): boolean; procedure filterMessages(aCtxt: TCEAppMessageCtxt); procedure clearOutOfRangeMessg; procedure actDemangleExecute(Sender: TObject); procedure actAutoSelExecute(Sender: TObject); procedure actClearCurCatExecute(Sender: TObject); procedure actClearAllExecute(Sender: TObject); procedure actSaveMsgExecute(Sender: TObject); procedure actCopyMsgExecute(Sender: TObject); procedure actSelAllExecute(Sender: TObject); procedure setMaxMessageCount(value: Integer); procedure setAutoSelectCategory(value: boolean); procedure setSingleMessageClick(value: boolean); procedure listDeletion(Sender: TObject; Node: TTreeNode); procedure selCtxtClick(Sender: TObject); function iconIndex(aKind: TCEAppMessageKind): Integer; procedure handleMessageClick(Sender: TObject); // procedure setColorError(value: TColor); procedure setColorInfo(value: TColor); procedure setColorHint(value: TColor); procedure setColorBuble(value: TColor); procedure setColorWarning(value: TColor); // procedure projNew(project: ICECommonProject); procedure projClosing(project: ICECommonProject); procedure projFocused(project: ICECommonProject); procedure projChanged(project: ICECommonProject); procedure projCompiling(project: ICECommonProject); procedure projCompiled(project: ICECommonProject; success: boolean); // procedure docNew(document: TCESynMemo); procedure docClosing(document: TCESynMemo); procedure docFocused(document: TCESynMemo); procedure docChanged(document: TCESynMemo); // function optionedWantCategory(): string; function optionedWantEditorKind: TOptionEditorKind; function optionedWantContainer: TPersistent; procedure optionedEvent(event: TOptionEditorEvent); function optionedOptionsModified: boolean; // function openFileFromDmdMessage(const aMessage: string): boolean; function getLineFromMessage(const aMessage: string): TPoint; function guessMessageKind(const aMessg: string): TCEAppMessageKind; // function singleServiceName: string; procedure message(const value: string; aData: Pointer; aCtxt: TCEAppMessageCtxt; aKind: TCEAppMessageKind); procedure clearbyContext(aCtxt: TCEAppMessageCtxt); procedure clearbyData(data: Pointer); procedure scrollToBack; protected procedure setToolBarFlat(value: boolean); override; procedure updateLoop; override; // function contextName: string; override; function contextActionCount: integer; override; function contextAction(index: integer): TAction; override; // property maxMessageCount: Integer read fMaxMessCnt write setMaxMessageCount; property autoSelectCategory: boolean read fAutoSelect write setAutoSelectCategory; property autoDEmangle: boolean read fAutoDemangle write fAutoDemangle; property singleMessageClick: boolean read fSingleClick write setSingleMessageClick; // property colorBuble: TColor read fMsgColors[amkBub] write setColorBuble; property colorInfo: TColor read fMsgColors[amkInf] write setColorInfo; property colorHint: TColor read fMsgColors[amkHint] write setColorHint; property colorWarning: TColor read fMsgColors[amkWarn] write setColorWarning; property colorError: TColor read fMsgColors[amkErr] write setColorError; public constructor create(aOwner: TComponent); override; destructor destroy; override; end; // Maps an identifier to a message kind type messageSemantic = record private { rendered on 2017-Jan-19 21:11:15.6560057 by IsItThere. - PRNG seed: 0 - map length: 64 - case sensitive: true } const fWords: array [0..63] of string = ('caution', '', '', '', 'Fatal', 'Critical', 'Error', 'Advice', 'Warn', '', '', 'advice', '', 'warning', '', 'information', '', '', '', 'Exception', '', '', 'illegal', '', '', 'Hint', 'errorlevel', 'Warning', 'critical', 'Deprecated', 'Invalid', 'fatal', '', 'Deprecation', 'hint', '', '', 'error', '', '', '', 'Caution', 'Tip', '', 'deprecated', '', '', 'Suggestion', 'deprecation', '', 'exception', 'ERROR', 'Information', '', '', '', 'suggestion', 'invalid', 'warn', 'Illegal', '', '', '', 'tip'); const fFilled: array [0..63] of boolean = (true, false, false, false, true, true, true, true, true, false, false, true, false, true, false, true, false, false, false, true, false, false, true, false, false, true, true, true, true, true, true, true, false, true, true, false, false, true, false, false, false, true, true, false, true, false, false, true, true, false, true, true, true, false, false, false, true, true, true, true, false, false, false, true); const fMap: array [0..63] of TCEAppMessageKind = ( amkWarn, amkBub, amkBub, amkBub, amkErr, amkErr, amkErr, amkHint, amkWarn, amkBub, amkBub, amkHint, amkBub, amkWarn, amkBub, amkInf, amkBub, amkBub, amkBub, amkErr, amkBub, amkBub, amkErr, amkBub, amkBub, amkHint, amkErr, amkWarn, amkErr, amkWarn, amkErr, amkErr, amkBub, amkWarn, amkHint, amkBub, amkBub, amkErr, amkBub, amkBub, amkBub, amkWarn, amkHint, amkBub, amkWarn, amkBub, amkBub, amkHint, amkWarn, amkBub, amkErr, amkErr, amkInf, amkBub, amkBub, amkBub, amkHint, amkErr, amkWarn, amkErr, amkBub, amkBub, amkBub, amkHint); const fCoefficients: array [0..255] of Byte = (151, 145, 214, 156, 15, 232, 156, 185, 180, 64, 178, 95, 6, 249, 69, 68, 240, 111, 93, 41, 229, 240, 146, 62, 148, 157, 106, 190, 120, 112, 104, 207, 85, 123, 228, 254, 43, 2, 236, 108, 39, 221, 41, 251, 144, 192, 247, 101, 210, 134, 105, 39, 208, 115, 116, 65, 209, 36, 237, 87, 195, 162, 142, 33, 203, 95, 12, 200, 124, 111, 9, 145, 187, 238, 173, 155, 214, 127, 229, 197, 232, 87, 213, 92, 39, 25, 218, 24, 193, 223, 45, 35, 157, 4, 34, 244, 154, 99, 21, 95, 203, 14, 100, 113, 68, 201, 199, 174, 249, 30, 153, 251, 122, 129, 244, 229, 188, 101, 103, 138, 164, 136, 188, 209, 164, 192, 76, 159, 40, 182, 137, 202, 107, 115, 9, 23, 14, 166, 47, 71, 243, 156, 148, 176, 187, 247, 143, 124, 180, 14, 250, 157, 212, 18, 151, 246, 174, 222, 41, 114, 148, 24, 34, 97, 116, 37, 173, 30, 177, 20, 55, 18, 15, 149, 94, 129, 87, 72, 25, 3, 82, 200, 198, 214, 49, 228, 10, 39, 191, 83, 128, 30, 117, 209, 216, 152, 89, 237, 253, 24, 173, 116, 65, 64, 55, 222, 210, 243, 140, 82, 219, 8, 35, 13, 123, 43, 15, 72, 174, 28, 10, 242, 74, 136, 18, 198, 247, 240, 196, 146, 49, 39, 175, 186, 38, 8, 110, 101, 179, 242, 152, 251, 227, 60, 25, 31, 123, 80, 149, 187, 67, 157, 120, 84, 83, 192); class function hash(const w: string): Word; static; public class function getType(const w: string): TCEAppMessageKind; static; end; implementation {$R *.lfm} const optname = 'messages.txt'; minColor = $232323; {$REGION TCEMessagesOptions ----------------------------------------------------} constructor TCEMessagesOptions.Create(AOwner: TComponent); begin inherited; fFont := TFont.Create; fFont.Style := [fsBold]; {$IFDEF WINDOWS} fFont.name := 'Consolas'; {$ENDIF} fFont.Size := ScaleY(11,96); fAutoSelect :=true; fMaxCount := 1000; fMsgColors[amkBub] := $FCE7D2; fMsgColors[amkWarn]:= $B3FFFF; fMsgColors[amkErr] := $BDBDFF; fMsgColors[amkInf] := $FFD0A8; fMsgColors[amkHint]:= $C2FFC2; end; destructor TCEMessagesOptions.destroy; begin fFont.Free; inherited; end; procedure TCEMessagesOptions.setFont(value: TFont); begin fFont.Assign(value); end; procedure TCEMessagesOptions.assign(source: TPersistent); var widg : TCEMessagesWidget; opts : TCEMessagesOptions; begin if source is TCEMessagesOptions then begin opts := TCEMessagesOptions(source); fFont.BeginUpdate; fFont.Assign(opts.font); fMaxCount := opts.fMaxCount; fAutoSelect := opts.fAutoSelect; fAutoDemangle:= opts.fAutoDemangle; fSingleClick := opts.fSingleClick; fFastDisplay := opts.fFastDisplay; fMsgColors := opts.fMsgColors; fAlwaysFilter := opts.fAlwaysFilter; fFont.EndUpdate; end else if source is TCEMessagesWidget then begin widg := TCEMessagesWidget(source); fFont.Assign(widg.List.Font); fMaxCount := widg.fMaxMessCnt; fAutoSelect := widg.fAutoSelect; fSingleClick := widg.fSingleClick; fFastDisplay := widg.fastDisplay; fMsgColors := widg.fMsgColors; fAutoDemangle:= widg.fAutoDemangle; fAlwaysFilter:=widg.fAlwaysFilter; end else inherited; end; procedure TCEMessagesOptions.AssignTo(target: TPersistent); var widg : TCEMessagesWidget; begin if target is TCEMessagesWidget then begin widg := TCEMessagesWidget(target); widg.List.Font.Assign(fFont); widg.maxMessageCount := fMaxCount; widg.autoSelectCategory := fAutoSelect; widg.singleMessageClick := fSingleClick; widg.fastDisplay:= fFastDisplay; widg.fMsgColors := fMsgColors; widg.fAutoDemangle:=fAutoDemangle; widg.fAlwaysFilter:=fAlwaysFilter; if fFastDisplay then widg.updaterByLoopInterval:= 70 else widg.updaterByLoopInterval:= 0; end else inherited; end; {$ENDREGION} {$REGION Standard Comp/Obj------------------------------------------------------} constructor TCEMessagesWidget.create(aOwner: TComponent); var fname: string; begin fMaxMessCnt := 500; fAutoSelect := true; fCtxt := amcAll; fActAutoSel := TAction.Create(self); fActAutoSel.Caption := 'Auto select message category'; fActAutoSel.AutoCheck := true; fActAutoSel.OnExecute := @actAutoSelExecute; fActClearAll := TAction.Create(self); fActClearAll.OnExecute := @actClearAllExecute; fActClearAll.caption := 'Clear all messages'; fActClearCurCat := TAction.Create(self); fActClearCurCat.OnExecute := @actClearCurCatExecute; fActClearCurCat.caption := 'Clear filtered messages'; fActCopyMsg := TAction.Create(self); fActCopyMsg.OnExecute := @actCopyMsgExecute; fActCopyMsg.Caption := 'Copy message(s)'; fActSelAll := TAction.Create(self); fActSelAll.OnExecute := @actSelAllExecute; fActSelAll.Caption := 'Select all'; fActSaveMsg := TAction.Create(self); fActSaveMsg.OnExecute := @actSaveMsgExecute; fActSaveMsg.caption := 'Save selected message(s) to...'; fActDemangle := TAction.Create(self); fActDemangle.OnExecute := @actDemangleExecute; fActDemangle.caption := 'Demangle selection'; inherited; fImages:= TImageList.Create(self); Case GetIconScaledSize of iss16: begin fImages.Width:=16; fImages.Height:=16; AssignPng(TreeFilterEdit1.Glyph, 'FILTER_CLEAR'); fImages.AddResourceName(HINSTANCE, 'BALLOON'); fImages.AddResourceName(HINSTANCE, 'INFORMATION'); fImages.AddResourceName(HINSTANCE, 'LIGHTBULB_OFF'); fImages.AddResourceName(HINSTANCE, 'WARNING'); fImages.AddResourceName(HINSTANCE, 'EXCLAMATION'); end; iss24: begin fImages.Width:=24; fImages.Height:=24; AssignPng(TreeFilterEdit1.Glyph, 'FILTER_CLEAR24'); fImages.AddResourceName(HINSTANCE, 'BALLOON24'); fImages.AddResourceName(HINSTANCE, 'INFORMATION24'); fImages.AddResourceName(HINSTANCE, 'LIGHTBULB_OFF24'); fImages.AddResourceName(HINSTANCE, 'WARNING24'); fImages.AddResourceName(HINSTANCE, 'EXCLAMATION24'); end; iss32: begin fImages.Width:=32; fImages.Height:=32; AssignPng(TreeFilterEdit1.Glyph, 'FILTER_CLEAR32'); fImages.AddResourceName(HINSTANCE, 'BALLOON32'); fImages.AddResourceName(HINSTANCE, 'INFORMATION32'); fImages.AddResourceName(HINSTANCE, 'LIGHTBULB_OFF32'); fImages.AddResourceName(HINSTANCE, 'WARNING32'); fImages.AddResourceName(HINSTANCE, 'EXCLAMATION32'); end; end; List.Images := fImages; List.DefaultItemHeight:= ScaleY(22,96); fMsgColors[amkBub] := $FCE7D2; fMsgColors[amkWarn] := $B3FFFF; fMsgColors[amkErr] := $BDBDFF; fMsgColors[amkInf] := $FFD0A8; fMsgColors[amkHint] := $C2FFC2; updaterByLoopInterval := 12; fOptions := TCEMessagesOptions.Create(Self); fOptions.assign(self); fOptions.Name:= 'messageOptions'; fOptionsBackup := TCEMessagesOptions.Create(Self); List.PopupMenu := contextMenu; List.OnDeletion := @ListDeletion; List.OnDblClick := @handleMessageClick; btnSelProj.OnClick := @selCtxtClick; btnSelMisc.OnClick := @selCtxtClick; btnSelEdit.OnClick := @selCtxtClick; btnSelApp.OnClick := @selCtxtClick; btnSelAll.OnClick := @selCtxtClick; fBtns[amcAll] := btnSelAll; fBtns[amcApp] := btnSelApp; fBtns[amcEdit]:= btnSelEdit; fBtns[amcMisc]:= btnSelMisc; fBtns[amcProj]:= btnSelProj; btnClearCat.OnClick := @actClearCurCatExecute; fEditorMessagePos := TCEEditorMessagePos.Create; fname := getCoeditDocPath + optname; if fname.fileExists then begin fOptions.loadFromFile(fname); fOptions.AssignTo(self); end; EntitiesConnector.addObserver(self); EntitiesConnector.addSingleService(self); end; destructor TCEMessagesWidget.destroy; begin fEditorMessagePos.Free; fOptions.saveToFile(getCoeditDocPath + optname); EntitiesConnector.removeObserver(self); inherited; end; procedure TCEMessagesWidget.setToolBarFlat(value: boolean); begin inherited setToolBarFlat(value); TreeFilterEdit1.Flat:=value; end; procedure TCEMessagesWidget.listDeletion(Sender: TObject; Node: TTreeNode); begin if node.data.isNotNil then Dispose(PMessageData(Node.Data)); end; procedure TCEMessagesWidget.ListKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var i: Integer; begin case Key of VK_BACK, VK_DELETE: begin if List.SelectionCount > 0 then begin for i := List.Items.Count-1 downto 0 do if List.Items[i].MultiSelected then List.Items.Delete(List.Items[i]); end else clearbyContext(amcAll); end; VK_UP, VK_DOWN: if fOptions.singleMessageClick then handleMessageClick(nil); VK_RETURN: handleMessageClick(nil); end; end; procedure TCEMessagesWidget.TreeFilterEdit1AfterFilter(Sender: TObject); begin fFiltering := TreeFilterEdit1.Filter.isNotEmpty; filterMessages(fCtxt); end; procedure TCEMessagesWidget.TreeFilterEdit1ButtonClick(Sender: TObject); begin fFiltering := false; filterMessages(fCtxt); end; procedure TCEMessagesWidget.selCtxtClick(Sender: TObject); var btn: TToolButton; i: Integer; begin if sender.isNil then exit; // fCtxt := amcAll; btn := TToolButton(Sender); for i := 0 to toolbar.ButtonCount-1 do toolbar.Buttons[i].Down := toolbar.Buttons[i] = btn; if btn = btnSelAll then fCtxt := amcAll else if btn = btnSelEdit then fCtxt := amcEdit else if btn = btnSelProj then fCtxt := amcProj else if btn = btnSelApp then fCtxt := amcApp else if btn = btnSelMisc then fCtxt := amcMisc; filterMessages(fCtxt); end; procedure TCEMessagesWidget.setMaxMessageCount(value: Integer); begin if value < 5 then value := 5; if fMaxMessCnt = value then exit; fMaxMessCnt := value; clearOutOfRangeMessg; end; procedure TCEMessagesWidget.setAutoSelectCategory(value: boolean); begin fAutoSelect := value; fActAutoSel.Checked:= fAutoSelect; end; procedure TCEMessagesWidget.setSingleMessageClick(value: boolean); begin fSingleClick := value; if fSingleClick then begin List.OnClick := @handleMessageClick; List.OnDblClick:= nil; end else begin List.OnClick := nil; List.OnDblClick:= @handleMessageClick; end; end; procedure TCEMessagesWidget.setColorError(value: TColor); begin fMsgColors[amkErr] := max(value, minColor); List.Invalidate; end; procedure TCEMessagesWidget.setColorInfo(value: TColor); begin fMsgColors[amkInf] := max(value, minColor); List.Invalidate; end; procedure TCEMessagesWidget.setColorHint(value: TColor); begin fMsgColors[amkHint] := max(value, minColor); List.Invalidate; end; procedure TCEMessagesWidget.setColorBuble(value: TColor); begin fMsgColors[amkBub] := max(value, minColor); List.Invalidate; end; procedure TCEMessagesWidget.setColorWarning(value: TColor); begin fMsgColors[amkWarn] := value; List.Invalidate; end; procedure TCEMessagesWidget.ListCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean); var x: integer; rc: TRect; begin rc := node.DisplayRect(false); x := rc.Left + 2 - TTreeHack(list).ScrolledLeft; // warning: the cast may become wrong if the enum is modified. Sender.Canvas.Brush.Color := fMsgColors[TCEAppMessageKind(node.ImageIndex + 1)]; if node.Selected then begin Sender.Canvas.DrawFocusRect(rc); Sender.Canvas.Brush.Color := Sender.Canvas.Brush.Color - minColor; end; Sender.Canvas.FillRect(rc); list.Images.Draw(sender.Canvas, x, (rc.Top + rc.Bottom - list.Images.Height) div 2, node.ImageIndex, Node.NodeEffect); x += list.Images.Width + 5; Sender.Canvas.TextOut(x, rc.Top, node.Text); DefaultDraw := false; end; {$ENDREGION} {$REGION ICEEditableOptions ----------------------------------------------------} function TCEMessagesWidget.optionedWantCategory(): string; begin exit('Messages'); end; function TCEMessagesWidget.optionedWantEditorKind: TOptionEditorKind; begin exit(oekGeneric); end; function TCEMessagesWidget.optionedWantContainer: TPersistent; begin fOptions.assign(self); fOptionsBackup.assign(self); exit(fOptions); end; procedure TCEMessagesWidget.optionedEvent(event: TOptionEditorEvent); begin case event of oeeAccept, oeeSelectCat: fOptionsBackup.assign(fOptions); oeeCancel: fOptions.assign(fOptionsBackup); end; fOptions.AssignTo(self); List.Invalidate; end; function TCEMessagesWidget.optionedOptionsModified: boolean; begin exit(false); end; {$ENDREGION} {$REGION ICEContextualActions---------------------------------------------------} function TCEMessagesWidget.contextName: string; begin result := 'Messages'; end; function TCEMessagesWidget.contextActionCount: integer; begin result := 7; end; function TCEMessagesWidget.contextAction(index: integer): TAction; begin case index of 0: result := fActAutoSel; 1: result := fActClearAll; 2: result := fActClearCurCat; 3: result := fActCopyMsg; 4: result := fActSelAll; 5: result := fActSaveMsg; 6: result := fActDemangle; else result := nil; end; end; procedure TCEMessagesWidget.actDemangleExecute(Sender: TObject); var i: integer; begin for i:= 0 to List.SelectionCount-1 do list.Selections[i].Text := demangle(list.Selections[i].Text); end; procedure TCEMessagesWidget.actAutoSelExecute(Sender: TObject); begin fAutoSelect := fActAutoSel.Checked; fOptions.autoSelect:=fAutoSelect; end; procedure TCEMessagesWidget.actClearAllExecute(Sender: TObject); begin clearbyContext(amcAll); end; procedure TCEMessagesWidget.actClearCurCatExecute(Sender: TObject); begin case fCtxt of amcAll, amcApp, amcMisc : clearbyContext(fCtxt); amcEdit: if fDoc.isNotNil then clearbyData(fDoc); amcProj: if fProj <> nil then clearbyData(fProj); end; end; procedure TCEMessagesWidget.actCopyMsgExecute(Sender: TObject); var i: Integer; str: string = ''; begin for i := 0 to List.Items.Count-1 do if List.Items[i].MultiSelected then str += List.Items[i].Text + LineEnding; Clipboard.AsText := str; end; procedure TCEMessagesWidget.actSelAllExecute(Sender: TObject); var i: Integer; begin List.BeginUpdate; for i := 0 to List.Items.Count-1 do if List.Items[i].Visible then List.Items[i].MultiSelected := true; List.EndUpdate; end; procedure TCEMessagesWidget.actSaveMsgExecute(Sender: TObject); var lst: TStringList; itm: TtreeNode; begin with TSaveDialog.Create(nil) do try if execute then begin lst := TStringList.Create; try for itm in List.Items do lst.Add(itm.Text); lst.SaveToFile(filename.normalizePath); finally lst.Free; end; end; finally free; end; end; {$ENDREGION} {$REGION ICEProjectObserver ----------------------------------------------------} procedure TCEMessagesWidget.projNew(project: ICECommonProject); begin fProj := project; filterMessages(fCtxt); end; procedure TCEMessagesWidget.projClosing(project: ICECommonProject); begin if fProj <> project then exit; // clearbyData(fProj); fProj := nil; filterMessages(fCtxt); end; procedure TCEMessagesWidget.projFocused(project: ICECommonProject); begin if fProj = project then exit; fProj := project; filterMessages(fCtxt); end; procedure TCEMessagesWidget.projChanged(project: ICECommonProject); begin end; procedure TCEMessagesWidget.projCompiling(project: ICECommonProject); begin fProjCompile := true; end; procedure TCEMessagesWidget.projCompiled(project: ICECommonProject; success: boolean); begin fProjCompile := false; end; {$ENDREGION} {$REGION ICEDocumentObserver ---------------------------------------------------} procedure TCEMessagesWidget.docNew(document: TCESynMemo); begin if fDoc.isNotNil and fOptions.fAutoSelect and (fCtxt = amcEdit) then begin if list.Selected.isNotNil then fEditorMessagePos[fDoc.fileName] := list.Selected.Index else fEditorMessagePos[fDoc.fileName] := -1; end; fDoc := document; filterMessages(fCtxt); end; procedure TCEMessagesWidget.docClosing(document: TCESynMemo); begin if document <> fDoc then exit; clearbyData(fDoc); fEditorMessagePos.Remove(fDoc.fileName); fDoc := nil; filterMessages(fCtxt); end; procedure TCEMessagesWidget.docFocused(document: TCESynMemo); var i: integer; begin if fDoc = document then exit; if fDoc.isNotNil and fOptions.fAutoSelect and (fCtxt = amcEdit) then begin if list.Selected.isNotNil then fEditorMessagePos[fDoc.fileName] := list.Selected.Index else fEditorMessagePos[fDoc.fileName] := -1; end; fDoc := document; filterMessages(fCtxt); if fOptions.fAutoSelect and (fCtxt = amcEdit) then begin i := fEditorMessagePos.IndexOf(fDoc.fileName); if i <> -1 then begin i := fEditorMessagePos.Data[i]; if (i <> -1) and (i < list.Items.Count) then begin list.Selected := list.Items[i]; list.Selected.MakeVisible; end; end; end; end; procedure TCEMessagesWidget.docChanged(document: TCESynMemo); begin fDoc := document; end; {$ENDREGION} {$REGION ICEMessagesDisplay ----------------------------------------------------} function TCEMessagesWidget.singleServiceName: string; begin exit('ICEMessagesDisplay'); end; procedure TCEMessagesWidget.message(const value: string; aData: Pointer; aCtxt: TCEAppMessageCtxt; aKind: TCEAppMessageKind); var dt: PMessageData; item: TTreeNode; msg: string; begin showWidget; if not fAlwaysFilter then TreeFilterEdit1.Filter:=''; case fAutoDemangle of false: msg := value; true: msg := demangle(value); end; if aKind = amkAuto then aKind := guessMessageKind(msg); if aCtxt = amcAutoCompile then begin case fProjCompile of false: aCtxt := amcAutoEdit; true: aCtxt := amcAutoProj; end; end; if aCtxt = amcAutoEdit then begin aData := fDoc; aCtxt := amcEdit; end else if aCtxt = amcAutoProj then begin aData := fProj; aCtxt := amcProj; end; dt := new(PMessageData); dt^.data := aData; dt^.ctxt := aCtxt; if fAutoSelect then if fCtxt <> aCtxt then fBtns[aCtxt].Click; if fastDisplay then IncLoopUpdate; item := List.Items.AddObject(nil, msg, dt); item.ImageIndex := iconIndex(aKind); item.SelectedIndex := item.ImageIndex; if not fastDisplay then begin clearOutOfRangeMessg; scrollToBack; TTreeHack(list).scrolledLeft := 0; List.Update; filterMessages(fCtxt); end else if fAlwaysFilter and fFiltering then begin filterMessages(fCtxt); end; end; procedure TCEMessagesWidget.clearByContext(aCtxt: TCEAppMessageCtxt); var i: Integer; msgdt: PMessageData; begin list.BeginUpdate; TreeFilterEdit1.Filter := ''; if aCtxt = amcAll then List.Items.Clear else for i := List.Items.Count-1 downto 0 do begin msgdt := PMessageData(List.Items[i].Data); if msgdt^.ctxt = aCtxt then List.Items.Delete(List.Items[i]); end; list.EndUpdate; end; procedure TCEMessagesWidget.clearByData(data: Pointer); var i: Integer; msgdt: PMessageData; begin if data.isNil then exit; if (TObject(data) = fDoc) and (fDoc.isNotNil) then fEditorMessagePos[fDoc.fileName] := -1; list.BeginUpdate; TreeFilterEdit1.Filter := ''; for i := List.Items.Count-1 downto 0 do begin msgdt := PMessageData(List.Items[i].Data); if (msgdt^.data = data) then List.Items.Delete(List.Items[i]); end; list.EndUpdate; end; {$ENDREGION} {$REGION Messages --------------------------------------------------------------} procedure TCEMessagesWidget.updateLoop; begin if fastDisplay then begin clearOutOfRangeMessg; scrollToBack; List.Update; filterMessages(fCtxt); end; end; function TCEMessagesWidget.iconIndex(aKind: TCEAppMessageKind): Integer; begin case aKind of amkBub: exit(0); amkInf: exit(1); amkHint: exit(2); amkWarn: exit(3); amkErr: exit(4); else exit(0); end; end; procedure TCEMessagesWidget.clearOutOfRangeMessg; begin list.BeginUpdate; while List.Items.Count > fMaxMessCnt do List.Items.Delete(List.Items.GetFirstNode); list.EndUpdate; end; procedure TCEMessagesWidget.scrollToBack; begin if not Visible then exit; if List.BottomItem.isNotNil then List.BottomItem.MakeVisible; end; procedure TCEMessagesWidget.handleMessageClick(Sender: TObject); var pos: TPoint; msg: string; dat: PMessageData; old: TCESynMemo; begin if List.Selected.isNil then exit; if ssCtrl in GetKeyShiftState then exit; old := fDoc; msg := List.Selected.Text; dat := PMessageData(List.Selected.Data); if not openFileFromDmdMessage(msg) then exit; // fixes strange bug : https://github.com/BBasile/Coedit/issues/320 if (fDoc <> old) and fOptions.singleMessageClick and assigned(dat) and (dat^.ctxt = amcEdit) then List.ClearSelection(false); // from here, since a doc has the focus, List.Selected is nil pos := getLineFromMessage(msg); if fDoc.isNil then exit; fDoc.setFocus; fDoc.CaretXY := pos; fDoc.SelectLine; end; function TCEMessagesWidget.itemShouldBeVisible(item: TTreeNode; aCtxt: TCEAppMessageCtxt): boolean; var msgDt: PMessageData; begin result := false; msgDt := PMessageData(item.Data); if (not assigned(msgDt)) then exit; if aCtxt = amcAll then result := true else case msgDt^.ctxt of amcEdit: result := (fDoc = TCESynMemo(msgDt^.data)) and (aCtxt = amcEdit); amcProj: result := (fProj = ICECommonProject(msgDt^.data)) and (aCtxt = amcProj); amcApp: result := aCtxt = amcApp; amcMisc: result := aCtxt = amcMisc; end; end; procedure TCEMessagesWidget.filterMessages(aCtxt: TCEAppMessageCtxt); var itm: TTreeNode; i: integer; begin if updating then exit; List.BeginUpdate; for i := 0 to List.Items.Count-1 do begin itm := List.Items.Item[i]; if fFiltering then itm.Visible := itemShouldBeVisible(itm, aCtxt) and AnsiContainsText(itm.Text, TreeFilterEdit1.Filter) else itm.Visible:= itemShouldBeVisible(itm, aCtxt); itm.Selected := false; end; list.EndUpdate; end; class function messageSemantic.hash(const w: string): Word; var i: integer; begin Result := 0; for i := 1 to length(w) do Result += fCoefficients[Byte(w[i])]; Result := Result and $3F; end; class function messageSemantic.getType(const w: string): TCEAppMessageKind; var h: Word; begin result := amkBub; h := hash(w); if fFilled[h] and (fWords[h] = w) then result := fMap[h]; end; function TCEMessagesWidget.guessMessageKind(const aMessg: string): TCEAppMessageKind; var idt: string; rng: TStringRange = (ptr:nil; pos:0; len: 0); const alp = ['a'..'z', 'A'..'Z', '_']; begin result := amkBub; rng.init(aMessg); while true do begin if rng.empty then break; idt := rng.popUntil(alp)^.takeWhile(alp).yield; if idt = '' then exit; result := messageSemantic.getType(idt); if result <> amkBub then exit; end; end; function TCEMessagesWidget.getLineFromMessage(const aMessage: string): TPoint; var rng: TStringRange = (ptr:nil; pos:0; len: 0); lne: string; col: string = ''; begin Result := Point(-1,-1); if aMessage.isEmpty then exit; rng.init(aMessage); {$IFDEF WINDOWS} if (aMessage.length > 3) and (aMessage[2..3] = ':\') then rng.popFrontN(3); {$ENDIF} rng.popUntil(['(', ':'])^.popWhile(['(', ':']); lne := rng.takeUntil([',', ':', ')', ' ']).yield; if rng.front in [',', ':'] then col := rng.popWhile([',', ':'])^.takeUntil(')').yield; result.y := strToIntDef(lne, -1); result.x := strToIntDef(col, -1); end; function TCEMessagesWidget.openFileFromDmdMessage(const aMessage: string): boolean; var i: integer = 0; ident: string = ''; absName: string; begin result := false; while (true) do begin inc(i); if i > aMessage.length then exit; // '(': line will be indicated after fname // -mixin: dmd, error in mixin(token string) '<fname>-mixinXX<index>(' if isEditable(ident.extractFileExt) and ((aMessage[i] = '(') or (aMessage[i] = ':') or ((aMessage[i] = '-') and (i < aMessage.length-5) and (aMessage[i..i+5] = '-mixin'))) then begin // relative fname if project file is the base path to a rel. fname absName := ExpandFileName(ident); if absName.fileExists then begin getMultiDocHandler.openDocument(absName); exit(true); end; // absolute fname if ident.fileExists then begin getMultiDocHandler.openDocument(ident); exit(true); end; // if fname relative to project path if fProj <> nil then begin absName := expandFilenameEx(fProj.filename.extractFileDir + DirectorySeparator, ident); if absName.fileExists then begin getMultiDocHandler.openDocument(absName); exit(true); end; end; // finally try using pwd ... absName := expandFilenameEx(GetCurrentDir, ident); if absName.fileExists then begin getMultiDocHandler.openDocument(absName); exit(true); end; // ... and $HOME absName := expandFilenameEx(GetUserDir, ident); if absName.fileExists then begin getMultiDocHandler.openDocument(absName); exit(true); end; end // <assertion failure messg>@<filename> else if aMessage[i] = '@' then ident := '' else ident += aMessage[i]; end; end; {$ENDREGION} end.