From: Jean-Philippe Bruyère Date: Mon, 12 Jul 2021 00:37:00 +0000 (+0200) Subject: wip X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=4385f6fdf4fda9461b719270d875e720c16b70e5;p=jp%2Fcrowedit.git wip --- diff --git a/CrowEdit.csproj b/CrowEdit.csproj index ab850da..06689f9 100644 --- a/CrowEdit.csproj +++ b/CrowEdit.csproj @@ -1,9 +1,8 @@  - netcoreapp3.1 + netcoreapp5 WinExe false - @@ -17,14 +16,15 @@ false - all false false - all + + + false diff --git a/CrowEdit.style b/CrowEdit.style index 50f7c1f..ad8f71a 100644 --- a/CrowEdit.style +++ b/CrowEdit.style @@ -1,13 +1,27 @@ SmallUIFont = "sans, 10"; +SmallFont = "consolas, 10"; InactiveTabBackground = "DarkGrey"; SelectedTabBackground = "Onyx"; InactiveTabForeground = "Grey"; SelectedTabForeground = "White"; MenuIconSize = "14"; +ControlHighlight = "RoyalBlue"; + +Splitter { + Thickness="1"; + Background="Transparent"; + Hover="{Background=White}"; + Unhover="{Background=Transparent}"; +} +DockStack { + Margin="0"; + Spacing="0"; +} DockWindow { Template = "#CrowEdit.ui.DockWindow.template"; Background = "DarkGrey"; + Margin="0"; } DockingTabView { Template = "#CrowEdit.ui.DockingTabView.template"; @@ -31,10 +45,3 @@ suggestionsListBox { UseLoadingThread = "false"; } -Editor { - Background="White"; - Foreground="Black"; - Text=""; - Multiline="true"; -} - diff --git a/CrowEditBase/src/CrowEditBase.cs b/CrowEditBase/src/CrowEditBase.cs index f65f786..c232988 100644 --- a/CrowEditBase/src/CrowEditBase.cs +++ b/CrowEditBase/src/CrowEditBase.cs @@ -17,6 +17,17 @@ namespace CrowEditBase string defaultClass; } protected Dictionary FileAssociations = new Dictionary (); + ObservableList logs = new ObservableList(); + public ObservableList MainLog => logs; + + public void Log(LogType type, string message) { + lock (logs) + logs.Add (new LogEntry(type, message)); + } + public void ResetLog () { + lock (logs) + logs.Clear (); + } public void AddFileAssociation (string extension, Type clientClass) { if (!FileAssociations.ContainsKey (extension)) diff --git a/CrowEditBase/src/Debug/BreakPoint.cs b/CrowEditBase/src/Debug/BreakPoint.cs new file mode 100644 index 0000000..09a4df2 --- /dev/null +++ b/CrowEditBase/src/Debug/BreakPoint.cs @@ -0,0 +1,102 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + +using System.Diagnostics; + +namespace CrowEditBase +{ + [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] + public class BreakPoint : CrowEditComponent + { + int index = -1; + public string Function; + string fileFullPath; + int line; + bool isEnabled; + + string type; + string disp; + string warning; + + public int Index { + get => index; + set { + if (index == value) + return; + index = value; + NotifyValueChanged(index); + } + } + public int Line { + get => line; + set { + if (line == value) + return; + line = value; + NotifyValueChanged (line); + } + } + public bool IsEnabled { + get => isEnabled; + set { + if (isEnabled == value) + return; + isEnabled = value; + NotifyValueChanged (isEnabled); + } + } + public string Type { + get => type; + set { + if (type == value) + return; + type = value; + NotifyValueChanged (type); + } + } + public string Disp { + get => disp; + set { + if (disp == value) + return; + disp = value; + NotifyValueChanged (disp); + } + } + public string Warning { + get => warning; + set { + if (warning == value) + return; + warning = value; + NotifyValueChanged (warning); + } + } + public string FileFullPath { + get => fileFullPath; + set { + if (fileFullPath == value) + return; + fileFullPath = value; + NotifyValueChanged (fileFullPath); + } + } + + protected BreakPoint(string fileFullPath, int line, bool isEnabled = true) + { + FileFullPath = fileFullPath; + Line = line; + IsEnabled = isEnabled; + } + + public void UpdateLocation (StackFrame frame) { + //FileName = frame.File; + FileFullPath = frame.FileFullPath; + Function = frame.Function; + Line = frame.Line - 1; + } + public override string ToString() => $"{Index}:{Type} {FileFullPath}:{Line} enabled:{IsEnabled}"; + private string GetDebuggerDisplay() => ToString(); + } +} diff --git a/CrowEditBase/src/Debug/Debugger.cs b/CrowEditBase/src/Debug/Debugger.cs new file mode 100644 index 0000000..a6eadc8 --- /dev/null +++ b/CrowEditBase/src/Debug/Debugger.cs @@ -0,0 +1,155 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System; +using System.Runtime.CompilerServices; +using Crow; + +namespace CrowEditBase +{ + public abstract class Debugger : CrowEditComponent + { + public enum Status + { + /// debugger process created + Init, + /// request loading sent + Starting, + /// executable loaded, breakpoints requested + Ready, + /// running state received + Running, + /// stopped event received + Stopped, + } + + protected Project project; + public Command CMDDebugStart, CMDDebugPause, CMDDebugStop, CMDDebugStepIn, CMDDebugStepOver, CMDDebugStepOut; + public virtual CommandGroup Commands => new CommandGroup ( + CMDDebugStart, CMDDebugPause, CMDDebugStop, CMDDebugStepIn, CMDDebugStepOver, CMDDebugStepOut); + protected virtual void initCommands () { + CMDDebugStart = new Command ("Start", Start, "#Icons.debug-play.svg"); + CMDDebugPause = new Command ("Pause", Pause, "#Icons.debug-pause.svg", false); + CMDDebugStop = new Command ("Stop", Stop, "#Icons.debug-stop.svg", false); + CMDDebugStepIn = new Command ("Step in", StepIn, "#Icons.debug-step-into.svg", false); + CMDDebugStepOut = new Command ("Step out", StepOut, "#Icons.debug-step-out.svg", false); + CMDDebugStepOver = new Command ("Step over", StepOver, "#Icons.debug-step-over.svg", false); + } + + + Status currentState = Status.Init; + bool breakOnStartup = false; + + public Status CurrentState + { + get => currentState; + set + { + if (currentState == value) + return; + currentState = value; + + CMDDebugStepIn.CanExecute = CMDDebugStepOut.CanExecute = CMDDebugStepOver.CanExecute = + (CurrentState == Status.Stopped); + CMDDebugStart.CanExecute = (CurrentState == Status.Ready || CurrentState == Status.Stopped); + CMDDebugPause.CanExecute = CMDDebugStop.CanExecute = (CurrentState == Status.Running); + } + } + StackFrame executingFile; + int executingLine = -1; + + public ObservableList OutputLog = new ObservableList(); + public ObservableList ErrorLog = new ObservableList(); + public ObservableList DebuggerLog = new ObservableList(); + + public ObservableList Frames = new ObservableList(); + public ObservableList Threads = new ObservableList(); + public ObservableList Watches = new ObservableList(); + public ObservableList BreakPoints = new ObservableList(); + + ThreadInfo currentThread; + StackFrame currentFrame; + BreakPoint currentBreakPoint; + + public ThreadInfo CurrentThread + { + get => currentThread; + set + { + if (currentThread == value) + return; + currentThread = value; + NotifyValueChanged(currentThread); + } + } + public StackFrame CurrentFrame + { + get => currentFrame; + set + { + if (currentFrame == value) + return; + currentFrame = value; + NotifyValueChanged(currentFrame); + onCurrentFrameChanged (); + } + } + protected abstract void onCurrentFrameChanged (); + protected abstract void onCurrentThreadChanged (); + public BreakPoint CurrentBreakPoint + { + get => currentBreakPoint; + set + { + if (currentBreakPoint == value) + return; + currentBreakPoint = value; + NotifyValueChanged(currentBreakPoint); + if (currentBreakPoint == null) + return; + //tryGoTo(currentFrame); + } + } + + public bool BreakOnStartup + { + get => breakOnStartup; + set + { + if (BreakOnStartup == value) + return; + breakOnStartup = value; + NotifyValueChanged(breakOnStartup); + } + } + public virtual Project Project + { + get => project; + set + { + if (project == value) + return; + project = value; + NotifyValueChanged(Project); + } + } + + public abstract void Start(); + public abstract void Pause(); + public abstract void Continue(); + public abstract void Stop(); + + public abstract void StepIn(); + public abstract void StepOver(); + public abstract void StepOut(); + + public abstract void InsertBreakPoint(BreakPoint bp); + public abstract void DeleteBreakPoint(BreakPoint bp); + + protected void ResetCurrentExecutingLocation() { + + } + + + } +} diff --git a/CrowEditBase/src/Debug/DebuggerObject.cs b/CrowEditBase/src/Debug/DebuggerObject.cs new file mode 100644 index 0000000..ccacdb7 --- /dev/null +++ b/CrowEditBase/src/Debug/DebuggerObject.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System; +using System.Runtime.CompilerServices; +using Crow; + +namespace CrowEditBase +{ + public class DebuggerObject : IValueChange { + #region IValueChange implementation + public event EventHandler ValueChanged; + public void NotifyValueChanged(string MemberName, object _value) + => ValueChanged.Raise(this, new ValueChangeEventArgs(MemberName, _value)); + + public void NotifyValueChanged(object _value, [CallerMemberName] string caller = null) + => NotifyValueChanged(caller, _value); + #endregion + } +} \ No newline at end of file diff --git a/CrowEditBase/src/Debug/StackFrame.cs b/CrowEditBase/src/Debug/StackFrame.cs new file mode 100644 index 0000000..4a10d17 --- /dev/null +++ b/CrowEditBase/src/Debug/StackFrame.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System.Diagnostics; + +namespace CrowEditBase +{ + [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] + public abstract class StackFrame + { + public int Level; + public string FileFullPath; + public int Line; + public int Column; + public int LineEnd; + public int ColumnEnd; + public string Function; + public string Address; + + public bool IsDefined => !string.IsNullOrEmpty(FileFullPath); + + public override string ToString() => $"{Level}:{FileFullPath}({Line},{Column} {Function})"; + string GetDebuggerDisplay() => ToString(); + } +} diff --git a/CrowEditBase/src/Debug/ThreadInfo.cs b/CrowEditBase/src/Debug/ThreadInfo.cs new file mode 100644 index 0000000..01b1d18 --- /dev/null +++ b/CrowEditBase/src/Debug/ThreadInfo.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System.Diagnostics; + +namespace CrowEditBase +{ + [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] + public class ThreadInfo + { + public int Id; + public string Name; + public bool IsStopped; + public bool IsRunning => !IsStopped; + + public override string ToString() => $"{Id}:{Name} Running:{IsRunning})"; + string GetDebuggerDisplay() => ToString(); + } +} diff --git a/CrowEditBase/src/Debug/Watch.cs b/CrowEditBase/src/Debug/Watch.cs new file mode 100644 index 0000000..cf33260 --- /dev/null +++ b/CrowEditBase/src/Debug/Watch.cs @@ -0,0 +1,128 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System; +using System.Diagnostics; +using Crow; + +namespace CrowEditBase +{ + [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] + public abstract class Watch : CrowEditComponent { + + Debugger dbg; + bool isExpanded; + string name; + string expression; + string value; + bool isEditable; + int numChild; + string type; + int threadId; + + ObservableList children = new ObservableList(); + + public CommandGroup Commands => new CommandGroup ( + new Command ("Update Value", () => UpdateValue()), + new Command ("Delete", () => Delete()) + ); + + public bool HasChildren => NumChild > 0; + + public bool IsExpanded { + get => isExpanded; + set { + if (isExpanded == value) + return; + isExpanded = value; + NotifyValueChanged(isExpanded); + + if (isExpanded) + onExpand(); + } + } + protected abstract void onExpand(); + + public ObservableList Children { + get => children; + set { + if (children == value) + return; + children = value; + NotifyValueChanged (children); + } + } + public string Name { + get => name; + set { + if (name == value) + return; + name = value; + NotifyValueChanged(name); + } + } + public string Expression { + get => expression; + set { + if (expression == value) + return; + expression = value; + NotifyValueChanged(expression); + } + } + public string Value { + get => value; + set { + if (this.value == value) + return; + this.value = value; + NotifyValueChanged(this.value); + } + } + public bool IsEditable { + get => isEditable; + set { + if (isEditable == value) + return; + isEditable = value; + NotifyValueChanged(isEditable); + } + } + public int NumChild { + get => numChild; + set { + if (numChild == value) + return; + numChild = value; + NotifyValueChanged(numChild); + NotifyValueChanged ("HasChildren", HasChildren); + } + } + public string Type { + get => type; + set { + if (type == value) + return; + type = value; + NotifyValueChanged(type); + } + } + public int ThreadId { + get => threadId; + set { + if (threadId == value) + return; + threadId = value; + NotifyValueChanged(threadId); + } + } + + public abstract void Create(); + public abstract void Delete(); + public abstract void UpdateValue (); + + + public override string ToString() => $"{Name}:{Expression} = {Value} [{Type}]"; + string GetDebuggerDisplay() => ToString(); + } +} diff --git a/CrowEditBase/src/Editor.cs b/CrowEditBase/src/Editor.cs index 8cff49e..d9e803f 100644 --- a/CrowEditBase/src/Editor.cs +++ b/CrowEditBase/src/Editor.cs @@ -14,17 +14,22 @@ using System.Reflection; using System.Collections; using CrowEditBase; using System.Threading; +using System.ComponentModel; namespace Crow { public interface IDocumentClient { } - public class Editor : TextBox { + public class Editor : ScrollingObject, IEditableTextWidget { #region CTOR protected Editor () : base () { + KeyEventsOverrides = true;//prevent scrollingObject moves by keyboard + initCommands (); + getLines(); + Thread t = new Thread (backgroundThreadFunc); t.IsBackground = true; t.Start (); @@ -41,33 +46,7 @@ namespace Crow ContextCommands = new CommandGroup (CMDCut, CMDCopy, CMDPaste); } - public override int CurrentColumn { - get => base.CurrentColumn; - set { - if (CurrentColumn == value) - return; - base.CurrentColumn = value; - CMDCopy.CanExecute = CMDCut.CanExecute = !SelectionIsEmpty; - } - } - public override int CurrentLine { - get => base.CurrentLine; - set { - if (CurrentLine == value) - return; - base.CurrentLine = value; - CMDCopy.CanExecute = CMDCut.CanExecute = !SelectionIsEmpty; - } - } - protected override CharLocation? CurrentLoc { - get => base.CurrentLoc; - set { - if (currentLoc == value) - return; - base.CurrentLoc = value; - CMDCopy.CanExecute = CMDCut.CanExecute = !SelectionIsEmpty; - } - } + /*protected override CharLocation? SelectionStart { get => base.SelectionStart; set { @@ -93,18 +72,12 @@ namespace Crow RegisterForGraphicUpdate (); } } - public override void OnTextChanged(object sender, TextChangeEventArgs e) + public event EventHandler TextChanged; + public virtual void OnTextChanged(object sender, TextChangeEventArgs e) { if (disableTextChangedEvent) return; - base.OnTextChanged(sender, e); - } - protected override void onFocused(object sender, EventArgs e) - { - if (CurrentLoc == null) - CurrentLoc = new CharLocation (0, 0); - base.onFocused(sender, e); - (IFace as CrowEditBase.CrowEditBase).CurrentEditor = this; + TextChanged.Raise (this, e); } protected void backgroundThreadFunc () { while (true) { @@ -117,5 +90,839 @@ namespace Crow Thread.Sleep (200); } } + + #region Label + protected string _text = ""; + int targetColumn = -1;//handle line changes with long->short->long line length sequence. + + protected CharLocation? hoverLoc = null; + protected CharLocation? currentLoc = null; + protected CharLocation? selectionStart = null; //selection start (row,column) + + protected virtual CharLocation? CurrentLoc { + get => currentLoc; + set { + if (currentLoc == value) + return; + currentLoc = value; + NotifyValueChanged ("CurrentLine", CurrentLine); + NotifyValueChanged ("CurrentColumn", CurrentColumn); + + CMDCopy.CanExecute = CMDCut.CanExecute = !SelectionIsEmpty; + } + } + public virtual int CurrentLine { + get => currentLoc.HasValue ? currentLoc.Value.Line : 0; + set { + if (currentLoc?.Line == value) + return; + currentLoc = new CharLocation (value, currentLoc.Value.Column, currentLoc.Value.VisualCharXPosition); + NotifyValueChanged ("CurrentLine", CurrentLine); + + CMDCopy.CanExecute = CMDCut.CanExecute = !SelectionIsEmpty; + } + } + public virtual int CurrentColumn { + get => currentLoc.HasValue ? currentLoc.Value.Column < 0 ? 0 : currentLoc.Value.Column : 0; + set { + if (CurrentColumn == value) + return; + currentLoc = new CharLocation (currentLoc.Value.Line, value); + NotifyValueChanged ("CurrentColumn", CurrentColumn); + + CMDCopy.CanExecute = CMDCut.CanExecute = !SelectionIsEmpty; + } + } + /// + /// Set current cursor position in label. + /// + /// Absolute character position in text. + public void SetCursorPosition (int position) { + CharLocation loc = lines.GetLocation (position); + loc.Column = Math.Min (loc.Column, lines[loc.Line].Length); + CurrentLoc = loc; + } + + Color selForeground, selBackground; + protected LineCollection lines; + protected bool textMeasureIsUpToDate = false; + protected object linesMutex = new object (); + protected string LineBreak = null; + protected Size cachedTextSize = default (Size); + protected bool mixedLineBreak = false; + + protected FontExtents fe; + protected TextExtents te; + + + /// + /// Background color for selected text inside this label. + /// + [DefaultValue ("SteelBlue")] + public virtual Color SelectionBackground { + get { return selBackground; } + set { + if (selBackground == value) + return; + selBackground = value; + NotifyValueChangedAuto (selBackground); + RegisterForRedraw (); + } + } + /// + /// Selected text color inside this label. + /// + [DefaultValue("White")] + public virtual Color SelectionForeground { + get { return selForeground; } + set { + if (selForeground == value) + return; + selForeground = value; + NotifyValueChangedAuto (selForeground); + RegisterForRedraw (); + } + } + + + /// + /// Moves cursor one char to the left. + /// + /// true if move succeed + public bool MoveLeft(){ + //targetColumn = -1; + CharLocation loc = CurrentLoc.Value; + if (loc.Column == 0) { + if (loc.Line == 0) + return false; + CurrentLoc = new CharLocation (loc.Line - 1, lines[loc.Line - 1].Length); + }else + CurrentLoc = new CharLocation (loc.Line, loc.Column - 1); + return true; + } + public bool MoveRight () { + targetColumn = -1; + CharLocation loc = CurrentLoc.Value; + if (loc.Column == lines[loc.Line].Length) { + if (loc.Line == lines.Count - 1) + return false; + CurrentLoc = new CharLocation (loc.Line + 1, 0); + } else + CurrentLoc = new CharLocation (loc.Line, loc.Column + 1); + return true; + } + public bool LineMove (int lineDiff) { + CharLocation loc = CurrentLoc.Value; + int newLine = Math.Min (Math.Max (0, loc.Line + lineDiff), lines.Count - 1); + + if (newLine == loc.Line) + return false; + + if (loc.Column > lines[newLine].Length) { + if (targetColumn < 0) + targetColumn = loc.Column; + CurrentLoc = new CharLocation (newLine, lines[newLine].Length); + } else if (targetColumn < 0) + CurrentLoc = new CharLocation (newLine, loc.Column); + else if (targetColumn > lines[newLine].Length) + CurrentLoc = new CharLocation (newLine, lines[newLine].Length); + else + CurrentLoc = new CharLocation (newLine, targetColumn); + + return true; + } + protected int visibleLines => (int)((double)ClientRectangle.Height / (fe.Ascent + fe.Descent)); + public void GotoWordStart(){ + int pos = lines.GetAbsolutePosition (CurrentLoc.Value); + //skip white spaces + while (pos > 0 && !char.IsLetterOrDigit (_text[pos-1])) + pos--; + while (pos > 0 && char.IsLetterOrDigit (_text[pos-1])) + pos--; + CurrentLoc = lines.GetLocation (pos); + } + public void GotoWordEnd(){ + int pos = lines.GetAbsolutePosition (CurrentLoc.Value); + //skip white spaces + while (pos < _text.Length -1 && !char.IsLetterOrDigit (_text[pos])) + pos++; + while (pos < _text.Length - 1 && char.IsLetterOrDigit (_text[pos])) + pos++; + CurrentLoc = lines.GetLocation (pos); + } + + protected void detectLineBreak () { + mixedLineBreak = false; + + if (lines.Count == 0 || lines[0].LineBreakLength == 0) { + LineBreak = Environment.NewLine; + return; + } + LineBreak = _text.GetLineBreak (lines[0]).ToString (); + + for (int i = 1; i < lines.Count; i++) { + ReadOnlySpan lb = _text.GetLineBreak (lines[i]); + if (!lb.SequenceEqual (LineBreak)) { + mixedLineBreak = true; + break; + } + } + } + + protected void getLines () { + if (lines == null) + lines = new LineCollection (10); + else + lines.Clear (); + + if (string.IsNullOrEmpty (_text)) + lines.Add (new TextLine (0, 0, 0)); + else + lines.Update (_text); + } + /// + /// Current Selected text span. May be used to set current position, or current selection. + /// + public TextSpan Selection { + set { + if (value.IsEmpty) + selectionStart = null; + else + selectionStart = lines.GetLocation (value.Start); + CurrentLoc = lines.GetLocation (value.End); + } + get { + if (CurrentLoc == null) + return default; + CharLocation selStart = CurrentLoc.Value, selEnd = CurrentLoc.Value; + if (selectionStart.HasValue) { + if (CurrentLoc.Value.Line < selectionStart.Value.Line) { + selStart = CurrentLoc.Value; + selEnd = selectionStart.Value; + } else if (CurrentLoc.Value.Line > selectionStart.Value.Line) { + selStart = selectionStart.Value; + selEnd = CurrentLoc.Value; + } else if (CurrentLoc.Value.Column < selectionStart.Value.Column) { + selStart = CurrentLoc.Value; + selEnd = selectionStart.Value; + } else { + selStart = selectionStart.Value; + selEnd = CurrentLoc.Value; + } + } + return new TextSpan (lines.GetAbsolutePosition (selStart), lines.GetAbsolutePosition (selEnd)); + } + } + public string SelectedText { + get { + TextSpan selection = Selection; + return selection.IsEmpty ? "" : _text.AsSpan (selection.Start, selection.Length).ToString (); + } + } + public bool SelectionIsEmpty => selectionStart.HasValue ? Selection.IsEmpty : true; + + protected virtual void measureTextBounds (Context gr) { + fe = gr.FontExtents; + te = new TextExtents (); + + cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent + fe.Descent) * Math.Max (1, lines.Count)); + + TextExtents tmp = default; + int longestLine = 0; + for (int i = 0; i < lines.Count; i++) { + if (lines[i].LengthInPixel < 0) { + if (lines[i].Length == 0) + lines.UpdateLineLengthInPixel (i, 0);// (int)Math.Ceiling (fe.MaxXAdvance); + else { + gr.TextExtents (_text.GetLine (lines[i]), Interface.TAB_SIZE, out tmp); + lines.UpdateLineLengthInPixel (i, (int)Math.Ceiling (tmp.XAdvance)); + } + } + if (lines[i].LengthInPixel > lines[longestLine].LengthInPixel) + longestLine = i; + } + cachedTextSize.Width = lines[longestLine].LengthInPixel; + textMeasureIsUpToDate = true; + + updateMaxScrolls (LayoutingType.Height); + updateMaxScrolls (LayoutingType.Width); + } + protected virtual void drawContent (Context gr) { + gr.Translate (-ScrollX, -ScrollY); + + Rectangle cb = ClientRectangle; + fe = gr.FontExtents; + double lineHeight = fe.Ascent + fe.Descent; + + CharLocation selStart = default, selEnd = default; + bool selectionNotEmpty = false; + + if (HasFocus) { + if (currentLoc?.Column < 0) { + updateLocation (gr, cb.Width, ref currentLoc); + NotifyValueChanged ("CurrentColumn", CurrentColumn); + } else + updateLocation (gr, cb.Width, ref currentLoc); + if (selectionStart.HasValue) { + updateLocation (gr, cb.Width, ref selectionStart); + if (CurrentLoc.Value != selectionStart.Value) + selectionNotEmpty = true; + } + if (selectionNotEmpty) { + if (CurrentLoc.Value.Line < selectionStart.Value.Line) { + selStart = CurrentLoc.Value; + selEnd = selectionStart.Value; + } else if (CurrentLoc.Value.Line > selectionStart.Value.Line) { + selStart = selectionStart.Value; + selEnd = CurrentLoc.Value; + } else if (CurrentLoc.Value.Column < selectionStart.Value.Column) { + selStart = CurrentLoc.Value; + selEnd = selectionStart.Value; + } else { + selStart = selectionStart.Value; + selEnd = CurrentLoc.Value; + } + } else + IFace.forceTextCursor = true; + } + + if (!string.IsNullOrEmpty (_text)) { + Foreground?.SetAsSource (IFace, gr); + + TextExtents extents; + Span bytes = stackalloc byte[128]; + double y = 0; + + for (int i = 0; i < lines.Count; i++) { + if (!cancelLinePrint (lineHeight, y, cb.Height)) { + int encodedBytes = -1; + if (lines[i].Length > 0) { + int size = lines[i].Length * 4 + 1; + if (bytes.Length < size) + bytes = size > 512 ? new byte[size] : stackalloc byte[size]; + + encodedBytes = Crow.Text.Encoding.ToUtf8 (_text.GetLine (lines[i]), bytes); + bytes[encodedBytes++] = 0; + + if (lines[i].LengthInPixel < 0) { + gr.TextExtents (bytes.Slice (0, encodedBytes), out extents); + lines.UpdateLineLengthInPixel (i, (int)extents.XAdvance); + } + } + + RectangleD lineRect = new RectangleD ( + (int)cb.X, + y + cb.Top, lines[i].LengthInPixel, lineHeight); + + if (encodedBytes > 0) { + gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent); + gr.ShowText (bytes.Slice (0, encodedBytes)); + } + /********** DEBUG TextLineCollection ************* + gr.SetSource (Colors.Red); + gr.SetFontSize (9); + gr.MoveTo (700, lineRect.Y + fe.Ascent); + gr.ShowText ($"({lines[i].Start}, {lines[i].End}, {lines[i].EndIncludingLineBreak})"); + gr.SetFontSize (Font.Size); + Foreground.SetAsSource (IFace, gr); + ********** DEBUG TextLineCollection *************/ + + if (HasFocus && selectionNotEmpty) { + RectangleD selRect = lineRect; + + if (i >= selStart.Line && i <= selEnd.Line) { + if (selStart.Line == selEnd.Line) { + selRect.X = selStart.VisualCharXPosition + cb.X; + selRect.Width = selEnd.VisualCharXPosition - selStart.VisualCharXPosition; + } else if (i == selStart.Line) { + double newX = selStart.VisualCharXPosition + cb.X; + selRect.Width -= (newX - selRect.X) - 10.0; + selRect.X = newX; + } else if (i == selEnd.Line) { + selRect.Width = selEnd.VisualCharXPosition - selRect.X + cb.X; + } else + selRect.Width += 10.0; + } else { + y += lineHeight; + continue; + } + + gr.SetSource (selBackground); + gr.Rectangle (selRect); + if (encodedBytes < 0) + gr.Fill (); + else { + gr.FillPreserve (); + gr.Save (); + gr.Clip (); + gr.SetSource (SelectionForeground); + gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent); + gr.ShowText (bytes.Slice (0, encodedBytes)); + gr.Restore (); + } + Foreground.SetAsSource (IFace, gr); + } + } + y += lineHeight; + } + } + + gr.Translate (ScrollX, ScrollY); + } + protected virtual void updateHoverLocation (Point mouseLocalPos) { + int hoverLine = (int)Math.Min (Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent + fe.Descent))), lines.Count - 1); + hoverLoc = new CharLocation (hoverLine, -1, mouseLocalPos.X); + using (Context gr = new Context (IFace.surf)) { + setFontForContext (gr); + updateLocation (gr, ClientRectangle.Width, ref hoverLoc); + } + } + protected virtual bool cancelLinePrint (double lineHeght, double y, int clientHeight) => false; + RectangleD? textCursor = null; + public virtual bool DrawCursor (Context ctx, out Rectangle rect) { + if (CurrentLoc == null) { + rect = default; + return false; + } + if (!CurrentLoc.Value.HasVisualX) { + setFontForContext (ctx); + lock (linesMutex) { + if (currentLoc?.Column < 0) { + updateLocation (ctx, ClientRectangle.Width, ref currentLoc); + NotifyValueChanged ("CurrentColumn", CurrentColumn); + } else + updateLocation (ctx, ClientRectangle.Width, ref currentLoc); + } + textCursor = null; + } + + + int lineHeight = (int)(fe.Ascent + fe.Descent); + textCursor = computeTextCursor (new RectangleD (CurrentLoc.Value.VisualCharXPosition, CurrentLoc.Value.Line * lineHeight, 1.0, lineHeight)); + + if (textCursor == null) { + rect = default; + return false; + } + //} + Rectangle c = ScreenCoordinates (textCursor.Value + Slot.Position + ClientRectangle.Position); + ctx.ResetClip (); + Foreground.SetAsSource (IFace, ctx, c); + ctx.LineWidth = 1.0; + ctx.MoveTo (0.5 + c.X, c.Y); + ctx.LineTo (0.5 + c.X, c.Bottom); + ctx.Stroke (); + rect = c; + return true; + } + + protected void updateLocation (Context gr, int clientWidth, ref CharLocation? location) { + if (location == null) + return; + CharLocation loc = location.Value; + //Console.WriteLine ($"updateLocation: {loc} text:{_text.Length}"); + if (loc.HasVisualX) + return; + TextLine ls = lines[loc.Line]; + ReadOnlySpan curLine = _text.GetLine (ls); + double cPos = 0; + + if (loc.Column >= 0) { + //int encodedBytes = Crow.Text.Encoding2.ToUtf8 (curLine.Slice (0, loc.Column), bytes); +#if DEBUG + if (loc.Column > curLine.Length) { + System.Diagnostics.Debug.WriteLine ($"loc.Column: {loc.Column} curLine.Length:{curLine.Length}"); + loc.Column = curLine.Length; + } +#endif + loc.VisualCharXPosition = gr.TextExtents (curLine.Slice (0, loc.Column), Interface.TAB_SIZE).XAdvance + cPos; + location = loc; + } else { + TextExtents te; + Span bytes = stackalloc byte[5];//utf8 single char buffer + '\0' + + for (int i = 0; i < ls.Length; i++) { + int encodedBytes = Crow.Text.Encoding.ToUtf8 (curLine.Slice (i, 1), bytes); + bytes[encodedBytes] = 0; + + gr.TextExtents (bytes, out te); + double halfWidth = te.XAdvance / 2; + + if (loc.VisualCharXPosition <= cPos + halfWidth) { + loc.Column = i; + loc.VisualCharXPosition = cPos; + location = loc; + return; + } + + cPos += te.XAdvance; + } + loc.Column = ls.Length; + loc.VisualCharXPosition = cPos; + location = loc; + } + } + + protected void checkShift () { + if (IFace.Shift) { + if (!selectionStart.HasValue) + selectionStart = CurrentLoc; + } else + selectionStart = null; + } + + #region GraphicObject overrides + public override void OnLayoutChanges (LayoutingType layoutType) { + base.OnLayoutChanges (layoutType); + updateMaxScrolls (layoutType); + } + public override bool UpdateLayout (LayoutingType layoutType) { + if ((LayoutingType.Sizing | layoutType) != LayoutingType.None) { + if (!System.Threading.Monitor.TryEnter (linesMutex)) + return false; + } + try { + bool result = base.UpdateLayout (layoutType); + return result; + } finally { + System.Threading.Monitor.Exit (linesMutex); + } + } + public override int measureRawSize(LayoutingType lt) + { + DbgLogger.StartEvent(DbgEvtType.GOMeasure, this, lt); + + if ((bool)lines?.IsEmpty) + getLines (); + + if (!textMeasureIsUpToDate) { + using (Context gr = new Context (IFace.surf)) { + setFontForContext (gr); + measureTextBounds (gr); + } + } + DbgLogger.EndEvent(DbgEvtType.GOMeasure); + return Margin * 2 + (lt == LayoutingType.Height ? cachedTextSize.Height : cachedTextSize.Width); + } + + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + setFontForContext (gr); + + if (!textMeasureIsUpToDate) { + lock (linesMutex) + measureTextBounds (gr); + } + + if (ClipToClientRect) { + gr.Save (); + CairoHelpers.CairoRectangle (gr, ClientRectangle, CornerRadius); + gr.Clip (); + } + + lock (linesMutex) + drawContent (gr); + + if (ClipToClientRect) + gr.Restore (); + } + #endregion + + #region Mouse handling + protected override void onFocused (object sender, EventArgs e) + { + base.onFocused (sender, e); + + if (CurrentLoc == null) { + selectionStart = new CharLocation (0, 0); + CurrentLoc = new CharLocation (lines.Count - 1, lines[lines.Count - 1].Length); + } + + RegisterForRedraw (); + + (IFace as CrowEditBase.CrowEditBase).CurrentEditor = this; + } + protected override void onUnfocused (object sender, EventArgs e) + { + base.onUnfocused (sender, e); + RegisterForRedraw (); + } + public override void onMouseEnter (object sender, MouseMoveEventArgs e) { + base.onMouseEnter (sender, e); + if (Focusable) + IFace.MouseCursor = MouseCursor.ibeam; + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + updateHoverLocation (ScreenPointToLocal (e.Position)); + + if (HasFocus && IFace.IsDown (MouseButton.Left)) { + CurrentLoc = hoverLoc; + RegisterForRedraw (); + } + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + if (e.Button == Glfw.MouseButton.Left) { + targetColumn = -1; + if (HasFocus) { + if (!IFace.Shift) + selectionStart = hoverLoc; + else if (!selectionStart.HasValue) + selectionStart = CurrentLoc; + CurrentLoc = hoverLoc; + IFace.forceTextCursor = true; + RegisterForRedraw (); + e.Handled = true; + } + } + base.onMouseDown (sender, e); + + //done at the end to set 'hasFocus' value after testing it + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + if (e.Button != MouseButton.Left || !HasFocus || !selectionStart.HasValue) + return; + if (selectionStart.Value == CurrentLoc.Value) + selectionStart = null; + } + public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) + { + base.onMouseDoubleClick (sender, e); + if (e.Button != MouseButton.Left || !HasFocus) + return; + + GotoWordStart (); + selectionStart = CurrentLoc; + GotoWordEnd (); + RegisterForRedraw (); + } + #endregion + + #region Keyboard handling + public override void onKeyDown (object sender, KeyEventArgs e) { + Key key = e.Key; + TextSpan selection = Selection; + switch (key) { + case Key.Backspace: + if (selection.IsEmpty) { + if (selection.Start == 0) + return; + if (CurrentLoc.Value.Column == 0) { + int lbLength = lines[CurrentLoc.Value.Line - 1].LineBreakLength; + update (new TextChange (selection.Start - lbLength, lbLength, "")); + }else + update (new TextChange (selection.Start - 1, 1, "")); + } else + update (new TextChange (selection.Start, selection.Length, "")); + break; + case Key.Delete: + if (selection.IsEmpty) { + if (selection.Start == _text.Length) + return; + if (CurrentLoc.Value.Column >= lines[CurrentLoc.Value.Line].Length) + update (new TextChange (selection.Start, lines[CurrentLoc.Value.Line].LineBreakLength, "")); + else + update (new TextChange (selection.Start, 1, "")); + } else { + if (IFace.Shift) + IFace.Clipboard = SelectedText; + update (new TextChange (selection.Start, selection.Length, "")); + } + break; + case Key.Insert: + if (IFace.Shift) + Paste (); + else if (IFace.Ctrl) + Copy (); + break; + case Key.KeypadEnter: + case Key.Enter: + if (string.IsNullOrEmpty (LineBreak)) + detectLineBreak (); + update (new TextChange (selection.Start, selection.Length, LineBreak)); + break; + case Key.Escape: + selectionStart = null; + CurrentLoc = lines.GetLocation (selection.Start); + RegisterForRedraw (); + break; + case Key.Tab: + update (new TextChange (selection.Start, selection.Length, "\t")); + break; + case Key.PageUp: + checkShift (); + LineMove (-visibleLines); + RegisterForRedraw (); + break; + case Key.PageDown: + checkShift (); + LineMove (visibleLines); + RegisterForRedraw (); + break; + case Key.Home: + targetColumn = -1; + checkShift (); + if (IFace.Ctrl) + CurrentLoc = new CharLocation (0, 0); + else + CurrentLoc = new CharLocation (CurrentLoc.Value.Line, 0); + RegisterForRedraw (); + break; + case Key.End: + checkShift (); + int l = IFace.Ctrl ? lines.Count - 1 : CurrentLoc.Value.Line; + CurrentLoc = new CharLocation (l, lines[l].Length); + RegisterForRedraw (); + break; + case Key.Left: + checkShift (); + if (IFace.Ctrl) + GotoWordStart (); + else + MoveLeft (); + RegisterForRedraw (); + break; + case Key.Right: + checkShift (); + if (IFace.Ctrl) + GotoWordEnd (); + else + MoveRight (); + RegisterForRedraw (); + break; + case Key.Up: + checkShift (); + LineMove (-1); + RegisterForRedraw (); + break; + case Key.Down: + checkShift (); + LineMove (1); + RegisterForRedraw (); + break; + default: + base.onKeyDown (sender, e); + return; + } + autoAdjustScroll = true; + IFace.forceTextCursor = true; + e.Handled = true; + } + #endregion + #endregion + + + #region textBox + bool autoAdjustScroll = false;//if scrollXY is changed directly, dont try adjust scroll to cursor + internal RectangleD? computeTextCursor (Rectangle cursor) { + Rectangle cb = ClientRectangle; + cursor -= new Point (ScrollX, ScrollY); + + if (autoAdjustScroll) { + autoAdjustScroll = false; + int goodMsrs = 0; + if (cursor.Right < 0) + ScrollX += cursor.Right; + else if (cursor.X > cb.Width) + ScrollX += cursor.X - cb.Width; + else + goodMsrs++; + + if (cursor.Y < 0) + ScrollY += cursor.Y; + else if (cursor.Bottom > cb.Height) + ScrollY += cursor.Bottom - cb.Height; + else + goodMsrs++; + + if (goodMsrs < 2) + return null; + } else if (cursor.Right < 0 || cursor.X > cb.Width || cursor.Y < 0 || cursor.Bottom > cb.Height) + return null; + + return cursor; + } + + void updateMaxScrolls (LayoutingType layout) { + Rectangle cb = ClientRectangle; + if (layout == LayoutingType.Width) { + MaxScrollX = cachedTextSize.Width - cb.Width; + NotifyValueChanged ("PageWidth", ClientRectangle.Width); + if (cachedTextSize.Width > 0) + NotifyValueChanged ("ChildWidthRatio", Math.Min (1.0, (double)cb.Width / cachedTextSize.Width)); + } else if (layout == LayoutingType.Height) { + MaxScrollY = cachedTextSize.Height - cb.Height; + NotifyValueChanged ("PageHeight", ClientRectangle.Height); + if (cachedTextSize.Height > 0) + NotifyValueChanged ("ChildHeightRatio", Math.Min (1.0, (double)cb.Height / cachedTextSize.Height)); + } + } + public virtual void Cut () { + TextSpan selection = Selection; + if (selection.IsEmpty) + return; + IFace.Clipboard = SelectedText; + update (new TextChange (selection.Start, selection.Length, "")); + } + public virtual void Copy () { + TextSpan selection = Selection; + if (selection.IsEmpty) + return; + IFace.Clipboard = SelectedText; + } + public virtual void Paste () { + TextSpan selection = Selection; + update (new TextChange (selection.Start, selection.Length, IFace.Clipboard)); + } + + #region Keyboard handling + public override void onKeyPress (object sender, KeyPressEventArgs e) { + base.onKeyPress (sender, e); + + TextSpan selection = Selection; + update (new TextChange (selection.Start, selection.Length, e.KeyChar.ToString ())); + + /*Insert (e.KeyChar.ToString()); + + SelRelease = -1; + SelBegin = new Point(CurrentColumn, SelBegin.Y); + + RegisterForGraphicUpdate();*/ + } + #endregion + + protected void update (TextChange change) { + lock (linesMutex) { + ReadOnlySpan src = _text.AsSpan (); + Span tmp = stackalloc char[src.Length + (change.ChangedText.Length - change.Length)]; + //Console.WriteLine ($"{Text.Length,-4} {change.Start,-4} {change.Length,-4} {change.ChangedText.Length,-4} tmp:{tmp.Length,-4}"); + src.Slice (0, change.Start).CopyTo (tmp); + change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start)); + src.Slice (change.End).CopyTo (tmp.Slice (change.Start + change.ChangedText.Length)); + + _text = tmp.ToString (); + lines.Update (change); + //lines.Update (_text); + selectionStart = null; + + CurrentLoc = lines.GetLocation (change.Start + change.ChangedText.Length); + textMeasureIsUpToDate = false; + IFace.forceTextCursor = true; + } + + OnTextChanged (this, new TextChangeEventArgs (change)); + + RegisterForGraphicUpdate (); + } + + #endregion + } } \ No newline at end of file diff --git a/CrowEditBase/src/IFileNode.cs b/CrowEditBase/src/IFileNode.cs new file mode 100644 index 0000000..9f3077a --- /dev/null +++ b/CrowEditBase/src/IFileNode.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2020 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System; +using System.Collections.Generic; +using System.Linq; +using Crow; +using CrowEditBase; +using static CrowEditBase.CrowEditBase; + +namespace CrowEditBase +{ + public interface IFileNode + { + string FullPath { get; } + } +} diff --git a/CrowEditBase/src/LogViewerWidget.cs b/CrowEditBase/src/LogViewerWidget.cs new file mode 100644 index 0000000..f9bd3a5 --- /dev/null +++ b/CrowEditBase/src/LogViewerWidget.cs @@ -0,0 +1,176 @@ +// Copyright (c) 2020 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + +using System; +using System.Xml.Serialization; +using System.ComponentModel; +using System.Collections; +using Crow.Cairo; + +namespace Crow +{ + public enum LogType { + Low, + Normal, + High, + Debug, + Warning, + Error, + Custom1, + Custom2, + Custom3, + } + public class LogEntry { + public LogType Type; + public string msg; + public LogEntry (LogType type, string message) { + Type = type; + msg = message; + } + public override string ToString() => msg; + } + public class LogViewerWidget : ScrollingObject + { + ObservableList lines; + bool scrollOnOutput; + int visibleLines = 1; + FontExtents fe; + + [DefaultValue(true)] + public virtual bool ScrollOnOutput { + get { return scrollOnOutput; } + set { + if (scrollOnOutput == value) + return; + scrollOnOutput = value; + NotifyValueChanged ("ScrollOnOutput", scrollOnOutput); + + } + } + public virtual ObservableList Lines { + get { return lines; } + set { + if (lines == value) + return; + if (lines != null) { + lines.ListAdd -= Lines_ListAdd; + lines.ListRemove -= Lines_ListRemove; + } + lines = value; + if (lines != null) { + lines.ListAdd += Lines_ListAdd; + lines.ListRemove += Lines_ListRemove; + } + NotifyValueChanged ("Lines", lines); + RegisterForGraphicUpdate (); + } + } + + void Lines_ListAdd (object sender, ListChangedEventArg e) + { + // try + // { + MaxScrollY = lines.Count - visibleLines; + if (scrollOnOutput) + ScrollY = MaxScrollY; + + // } + // catch (System.Exception ex) + // { + // Console.WriteLine ($"list add valueChange handler bug:{ex}"); + // } + } + + void Lines_ListRemove (object sender, ListChangedEventArg e) + { + MaxScrollY = lines.Count - visibleLines; + } + + + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + + if (layoutType == LayoutingType.Height) { + using (ImageSurface img = new ImageSurface (Format.Argb32, 10, 10)) { + using (Context gr = new Context (img)) { + //Cairo.FontFace cf = gr.GetContextFontFace (); + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + + fe = gr.FontExtents; + } + } + visibleLines = (int)Math.Floor ((double)ClientRectangle.Height / fe.Height); + MaxScrollY = lines == null ? 0 : lines.Count - visibleLines; + } + } + protected override void onDraw (Cairo.Context gr) + { + base.onDraw (gr); + + if (lines == null) + return; + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + + Rectangle r = ClientRectangle; + + + double y = ClientRectangle.Y; + double x = ClientRectangle.X - ScrollX; + + lock (lines) { + for (int i = 0; i < visibleLines; i++) { + if (i + ScrollY >= Lines.Count) + break; + //if ((lines [i + Scroll] as string).StartsWith ("error", StringComparison.OrdinalIgnoreCase)) { + // errorFill.SetAsSource (gr); + // gr.Rectangle (x, y, (double)r.Width, fe.Height); + // gr.Fill (); + // Foreground.SetAsSource (gr); + //} + LogEntry le = lines[i+ScrollY]; + switch (le.Type) { + case LogType.Low: + gr.SetSource (Colors.DimGrey); + break; + case LogType.Normal: + gr.SetSource (Colors.Grey); + break; + case LogType.High: + gr.SetSource (Colors.White); + break; + case LogType.Debug: + gr.SetSource (Colors.Yellow); + break; + case LogType.Warning: + gr.SetSource (Colors.Orange); + break; + case LogType.Error: + gr.SetSource (Colors.Red); + break; + case LogType.Custom1: + gr.SetSource (Colors.Cyan); + break; + case LogType.Custom2: + gr.SetSource (Colors.Green); + break; + case LogType.Custom3: + gr.SetSource (Colors.LightPink); + break; + } + gr.MoveTo (x, y + fe.Ascent); + gr.ShowText (le.msg); + y += fe.Height; + gr.Fill (); + } + } + } + + } +} + diff --git a/CrowEditBase/src/Plugin.cs b/CrowEditBase/src/Plugin.cs index b2fe1b0..8639a95 100644 --- a/CrowEditBase/src/Plugin.cs +++ b/CrowEditBase/src/Plugin.cs @@ -21,6 +21,11 @@ namespace CrowEditBase public Assembly Load (AssemblyName assemblyName) => loadContext.LoadFromAssemblyName (assemblyName); + + public bool TryGet (AssemblyName assemblyName, out Assembly assembly) { + assembly = loadContext.Assemblies.FirstOrDefault (a=>a.GetName().Name == assemblyName.Name); + return assembly != null; + } public virtual bool IsLoaded { get { return isLoaded; } set { diff --git a/CrowEditBase/src/PluginsLoadContext.cs b/CrowEditBase/src/PluginsLoadContext.cs index 7e850fa..6827839 100644 --- a/CrowEditBase/src/PluginsLoadContext.cs +++ b/CrowEditBase/src/PluginsLoadContext.cs @@ -25,15 +25,18 @@ namespace CrowEditBase }*/ public class PluginsLoadContext : AssemblyLoadContext { public readonly Assembly MainAssembly; - string pluginDirectory; - public PluginsLoadContext (string pluginsDirectory) - : base ($"CrowEditPluginsContext+{pluginsDirectory}", true) { - this.pluginDirectory = pluginsDirectory; - string pluginAssembly = Path.Combine (pluginsDirectory, $"{Path.GetFileName (pluginsDirectory)}.dll"); + public readonly string Name; + readonly string fullPath; + public PluginsLoadContext (string pluginDirectory) + : base (Path.GetFileName (pluginDirectory), false) { + fullPath = pluginDirectory; + Name = Path.GetFileName (pluginDirectory); + + string pluginAssembly = Path.Combine (fullPath, $"{Name}.dll"); MainAssembly = LoadFromAssemblyPath (pluginAssembly); } protected override Assembly Load(AssemblyName assemblyName) { - string assemblyPath = Path.Combine (pluginDirectory, assemblyName.Name + ".dll"); + string assemblyPath = Path.Combine (fullPath, assemblyName.Name + ".dll"); return File.Exists (assemblyPath) ? LoadFromAssemblyPath (assemblyPath) : null; } diff --git a/CrowEditBase/src/Project.cs b/CrowEditBase/src/Project.cs index 8c0017f..4c001ad 100644 --- a/CrowEditBase/src/Project.cs +++ b/CrowEditBase/src/Project.cs @@ -45,10 +45,10 @@ namespace CrowEditBase FullPath = fullPath; } public Command CMDLoad, CMDUnload, CMDReload; - public CommandGroup Commands => new CommandGroup ( + public virtual CommandGroup Commands => new CommandGroup ( CMDLoad, CMDUnload, CMDReload); - protected virtual void initCommands () { + void initCommands () { CMDLoad = new Command ("Load", Load, "#icons.reply.svg", false); CMDUnload = new Command ("Unload", Unload, "#icons.share-arrow.svg", false); CMDReload = new Command ("Reload", () => { Unload(); Load();}, "#icons.refresh.svg", false); diff --git a/CrowEditBase/src/SourceEditor.cs b/CrowEditBase/src/SourceEditor.cs index 89abcef..382a8d5 100644 --- a/CrowEditBase/src/SourceEditor.cs +++ b/CrowEditBase/src/SourceEditor.cs @@ -163,11 +163,11 @@ namespace Crow if (IFace.Shift) { for (int l = lineStart; l <= lineEnd; l++) { - if (Text[lines[l].Start] == '\t') + if (_text[lines[l].Start] == '\t') update (new TextChange (lines[l].Start, 1, "")); - else if (Char.IsWhiteSpace (Text[lines[l].Start])) { + else if (Char.IsWhiteSpace (_text[lines[l].Start])) { int i = 1; - while (i < lines[l].Length && i < Interface.TAB_SIZE && Char.IsWhiteSpace (Text[i])) + while (i < lines[l].Length && i < Interface.TAB_SIZE && Char.IsWhiteSpace (_text[i])) i++; update (new TextChange (lines[l].Start, i, "")); } diff --git a/CrowEditBase/ui/IDE.style b/CrowEditBase/ui/IDE.style index f1a7cc7..e5ec8d4 100644 --- a/CrowEditBase/ui/IDE.style +++ b/CrowEditBase/ui/IDE.style @@ -1,4 +1,11 @@ -icon { +Editor { + Background="White"; + Foreground="Black"; + MouseWheelSpeed = "20"; + BubbleMouseEvent ="None"; +} + +icon { Width="14"; Height="14"; } diff --git a/plugins/CECrowDebugLog/CECrowDebugLog.csproj b/plugins/CECrowDebugLog/CECrowDebugLog.csproj index 127c3d0..a20d97d 100644 --- a/plugins/CECrowDebugLog/CECrowDebugLog.csproj +++ b/plugins/CECrowDebugLog/CECrowDebugLog.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + netcoreapp5 false @@ -10,7 +10,6 @@ - - + diff --git a/plugins/CECrowDebugLog/src/CrowService.cs b/plugins/CECrowDebugLog/src/CrowService.cs index 2a9f670..fd446d6 100644 --- a/plugins/CECrowDebugLog/src/CrowService.cs +++ b/plugins/CECrowDebugLog/src/CrowService.cs @@ -28,7 +28,7 @@ namespace Crow initCommands (); //resolve other plugins dependencies - AssemblyLoadContext.GetLoadContext (Assembly.GetExecutingAssembly ()).Resolving += resolvePluginRefs; + //AssemblyLoadContext.GetLoadContext (Assembly.GetExecutingAssembly ()).Resolving += resolvePluginRefs; if (CrowEditBase.CrowEditBase.App.TryGetWindow ("#CECrowDebugLog.ui.winLogGraph.crow", out Window win)) win.DataSource = this; @@ -497,8 +497,8 @@ namespace Crow Events = events; Widgets = widgets; firstWidgetIndexToGet += widgets.Count; - if (widgets.Count > 0 && firstWidgetIndexToGet != widgets.Last().InstanceIndex + 1) - Debugger.Break (); + /*if (widgets.Count > 0 && firstWidgetIndexToGet != widgets.Last().InstanceIndex + 1) + Debugger.Break ();*/ } } void updateWidgetEvents (IList widgets, DbgEvent evt) { diff --git a/plugins/CENetcoreDbgPlugin/CENetcoreDbgPlugin.csproj b/plugins/CENetcoreDbgPlugin/CENetcoreDbgPlugin.csproj new file mode 100644 index 0000000..4540498 --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/CENetcoreDbgPlugin.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp5 + false + + + + + + + + + + + + diff --git a/plugins/CENetcoreDbgPlugin/src/BreakPoint.cs b/plugins/CENetcoreDbgPlugin/src/BreakPoint.cs new file mode 100644 index 0000000..3c19b3f --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/src/BreakPoint.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + +using System.Diagnostics; + +namespace NetcoreDbgPlugin +{ + [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] + public class BreakPoint : CrowEditBase.BreakPoint + { + public BreakPoint(string fileFullPath, int line, bool isEnabled = true) : base (fileFullPath, line, isEnabled) { + } + + public void Update (MITupple bkpt) { + Index = int.Parse (bkpt.GetAttributeValue("number")); + Type = bkpt.GetAttributeValue("type"); + Disp = bkpt.GetAttributeValue("disp"); + IsEnabled = bkpt.GetAttributeValue("enabled") == "y"; + if (bkpt.TryGetAttributeValue("warning", out string warning)) + Warning = warning; + else { + Warning = null; + Function = bkpt.GetAttributeValue("func"); + //FileName = bkpt.GetAttributeValue("file"); + FileFullPath = bkpt.GetAttributeValue("fullname")?.Replace("\\\\", "\\"); + Line = int.Parse (bkpt.GetAttributeValue("line")) - 1; + + /*if (project.TryGetProjectFileFromPath(FileFullName, out ProjectFileNode pf)) + File = pf as CSProjectItem;*/ + + } + } + + public override string ToString() => $"{Index}:{Type} {FileFullPath}:{Line} enabled:{IsEnabled}"; + private string GetDebuggerDisplay() => ToString(); + } +} diff --git a/plugins/CENetcoreDbgPlugin/src/CLRAddress.cs b/plugins/CENetcoreDbgPlugin/src/CLRAddress.cs new file mode 100644 index 0000000..ec4dc71 --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/src/CLRAddress.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System.Diagnostics; + +namespace NetcoreDbgPlugin +{ + [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] + public class CLRAddress + { + public string ModuleID; + public string MethodToken; + public long IlOffset; + public long NativeOffset; + public CLRAddress(MITupple clrAddress) + { + ModuleID = clrAddress.GetAttributeValue("module-id"); + MethodToken = clrAddress.GetAttributeValue("method-token"); + IlOffset = long.Parse(clrAddress.GetAttributeValue("il-offset")); + NativeOffset = long.Parse(clrAddress.GetAttributeValue("native-offset")); + } + public override string ToString() => $"Mod:{ModuleID} Meth:{MethodToken} IL:{IlOffset} Native:{NativeOffset}"; + private string GetDebuggerDisplay() => ToString(); + } +} diff --git a/plugins/CENetcoreDbgPlugin/src/MIObjects.cs b/plugins/CENetcoreDbgPlugin/src/MIObjects.cs new file mode 100644 index 0000000..99d5ab9 --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/src/MIObjects.cs @@ -0,0 +1,138 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace NetcoreDbgPlugin +{ + [DebuggerDisplay("{Name}")] + public class MIObject + { + public string Name; + public MIObject(ReadOnlySpan name) + { + Name = name.ToString(); + } + } + [DebuggerDisplay("{Name}={Value}")] + public class MIAttribute : MIObject + { + public string Value; + public MIAttribute(ReadOnlySpan name, ReadOnlySpan value) : base(name) + { + Value = value.ToString(); + } + public T GetValue() + { + Type type = typeof(T); + if (type == typeof(string)) + return (T)(object)Value; + if (type.IsEnum) + { + return (T)Enum.Parse(typeof(T), Value); + } + else + { + MethodInfo miParse = type.GetMethod("Parse", new Type[] { typeof(string) }); + if (miParse != null) + return (T)miParse.Invoke(null, new object[] { Value }); + } + return (T)Convert.ChangeType(Value, type); + } + } + [DebuggerDisplay("{Name} ({Attributes.Count})")] + public class MITupple : MIObject + { + public List Attributes = new List(); + public MITupple(ReadOnlySpan name) : base(name) { } + + public MIObject this[string attributeName] + => Attributes.FirstOrDefault(a => a.Name == attributeName); + public MIObject this[int index] + => Attributes[index]; + + public string GetAttributeValue(string attributeName) + => Attributes.OfType().FirstOrDefault(a => a.Name == attributeName)?.Value; + public bool TryGetAttributeValue(string attributeName, out MIAttribute value) + { + value = Attributes.OfType().FirstOrDefault(a => a.Name == attributeName); + return value != null; + } + public bool TryGetAttributeValue(string attributeName, out string value) + { + value = Attributes.OfType().FirstOrDefault(a => a.Name == attributeName)?.Value; + return !string.IsNullOrEmpty (value); + } + + public static MITupple Parse (ReadOnlySpan data) + { + int tokStart = 0; + int curPos = 0; + + Stack mistack = new Stack(); + mistack.Push(new MITupple("Root")); + ReadOnlySpan curName = null; + MITupple tup = null; + + while (curPos < data.Length) + { + switch (data[curPos]) + { + case '[': + mistack.Push(new MIList(curName)); + curName = null; + break; + case '{': + mistack.Push(new MITupple(curName)); + curName = null; + break; + case '}': + tup = mistack.Pop() as MITupple; + if (mistack.Peek() is MITupple mit) + mit.Attributes.Add(tup); + else + (mistack.Peek() as MIList).Items.Add(tup); + break; + case ']': + MIList list = mistack.Pop() as MIList; + (mistack.Peek() as MITupple).Attributes.Add(list); + break; + case ',': + curName = null; + break; + case '"': + tup = mistack.Peek() as MITupple; + tokStart = ++curPos; + while (curPos < data.Length && !(data[curPos] == '"' && data[curPos-1] != '\\')) + curPos++; + + tup.Attributes.Add(new MIAttribute(curName, data.Slice(tokStart, curPos - tokStart))); + break; + case '=': + curName = data.Slice(tokStart, curPos - tokStart); + break; + default: + curPos++; + continue; + } + tokStart = ++curPos; + } + return mistack.Pop() as MITupple; + } + } + [DebuggerDisplay("{Name} ({Items.Count})")] + public class MIList : MIObject + { + public List Items = new List(); + public MIList(ReadOnlySpan name) : base(name) + { + } + } + + +} \ No newline at end of file diff --git a/plugins/CENetcoreDbgPlugin/src/NetcoreDbgService.cs b/plugins/CENetcoreDbgPlugin/src/NetcoreDbgService.cs new file mode 100644 index 0000000..639c030 --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/src/NetcoreDbgService.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2013-2019 Bruyère Jean-Philippe +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using CrowEditBase; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using static CrowEditBase.CrowEditBase; +using Crow; + + +namespace NetcoreDbgPlugin +{ + public class NetcoreDbgService : Service { + public NetcoreDbgService () : base () { + } + public string NetcoredbgPath { + get => Configuration.Global.Get ("NetcoredbgPath"); + set { + if (value == NetcoredbgPath) + return; + Configuration.Global.Set ("NetcoredbgPath", value); + NotifyValueChanged (value); + } + } + NetcoredbgDebugger dbg; + public override void Start() { + if (CurrentState == Status.Running) + return; + dbg = new NetcoredbgDebugger (); + CurrentState = Status.Running; + } + public override void Stop() + { + if (CurrentState != Status.Running) + return; + + dbg.Terminate (); + dbg = null; + CurrentState = Status.Stopped; + } + public override void Pause() + { + if (CurrentState != Status.Running) + return; + + dbg.Terminate (); + CurrentState = Status.Paused; + } + + public override string ConfigurationWindowPath => "#CENetcoreDbgPlugin.ui.winConfiguration.crow"; + } +} \ No newline at end of file diff --git a/plugins/CENetcoreDbgPlugin/src/NetcoredbgDebugger.cs b/plugins/CENetcoreDbgPlugin/src/NetcoredbgDebugger.cs new file mode 100644 index 0000000..16cfdb2 --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/src/NetcoredbgDebugger.cs @@ -0,0 +1,428 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using CrowEditBase; +using Crow; +using CERoslynPlugin; + +using static CrowEditBase.CrowEditBase; + +namespace NetcoreDbgPlugin +{ + public class NetcoredbgDebugger : Debugger + { + System.Diagnostics.Process procdbg; + public class Request { + public readonly string Command; + public Request (string command) { + Command = command; + } + public override string ToString() => Command; + } + public class Request : Request { + public T RequestObject; + public Request (T obj, string command) : base (command){ + RequestObject = obj; + } + } + Queue pendingRequest = new Queue(); + MSBuildProject msbProject => Project as MSBuildProject; + + public override Project Project + { + get => base.Project; + set + { + if (base.Project == value || !(value is MSBuildProject msbProj)) + return; + base.Project = value; + } + } + void initDebugSession () { + if (CurrentState != Status.Init || msbProject == null) + return; + + bool result = procdbg.Start(); + + procdbg.BeginOutputReadLine(); + + CreateNewRequest($"-file-exec-and-symbols {msbProject.OutputAssembly}"); + CreateNewRequest($"-environment-cd {Path.GetDirectoryName(msbProject.OutputAssembly)}"); + + foreach (BreakPoint bp in BreakPoints) + InsertBreakPoint(bp); + + CurrentState = Status.Starting; + } + #region CTOR + public NetcoredbgDebugger() + { + procdbg = new System.Diagnostics.Process(); + procdbg.StartInfo.FileName = App.GetService().NetcoredbgPath; + procdbg.StartInfo.Arguments = "--interpreter=mi"; + procdbg.StartInfo.CreateNoWindow = true; + procdbg.StartInfo.RedirectStandardInput = true; + procdbg.StartInfo.RedirectStandardOutput = true; + procdbg.StartInfo.RedirectStandardError = true; + procdbg.StartInfo.StandardErrorEncoding = System.Text.Encoding.UTF8; + //procdbg.StartInfo.StandardInputEncoding = System.Text.Encoding.UTF8; + procdbg.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8; + + procdbg.EnableRaisingEvents = true; + procdbg.OutputDataReceived += Procdbg_OutputDataReceived; + procdbg.ErrorDataReceived += Procdbg_ErrorDataReceived; + procdbg.Exited += Procdbg_Exited; + + BreakPoints.ListAdd += BreakPoints_ListAdd; + BreakPoints.ListRemove += BreakPoints_ListRemove; + } + #endregion + + public void Terminate () { + if (CurrentState == Status.Running || CurrentState == Status.Stopped) + Stop (); + procdbg?.Dispose(); + } + /*protected override void ResetCurrentExecutingLocation() + { + if (executingFile == null) + return; + executingFile.ExecutingLine = -1; + executingFile = null; + }*/ + /// + /// send request on netcoredbg process stdin + /// + void sendRequest(Request request) + { + DebuggerLog.Add($"<- {request}"); + procdbg.StandardInput.WriteLine(request); + } + + /// + /// enqueue new request, send it if no other request is pending + /// + public void CreateNewRequest(string request) + { + lock (pendingRequest) + { + pendingRequest.Enqueue(new Request (request)); + if (pendingRequest.Count == 1) + sendRequest(pendingRequest.Peek()); + } + } + public void CreateNewRequest(Request request) + { + lock (pendingRequest) + { + pendingRequest.Enqueue(request); + if (pendingRequest.Count == 1) + sendRequest(pendingRequest.Peek()); + } + } + + #region Debugger abstract class implementation + public override void Start() + { + initDebugSession (); + CreateNewRequest($"-exec-run"); + } + public override void Pause() + { + CreateNewRequest($"-exec-interrupt"); + } + public override void Continue() + { + CreateNewRequest($"-exec-continue"); + } + public override void Stop() + { + CreateNewRequest($"-exec-abort"); + } + + public override void StepIn() + { + CreateNewRequest($"-exec-step"); + } + public override void StepOver() + { + CreateNewRequest($"-exec-next"); + } + public override void StepOut() + { + CreateNewRequest($"-exec-finish"); + } + + public override void InsertBreakPoint(CrowEditBase.BreakPoint bp) + { + BreakPoint bk = bp as BreakPoint; + CreateNewRequest (new Request (bk, $"-break-insert {bk.FileFullPath}:{bk.Line + 1}")); + } + public override void DeleteBreakPoint(CrowEditBase.BreakPoint bp) + { + if (bp.Index < 0) + return; + CreateNewRequest($"-break-delete {bp.Index}"); + } + protected override void onCurrentFrameChanged () { + if (CurrentFrame == null) + return; + tryGoTo((StackFrame)CurrentFrame); + updateWatches (); + } + protected override void onCurrentThreadChanged () { + if (CurrentThread == null) + return; + getStackFrames((ThreadInfo)CurrentThread); + updateWatches (); + } + #endregion + + public void GetStackFrames(MIList list) + { + + } + + private void BreakPoints_ListRemove(object sender, ListChangedEventArg e) + { + if (CurrentState == Status.Init) + return; + DeleteBreakPoint((BreakPoint)e.Element); + } + private void BreakPoints_ListAdd(object sender, ListChangedEventArg e) + { + if (CurrentState == Status.Init) + return; + InsertBreakPoint((BreakPoint)e.Element); + } + private void Procdbg_Exited(object sender, EventArgs e) + { + DebuggerLog.Add("GDB process Terminated."); + + CurrentState = Status.Init; + } + + void getStackFrames(ThreadInfo thread = null) + { + if (thread == null) + CreateNewRequest($"-stack-list-frames"); + else + CreateNewRequest($"-stack-list-frames --thread {thread.Id}"); + } + void getVariables(ThreadInfo thread = null, int stackLevel = 0) + { + CreateNewRequest($"-stack-list-variables"); + } + void updateWatches () { + foreach (Watch w in Watches) + w.UpdateValue (); + } + + void tryGoTo(StackFrame frame) + { + /*if (string.IsNullOrEmpty(frame.FileFullName)) + return; + executingLine = frame.Line - 1; + string strPath = frame.FileFullName; + + if (project.TryGetProjectFileFromPath(strPath, out ProjectFileNode pf)) + { + if (!pf.IsOpened) + pf.Open(); + pf.IsSelected = true; + + executingFile = pf as CSProjectItem; + executingFile.ExecutingLine = executingLine; + executingFile.CurrentLine = executingLine; + } + else + { + ResetCurrentExecutingLocation(); + DebuggerLog.Add($"[ERROR]:current executing file ({strPath}) not found."); + }*/ + } + bool hasPendingRequest { + get { + lock (pendingRequest) { + return pendingRequest.Count > 0; + } + } + } + + void Procdbg_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e) { + DebuggerLog.Add($"-> Error: {e.Data}"); + } + + void Procdbg_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e) { + if (string.IsNullOrEmpty(e.Data)) + return; + + DebuggerLog.Add($"-> {e.Data}"); + + char firstChar = e.Data[0]; + ReadOnlySpan data = e.Data.AsSpan(1); + + if (firstChar == '(') + return; + + int tokEnd = data.IndexOf(','); + + ReadOnlySpan data_id = tokEnd < 0 ? data : data.Slice(0, tokEnd); + if (tokEnd >= 0) + data = data.Slice(tokEnd + 1); + + MITupple obj = MITupple.Parse (data); + + if (firstChar == '^') + { + Request request = null; + lock (pendingRequest) + { + if (pendingRequest.Count > 0) + { + request = pendingRequest.Dequeue(); + if (pendingRequest.Count > 0) + sendRequest(pendingRequest.Peek()); + } + } + + if (data_id.SequenceEqual("running")) + { + CurrentState = Status.Running; + CurrentFrame = null; + CurrentThread = null; + Threads.Clear(); + Frames.Clear(); + } + else if (data_id.SequenceEqual("done")) + { + if (obj.Attributes.Count > 0) + { + if (obj[0].Name == "threads") { + MIList threads = obj[0] as MIList; + Threads.Clear(); + foreach (MITupple t in threads.Items) + Threads.Add(new ThreadInfo(t)); + } else if (obj[0].Name == "stack") { + MIList stack = obj[0] as MIList; + Frames.Clear(); + foreach (MITupple f in stack.Items) + Frames.Add(new StackFrame(f)); + } else if (request is Request w) { + /*if (w.Command.StartsWith ("-var-delete")) { + Watches.Remove (w.RequestObject); + } else*/ + if (w.Command.StartsWith("-var-list-children")) { + if (int.Parse (obj.GetAttributeValue ("numchild")) > 0) { + foreach (MITupple child in (obj["children"] as MIList).Items) + w.RequestObject.Children.Add(new Watch(this, child)); + } + } else if (w.Command.StartsWith ("-var-evaluate")) + w.RequestObject.Value = obj.GetAttributeValue("value"); + else + w.RequestObject.Update (obj); + } else if (request is Request bpReq) { + BreakPoint bp = bpReq.RequestObject; + bp.Update (obj["bkpt"] as MITupple); + + } else + DebuggerLog.Add($"=> request result not handled: {request}"); + } + + + } + else if (data_id.SequenceEqual("exit")) + { + DebuggerLog.Add($"=> exit request done: {request}"); + CreateNewRequest($"-gdb-exit"); + } + else + print_unknown_datas($"requested: {request} data:{e.Data}"); + + } + else if (firstChar == '*') + { + if (data_id.SequenceEqual("stopped")) + { + CurrentState = Status.Stopped; + string reason = obj.GetAttributeValue("reason"); + if (reason == "exited") + { + CurrentState = Status.Ready; + DebuggerLog.Add($"Exited({obj.GetAttributeValue("exit-code")})"); + //CreateNewRequest($"-gdb-exit"); + } + else if (reason == "entry-point-hit" && !BreakOnStartup) { + Continue(); + } else { + DebuggerLog.Add($"Stopped reason:{reason}"); + + StackFrame frame = new StackFrame(obj["frame"] as MITupple); + if (reason == "breakpoint-hit") { + BreakPoint bp = (BreakPoint)BreakPoints.FirstOrDefault (bk=>bk.Index == int.Parse (obj.GetAttributeValue ("bkptno"))); + bp.UpdateLocation (frame); + } + + tryGoTo(frame); + + CreateNewRequest($"-thread-info"); + getStackFrames(); + getVariables(); + updateWatches (); + } + + } else + print_unknown_datas(e.Data); + } else if (firstChar == '=') {//EVENTS + if (data_id.SequenceEqual("message")) { + OutputLog.Add(obj.GetAttributeValue("text").ToString().Replace(@"\0", "")); + } else if (data_id.SequenceEqual("breakpoint-modified")) { + OutputLog.Add($"{e.Data}"); + MITupple bkpt = obj["bkpt"] as MITupple; + BreakPoint bp = (BreakPoint)BreakPoints.FirstOrDefault (bk=>bk.Index == int.Parse (bkpt.GetAttributeValue("number"))); + bp.Update (bkpt); + } + } else + print_unknown_datas(e.Data); + } + + void print_unknown_datas(string data) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(data); + Console.ResetColor(); + } + + public void WatchRequest(Watch w) + { + string strThread = CurrentThread == null ? "" : $"--thread {CurrentThread.Id}"; + string strLevel = CurrentFrame == null ? "" : $"--frame {CurrentFrame.Level}"; + CreateNewRequest (new Request (w, $"-var-create {w.Name} {w.Expression} {strThread} {strLevel}")); + } + public void WatchChildrenRequest(Watch w) + { + string strThread = CurrentThread == null ? "" : $"--thread {CurrentThread.Id}"; + string strLevel = CurrentFrame == null ? "" : $"--frame {CurrentFrame.Level}"; + CreateNewRequest (new Request (w, $"-var-list-children 1 {w.Name} {strThread} {strLevel}")); + } + + public void OnValidateCommand(Object sender, ValidateEventArgs e) + { + CreateNewRequest(e.ValidatedText); + (sender as TextBox).Text = ""; + } + + public void OnValidateNewWatch(Object sender, ValidateEventArgs e) + { + Watch w = new Watch(this, e.ValidatedText); + Watches.Add(w); + WatchRequest(w); + (sender as TextBox).Text = ""; + } + + } +} diff --git a/plugins/CENetcoreDbgPlugin/src/StackFrame.cs b/plugins/CENetcoreDbgPlugin/src/StackFrame.cs new file mode 100644 index 0000000..afa2e42 --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/src/StackFrame.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System.Diagnostics; + +namespace NetcoreDbgPlugin +{ + [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] + public class StackFrame : CrowEditBase.StackFrame + { + public bool HasCLRAddress => ClrAddress != null; + public CLRAddress ClrAddress; + + public StackFrame(MITupple frame) + { + if (frame.TryGetAttributeValue("level", out MIAttribute level)) + this.Level = int.Parse(level.Value); + //File = frame.GetAttributeValue("file"); + FileFullPath = frame.GetAttributeValue("fullname")?.Replace("\\\\", "\\"); + int.TryParse(frame.GetAttributeValue("line"), out Line); + int.TryParse(frame.GetAttributeValue("col"), out Column); + int.TryParse(frame.GetAttributeValue("end-line"), out LineEnd); + int.TryParse(frame.GetAttributeValue("end-col"), out ColumnEnd); + Function = frame.GetAttributeValue("func"); + Address = frame.GetAttributeValue("addr"); + MITupple clrAddrs = frame["clr-addr"] as MITupple; + if (clrAddrs != null) + ClrAddress = new CLRAddress(clrAddrs); + } + public override string ToString() => $"{Level}:{FileFullPath}({Line},{Column} {Function})"; + string GetDebuggerDisplay() => ToString(); + } +} diff --git a/plugins/CENetcoreDbgPlugin/src/ThreadInfo.cs b/plugins/CENetcoreDbgPlugin/src/ThreadInfo.cs new file mode 100644 index 0000000..739c42b --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/src/ThreadInfo.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System.Diagnostics; + +namespace NetcoreDbgPlugin +{ + public class ThreadInfo : CrowEditBase.ThreadInfo + { + public ThreadInfo(MITupple frame) + { + Id = int.Parse(frame.GetAttributeValue("id")); + Name = frame.GetAttributeValue("name"); + IsStopped = frame.GetAttributeValue("state") == "stopped"; + } + } +} diff --git a/plugins/CENetcoreDbgPlugin/src/Watch.cs b/plugins/CENetcoreDbgPlugin/src/Watch.cs new file mode 100644 index 0000000..6ad2db4 --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/src/Watch.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2013-2021 Jean-Philippe Bruyère +// +// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using System; +using System.Diagnostics; +//using static Crow.Coding.NetcoredbgDebugger; +using Crow; + +namespace NetcoreDbgPlugin +{ + public class Watch : CrowEditBase.Watch { + static int curId; + + NetcoredbgDebugger dbg; + + protected override void onExpand() { + if (HasChildren && Children.Count == 0) + dbg.WatchChildrenRequest (this); + } + + + public override void Create() + { + } + public override void Delete() + { + dbg.CreateNewRequest (new NetcoredbgDebugger.Request (this, $"-var-delete {Name}")); + dbg.Watches.Remove (this); + } + public override void UpdateValue () { + string strThread = dbg.CurrentThread == null ? "" : $"--thread {dbg.CurrentThread.Id}"; + string strLevel = dbg.CurrentFrame == null ? "" : $"--frame {dbg.CurrentFrame.Level}"; + dbg.CreateNewRequest (new NetcoredbgDebugger.Request (this, $"-var-evaluate-expression {Name} {strThread} {strLevel}")); + foreach (Watch w in Children) + w.UpdateValue (); + } + public Watch(NetcoredbgDebugger debugger, string expression) + { + dbg = debugger; + Name = $"watch_{curId++}"; + Expression = expression; + } + public Watch(NetcoredbgDebugger debugger, MITupple variable) + { + dbg = debugger; + Update (variable); + } + public void Update (MITupple variable) + { + Name = variable.GetAttributeValue("name"); + Expression = variable.GetAttributeValue("exp"); + Value = variable.GetAttributeValue("value"); + IsEditable = variable.GetAttributeValue("attributes") == "editable"; + Type = variable.GetAttributeValue("type"); + NumChild = int.Parse(variable.GetAttributeValue("numchild")); + ThreadId = int.Parse(variable.GetAttributeValue("thread-id")); + NotifyValueChanged ("HasChildren", HasChildren); + } + } +} diff --git a/plugins/CENetcoreDbgPlugin/ui/winConfiguration.crow b/plugins/CENetcoreDbgPlugin/ui/winConfiguration.crow new file mode 100644 index 0000000..b929424 --- /dev/null +++ b/plugins/CENetcoreDbgPlugin/ui/winConfiguration.crow @@ -0,0 +1,22 @@ + + + + +