mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-10 04:22:20 +02:00
Minor IDE changes
This commit is contained in:
parent
be3c968e2b
commit
2ea5d31c37
15 changed files with 373 additions and 87 deletions
|
@ -1919,6 +1919,37 @@ namespace Beefy.widgets
|
||||||
InsertAtCursor(text);
|
InsertAtCursor(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CutText()
|
||||||
|
{
|
||||||
|
if (!CheckReadOnly())
|
||||||
|
{
|
||||||
|
String selText = scope String();
|
||||||
|
GetSelectionText(selText);
|
||||||
|
if (!selText.IsWhiteSpace)
|
||||||
|
{
|
||||||
|
BFApp.sApp.SetClipboardText(selText);
|
||||||
|
DeleteSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyText()
|
||||||
|
{
|
||||||
|
String selText = scope String();
|
||||||
|
GetSelectionText(selText);
|
||||||
|
if (!selText.IsWhiteSpace)
|
||||||
|
BFApp.sApp.SetClipboardText(selText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PasteText()
|
||||||
|
{
|
||||||
|
String aText = scope String();
|
||||||
|
BFApp.sApp.GetClipboardText(aText);
|
||||||
|
aText.Replace("\r", "");
|
||||||
|
if ((aText != null) && (!CheckReadOnly()))
|
||||||
|
PasteText(aText);
|
||||||
|
}
|
||||||
|
|
||||||
public override void KeyDown(KeyCode keyCode, bool isRepeat)
|
public override void KeyDown(KeyCode keyCode, bool isRepeat)
|
||||||
{
|
{
|
||||||
base.KeyDown(keyCode, isRepeat);
|
base.KeyDown(keyCode, isRepeat);
|
||||||
|
@ -1944,38 +1975,16 @@ namespace Beefy.widgets
|
||||||
{
|
{
|
||||||
case (KeyCode)'A':
|
case (KeyCode)'A':
|
||||||
SelectAll();
|
SelectAll();
|
||||||
break;
|
|
||||||
case (KeyCode)'C':
|
case (KeyCode)'C':
|
||||||
String selText = scope String();
|
CopyText();
|
||||||
GetSelectionText(selText);
|
|
||||||
if (!selText.IsWhiteSpace)
|
|
||||||
BFApp.sApp.SetClipboardText(selText);
|
|
||||||
break;
|
|
||||||
case (KeyCode)'X':
|
case (KeyCode)'X':
|
||||||
if (!CheckReadOnly())
|
CutText();
|
||||||
{
|
|
||||||
String selText = scope String();
|
|
||||||
GetSelectionText(selText);
|
|
||||||
if (!selText.IsWhiteSpace)
|
|
||||||
{
|
|
||||||
BFApp.sApp.SetClipboardText(selText);
|
|
||||||
DeleteSelection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case (KeyCode)'V':
|
case (KeyCode)'V':
|
||||||
String aText = scope String();
|
PasteText();
|
||||||
BFApp.sApp.GetClipboardText(aText);
|
|
||||||
aText.Replace("\r", "");
|
|
||||||
if ((aText != null) && (!CheckReadOnly()))
|
|
||||||
PasteText(aText);
|
|
||||||
break;
|
|
||||||
case (KeyCode)'Z':
|
case (KeyCode)'Z':
|
||||||
Undo();
|
Undo();
|
||||||
break;
|
|
||||||
case (KeyCode)'Y':
|
case (KeyCode)'Y':
|
||||||
Redo();
|
Redo();
|
||||||
break;
|
|
||||||
case .Return:
|
case .Return:
|
||||||
if (mIsMultiline)
|
if (mIsMultiline)
|
||||||
mEditWidget.Submit();
|
mEditWidget.Submit();
|
||||||
|
|
|
@ -54,9 +54,9 @@ namespace System.IO
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void GetFileName(String inPath, String outFileName)
|
public static void GetFileName(StringView inPath, String outFileName)
|
||||||
{
|
{
|
||||||
if (inPath == null)
|
if (inPath.IsEmpty)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CheckInvalidPathChars(inPath);
|
CheckInvalidPathChars(inPath);
|
||||||
|
@ -236,7 +236,7 @@ namespace System.IO
|
||||||
outFileName.Append(inPath, lastSlash + 1);
|
outFileName.Append(inPath, lastSlash + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<void> GetExtension(String inPath, String outExt)
|
public static Result<void> GetExtension(StringView inPath, String outExt)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
if ((i = inPath.LastIndexOf('.')) != -1)
|
if ((i = inPath.LastIndexOf('.')) != -1)
|
||||||
|
|
|
@ -561,6 +561,17 @@ namespace System.IO
|
||||||
return .Ok;
|
return .Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Result<char8> Read()
|
||||||
|
{
|
||||||
|
if (mStream == null)
|
||||||
|
return .Err;
|
||||||
|
if (mCharPos == mCharLen)
|
||||||
|
{
|
||||||
|
if (Try!(ReadBuffer()) == 0) return .Err;
|
||||||
|
}
|
||||||
|
return mCharBuffer[mCharPos++];
|
||||||
|
}
|
||||||
|
|
||||||
public struct LineReader : IEnumerator<Result<StringView>>
|
public struct LineReader : IEnumerator<Result<StringView>>
|
||||||
{
|
{
|
||||||
StreamReader mStreamReader;
|
StreamReader mStreamReader;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace System
|
namespace System
|
||||||
{
|
{
|
||||||
struct Int32 : int32, IInteger, ISigned, IHashable, IFormattable, IOpComparable, IIsNaN, IOpNegatable, IOpAddable
|
struct Int32 : int32, IInteger, ISigned, IHashable, IFormattable, IOpComparable, IIsNaN, IOpNegatable, IOpAddable
|
||||||
|
@ -120,17 +122,19 @@ namespace System
|
||||||
ToString(outString, minNumerals);
|
ToString(outString, minNumerals);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<int32, ParseError> Parse(StringView val)
|
public static Result<int32, ParseError> Parse(StringView val, NumberStyles style)
|
||||||
{
|
{
|
||||||
if (val.Length == 0)
|
if (val.IsEmpty)
|
||||||
return .Err(.NoValue);
|
return .Err(.NoValue);
|
||||||
|
|
||||||
bool isNeg = false;
|
bool isNeg = false;
|
||||||
int32 result = 0;
|
int32 result = 0;
|
||||||
//TODO: Use Number.ParseNumber
|
|
||||||
|
int32 radix = style.HasFlag(.AllowHexSpecifier) ? 0x10 : 10;
|
||||||
|
|
||||||
for (int32 i = 0; i < val.Length; i++)
|
for (int32 i = 0; i < val.Length; i++)
|
||||||
{
|
{
|
||||||
char8 c = val.Ptr[i];
|
char8 c = val[i];
|
||||||
|
|
||||||
if ((i == 0) && (c == '-'))
|
if ((i == 0) && (c == '-'))
|
||||||
{
|
{
|
||||||
|
@ -140,13 +144,39 @@ namespace System
|
||||||
|
|
||||||
if ((c >= '0') && (c <= '9'))
|
if ((c >= '0') && (c <= '9'))
|
||||||
{
|
{
|
||||||
result *= 10;
|
result *= radix;
|
||||||
result += (int32)(c - '0');
|
result += (int32)(c - '0');
|
||||||
}
|
}
|
||||||
else
|
else if ((c >= 'a') && (c <= 'f'))
|
||||||
return .Err(.InvalidChar(isNeg ? -result : result));
|
{
|
||||||
|
result *= radix;
|
||||||
|
result += c - 'a' + 10;
|
||||||
}
|
}
|
||||||
return .Ok(isNeg ? -result : result);
|
else if ((c >= 'A') && (c <= 'F'))
|
||||||
|
{
|
||||||
|
result *= radix;
|
||||||
|
result += c - 'A' + 10;
|
||||||
|
}
|
||||||
|
else if ((c == 'X') || (c == 'x'))
|
||||||
|
{
|
||||||
|
if (result != 0)
|
||||||
|
return .Err(.InvalidChar(result));
|
||||||
|
radix = 0x10;
|
||||||
|
}
|
||||||
|
else if (c == '\'')
|
||||||
|
{
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return .Err(.InvalidChar(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
return isNeg ? -result : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<int32, ParseError> Parse(StringView val)
|
||||||
|
{
|
||||||
|
return Parse(val, .Any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1380,7 +1380,7 @@ void HashContext::Mixin(const void* data, int size)
|
||||||
|
|
||||||
if (mDbgViz)
|
if (mDbgViz)
|
||||||
{
|
{
|
||||||
int findIdx = 0x1BCF;
|
int findIdx = 0x2cc159;
|
||||||
if ((mBufOffset + mBufSize <= findIdx) && (mBufOffset + mBufSize + addBytes > findIdx))
|
if ((mBufOffset + mBufSize <= findIdx) && (mBufOffset + mBufSize + addBytes > findIdx))
|
||||||
{
|
{
|
||||||
NOP;
|
NOP;
|
||||||
|
@ -1429,9 +1429,16 @@ Val128 HashContext::Finish128()
|
||||||
{
|
{
|
||||||
String filePath = StrFormat("c:\\temp\\hash%d.bin", gDbgVizIdx++);
|
String filePath = StrFormat("c:\\temp\\hash%d.bin", gDbgVizIdx++);
|
||||||
mDbgVizStream = new FileStream();
|
mDbgVizStream = new FileStream();
|
||||||
mDbgVizStream->Open(filePath, "wb");
|
if (mDbgVizStream->Open(filePath, "wb"))
|
||||||
|
{
|
||||||
OutputDebugStrF("Creating dbg hash: %s\n", filePath.c_str());
|
OutputDebugStrF("Creating dbg hash: %s\n", filePath.c_str());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutputDebugStrF("FAILED creating dbg hash: %s\n", filePath.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((mDbgVizStream != NULL) && (mDbgVizStream->IsOpen()))
|
||||||
mDbgVizStream->Write(mBuf, mBufSize);
|
mDbgVizStream->Write(mBuf, mBufSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -682,12 +682,16 @@ public:
|
||||||
|
|
||||||
intptr CompareTo(const StringImpl& strB, bool ignoreCase = false) const
|
intptr CompareTo(const StringImpl& strB, bool ignoreCase = false) const
|
||||||
{
|
{
|
||||||
return Compare(*this, 0, strB, 0, strB.GetLength(), ignoreCase);
|
if (ignoreCase)
|
||||||
|
return CompareOrdinalIgnoreCaseHelper(GetPtr(), GetLength(), strB.GetPtr(), strB.GetLength());
|
||||||
|
return CompareOrdinalHelper(GetPtr(), GetLength(), strB.GetPtr(), strB.GetLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
static intptr Compare(const StringImpl& strA, const StringImpl& strB, bool ignoreCase)
|
static intptr Compare(const StringImpl& strA, const StringImpl& strB, bool ignoreCase)
|
||||||
{
|
{
|
||||||
return Compare(strA, 0, strB, 0, strB.GetLength(), ignoreCase);
|
if (ignoreCase)
|
||||||
|
return CompareOrdinalIgnoreCaseHelper(strA.GetPtr(), strA.GetLength(), strB.GetPtr(), strB.GetLength());
|
||||||
|
return CompareOrdinalHelper(strA.GetPtr(), strA.GetLength(), strB.GetPtr(), strB.GetLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
static intptr Compare(const StringImpl& strA, intptr indexA, const StringImpl& strB, intptr indexB, intptr length, bool ignoreCase);
|
static intptr Compare(const StringImpl& strA, intptr indexA, const StringImpl& strB, intptr indexB, intptr length, bool ignoreCase);
|
||||||
|
|
|
@ -182,6 +182,7 @@ namespace IDE
|
||||||
public AutoCompletePanel mAutoCompletePanel;
|
public AutoCompletePanel mAutoCompletePanel;
|
||||||
|
|
||||||
public Rect mRequestedWindowRect = Rect(64, 64, 1200, 1024);
|
public Rect mRequestedWindowRect = Rect(64, 64, 1200, 1024);
|
||||||
|
public BFWindow.ShowKind mRequestedShowKind;
|
||||||
public WakaTime mWakaTime ~ delete _;
|
public WakaTime mWakaTime ~ delete _;
|
||||||
|
|
||||||
public Settings mSettings = new Settings() ~ delete _;
|
public Settings mSettings = new Settings() ~ delete _;
|
||||||
|
@ -1417,10 +1418,11 @@ namespace IDE
|
||||||
|
|
||||||
void SerializeWindow(StructuredData data, WidgetWindow window)
|
void SerializeWindow(StructuredData data, WidgetWindow window)
|
||||||
{
|
{
|
||||||
data.Add("X", window.mX);
|
data.Add("X", window.mNormX);
|
||||||
data.Add("Y", window.mY);
|
data.Add("Y", window.mNormY);
|
||||||
data.Add("Width", window.mWindowWidth);
|
data.Add("Width", window.mNormWidth);
|
||||||
data.Add("Height", window.mWindowHeight);
|
data.Add("Height", window.mNormHeight);
|
||||||
|
data.Add("ShowKind", window.mShowKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerializeTabbedView(StructuredData data, DarkTabbedView tabbedView, bool serializeDocs)
|
void SerializeTabbedView(StructuredData data, DarkTabbedView tabbedView, bool serializeDocs)
|
||||||
|
@ -2670,8 +2672,11 @@ namespace IDE
|
||||||
int32 width = data.GetInt("Width");
|
int32 width = data.GetInt("Width");
|
||||||
int32 height = data.GetInt("Height");
|
int32 height = data.GetInt("Height");
|
||||||
if ((width > 0) && (height > 0))
|
if ((width > 0) && (height > 0))
|
||||||
|
{
|
||||||
mRequestedWindowRect = Rect(x, y, width, height);
|
mRequestedWindowRect = Rect(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
mRequestedShowKind = data.GetEnum<BFWindow.ShowKind>("ShowKind");
|
||||||
|
}
|
||||||
|
|
||||||
bool LoadWorkspaceUserData(StructuredData data)
|
bool LoadWorkspaceUserData(StructuredData data)
|
||||||
{
|
{
|
||||||
|
@ -9730,6 +9735,8 @@ namespace IDE
|
||||||
{
|
{
|
||||||
scope AutoBeefPerf("IDEApp.Init");
|
scope AutoBeefPerf("IDEApp.Init");
|
||||||
|
|
||||||
|
//int zag = 123;
|
||||||
|
|
||||||
if (mVerbosity == .Default)
|
if (mVerbosity == .Default)
|
||||||
mVerbosity = .Detailed;
|
mVerbosity = .Detailed;
|
||||||
|
|
||||||
|
@ -9903,6 +9910,9 @@ namespace IDE
|
||||||
if (mRunningTestScript)
|
if (mRunningTestScript)
|
||||||
flags |= .NoActivate;
|
flags |= .NoActivate;
|
||||||
|
|
||||||
|
if (mRequestedShowKind == .Maximized)
|
||||||
|
flags |= .ShowMaximized;
|
||||||
|
|
||||||
scope AutoBeefPerf("IDEApp.Init:CreateMainWindow");
|
scope AutoBeefPerf("IDEApp.Init:CreateMainWindow");
|
||||||
mMainWindow = new WidgetWindow(null, "Beef IDE", (int32)mRequestedWindowRect.mX,
|
mMainWindow = new WidgetWindow(null, "Beef IDE", (int32)mRequestedWindowRect.mX,
|
||||||
(int32)mRequestedWindowRect.mY, (int32)mRequestedWindowRect.mWidth, (int32)mRequestedWindowRect.mHeight,
|
(int32)mRequestedWindowRect.mY, (int32)mRequestedWindowRect.mWidth, (int32)mRequestedWindowRect.mHeight,
|
||||||
|
|
|
@ -186,6 +186,11 @@ namespace IDE
|
||||||
|
|
||||||
public void Apply()
|
public void Apply()
|
||||||
{
|
{
|
||||||
|
#if CLI
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#unwarn
|
||||||
String symbolServerPath = scope String()..Join("\n", mSymbolSearchPath.GetEnumerator());
|
String symbolServerPath = scope String()..Join("\n", mSymbolSearchPath.GetEnumerator());
|
||||||
if (mUseSymbolServers == .No)
|
if (mUseSymbolServers == .No)
|
||||||
{
|
{
|
||||||
|
@ -479,6 +484,11 @@ namespace IDE
|
||||||
|
|
||||||
public void Apply()
|
public void Apply()
|
||||||
{
|
{
|
||||||
|
#if CLI
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#unwarn
|
||||||
gApp.mCommands.mKeyMap.Clear();
|
gApp.mCommands.mKeyMap.Clear();
|
||||||
|
|
||||||
for (let entry in mEntries)
|
for (let entry in mEntries)
|
||||||
|
|
|
@ -1383,6 +1383,9 @@ namespace IDE.ui
|
||||||
|
|
||||||
void ShowRightClickMenu(WatchListViewItem listViewItem, float x, float y)
|
void ShowRightClickMenu(WatchListViewItem listViewItem, float x, float y)
|
||||||
{
|
{
|
||||||
|
if (mOrigEvalString.StartsWith(":"))
|
||||||
|
return;
|
||||||
|
|
||||||
float clickX = x;
|
float clickX = x;
|
||||||
float clickY = DarkTheme.sDarkTheme.mSmallFont.GetLineSpacing() + GS!(1);
|
float clickY = DarkTheme.sDarkTheme.mSmallFont.GetLineSpacing() + GS!(1);
|
||||||
listViewItem.SelfToOtherTranslate(listViewItem.mListView.GetRoot(), clickX, clickY, var aX, var aY);
|
listViewItem.SelfToOtherTranslate(listViewItem.mListView.GetRoot(), clickX, clickY, var aX, var aY);
|
||||||
|
|
|
@ -395,6 +395,7 @@ namespace IDE.ui
|
||||||
String editVal = subItem.mLabel;
|
String editVal = subItem.mLabel;
|
||||||
mEditWidget.SetText(editVal);
|
mEditWidget.SetText(editVal);
|
||||||
mEditWidget.Content.SelectAll();
|
mEditWidget.Content.SelectAll();
|
||||||
|
mEditSubmitting = true; // Default to submitting on lost focus
|
||||||
|
|
||||||
mEditingItem = item;
|
mEditingItem = item;
|
||||||
mEditingRepType = repType;
|
mEditingRepType = repType;
|
||||||
|
@ -479,6 +480,7 @@ namespace IDE.ui
|
||||||
String editText = scope String();
|
String editText = scope String();
|
||||||
mEditWidget.GetText(editText);
|
mEditWidget.GetText(editText);
|
||||||
String expr = scope String();
|
String expr = scope String();
|
||||||
|
editText.Append(", d");
|
||||||
if (!MemoryPanel.EvalExpression(editText, expr))
|
if (!MemoryPanel.EvalExpression(editText, expr))
|
||||||
expr = editText;
|
expr = editText;
|
||||||
|
|
||||||
|
@ -593,6 +595,7 @@ namespace IDE.ui
|
||||||
|
|
||||||
void HandleRenameCancel(EditEvent theEvent)
|
void HandleRenameCancel(EditEvent theEvent)
|
||||||
{
|
{
|
||||||
|
mEditSubmitting = false;
|
||||||
HandleEditLostFocus((EditWidget)theEvent.mSender);
|
HandleEditLostFocus((EditWidget)theEvent.mSender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Beefy.widgets;
|
||||||
using Beefy.theme;
|
using Beefy.theme;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Beefy.utils;
|
using Beefy.utils;
|
||||||
|
using Beefy.gfx;
|
||||||
|
|
||||||
namespace IDE.ui
|
namespace IDE.ui
|
||||||
{
|
{
|
||||||
|
@ -65,6 +66,17 @@ namespace IDE.ui
|
||||||
mModulesDirty = true;
|
mModulesDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void GetFileNameFrom(ListViewItem item, String filePath)
|
||||||
|
{
|
||||||
|
var pathItem = item.GetSubItem(1);
|
||||||
|
StringView label = pathItem.Label;
|
||||||
|
int parenPos = label.IndexOf('(');
|
||||||
|
if (parenPos != -1)
|
||||||
|
label.RemoveToEnd(parenPos);
|
||||||
|
label.Trim();
|
||||||
|
filePath.Append(label);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ShowRightClickMenu(Widget relWidget, float x, float y)
|
protected override void ShowRightClickMenu(Widget relWidget, float x, float y)
|
||||||
{
|
{
|
||||||
base.ShowRightClickMenu(relWidget, x, y);
|
base.ShowRightClickMenu(relWidget, x, y);
|
||||||
|
@ -76,15 +88,54 @@ namespace IDE.ui
|
||||||
{
|
{
|
||||||
Menu menu = new Menu();
|
Menu menu = new Menu();
|
||||||
Menu anItem;
|
Menu anItem;
|
||||||
anItem = menu.AddItem("Load Symbols...");
|
anItem = menu.AddItem("Load Image...");
|
||||||
anItem.mOnMenuItemSelected.Add(new (item) =>
|
anItem.mOnMenuItemSelected.Add(new (item) =>
|
||||||
{
|
{
|
||||||
listView.GetRoot().WithSelectedItems(scope (item) =>
|
listView.GetRoot().WithSelectedItems(scope (item) =>
|
||||||
{
|
{
|
||||||
var pathItem = item.GetSubItem(1);
|
String filePath = scope .();
|
||||||
|
GetFileNameFrom(item, filePath);
|
||||||
|
|
||||||
String dir = scope String();
|
String dir = scope String();
|
||||||
Path.GetDirectoryPath(pathItem.Label, dir);
|
Path.GetDirectoryPath(filePath, dir);
|
||||||
|
IDEUtils.FixFilePath(dir);
|
||||||
|
|
||||||
|
String fileName = scope String();
|
||||||
|
Path.GetFileName(filePath, fileName);
|
||||||
|
|
||||||
|
String extName = scope String();
|
||||||
|
Path.GetExtension(filePath, extName);
|
||||||
|
extName.ToLower();
|
||||||
|
|
||||||
|
var fileDialog = scope System.IO.OpenFileDialog();
|
||||||
|
fileDialog.ShowReadOnly = false;
|
||||||
|
fileDialog.Title = "Select Image File";
|
||||||
|
fileDialog.Multiselect = false;
|
||||||
|
if (!dir.IsEmpty)
|
||||||
|
fileDialog.InitialDirectory = dir;
|
||||||
|
fileDialog.ValidateNames = true;
|
||||||
|
fileDialog.DefaultExt = ".exe";
|
||||||
|
fileDialog.FileName = fileName;
|
||||||
|
fileDialog.SetFilter(scope String()..AppendF("{0}|{0}|File (*{1})|*{1}|All files (*.*)|*.*", fileName, extName));
|
||||||
|
mWidgetWindow.PreModalChild();
|
||||||
|
if (fileDialog.ShowDialog(gApp.GetActiveWindow()).GetValueOrDefault() == .OK)
|
||||||
|
{
|
||||||
|
var fileNames = fileDialog.FileNames;
|
||||||
|
gApp.mDebugger.LoadImageForModule(filePath, fileNames[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
anItem = menu.AddItem("Load Debug Info...");
|
||||||
|
anItem.mOnMenuItemSelected.Add(new (item) =>
|
||||||
|
{
|
||||||
|
listView.GetRoot().WithSelectedItems(scope (item) =>
|
||||||
|
{
|
||||||
|
String filePath = scope .();
|
||||||
|
GetFileNameFrom(item, filePath);
|
||||||
|
|
||||||
|
String dir = scope String();
|
||||||
|
Path.GetDirectoryPath(filePath, dir);
|
||||||
IDEUtils.FixFilePath(dir);
|
IDEUtils.FixFilePath(dir);
|
||||||
|
|
||||||
var fileDialog = scope System.IO.OpenFileDialog();
|
var fileDialog = scope System.IO.OpenFileDialog();
|
||||||
|
@ -95,14 +146,14 @@ namespace IDE.ui
|
||||||
fileDialog.InitialDirectory = dir;
|
fileDialog.InitialDirectory = dir;
|
||||||
fileDialog.ValidateNames = true;
|
fileDialog.ValidateNames = true;
|
||||||
fileDialog.DefaultExt = ".exe";
|
fileDialog.DefaultExt = ".exe";
|
||||||
fileDialog.SetFilter("PDB Debug Unfo (*.pdb)|*.pdb|All files (*.*)|*.*");
|
fileDialog.SetFilter("PDB Debug Info (*.pdb)|*.pdb|All files (*.*)|*.*");
|
||||||
mWidgetWindow.PreModalChild();
|
mWidgetWindow.PreModalChild();
|
||||||
if (fileDialog.ShowDialog(gApp.GetActiveWindow()).GetValueOrDefault() == .OK)
|
if (fileDialog.ShowDialog(gApp.GetActiveWindow()).GetValueOrDefault() == .OK)
|
||||||
{
|
{
|
||||||
var fileNames = fileDialog.FileNames;
|
var fileNames = fileDialog.FileNames;
|
||||||
if (gApp.mDebugger.mIsRunning)
|
if (gApp.mDebugger.mIsRunning)
|
||||||
{
|
{
|
||||||
gApp.mDebugger.LoadDebugInfoForModule(scope String(pathItem.Label), fileNames[0]);
|
gApp.mDebugger.LoadDebugInfoForModule(filePath, fileNames[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -126,20 +177,22 @@ namespace IDE.ui
|
||||||
if (moduleInfoStr.IsEmpty)
|
if (moduleInfoStr.IsEmpty)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
ListViewItem lvItem;
|
||||||
|
|
||||||
if (idx < mListView.GetRoot().GetChildCount())
|
if (idx < mListView.GetRoot().GetChildCount())
|
||||||
{
|
{
|
||||||
let lvItem = mListView.GetRoot().GetChildAtIndex(idx);
|
lvItem = mListView.GetRoot().GetChildAtIndex(idx);
|
||||||
int subIdx = 0;
|
int subIdx = 0;
|
||||||
for (let moduleStr in moduleInfoStr.Split('\t'))
|
for (let moduleStr in moduleInfoStr.Split('\t'))
|
||||||
{
|
{
|
||||||
let subLVItem = (subIdx == 0) ? lvItem : lvItem.GetSubItem(subIdx);
|
let subLVItem = (DarkListViewItem)((subIdx == 0) ? lvItem : lvItem.GetSubItem(subIdx));
|
||||||
subLVItem.Label = moduleStr;
|
subLVItem.Label = moduleStr;
|
||||||
subIdx++;
|
subIdx++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
let lvItem = mListView.GetRoot().CreateChildItem();
|
lvItem = mListView.GetRoot().CreateChildItem();
|
||||||
int subIdx = 0;
|
int subIdx = 0;
|
||||||
for (let moduleStr in moduleInfoStr.Split('\t'))
|
for (let moduleStr in moduleInfoStr.Split('\t'))
|
||||||
{
|
{
|
||||||
|
@ -149,6 +202,17 @@ namespace IDE.ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DarkListViewItem subLVItem = (DarkListViewItem)lvItem.GetSubItem(1);
|
||||||
|
if (subLVItem.mLabel.StartsWith("!"))
|
||||||
|
{
|
||||||
|
subLVItem.mTextColor = 0xFFFF8080;
|
||||||
|
subLVItem.mLabel.Remove(0, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
subLVItem.mTextColor = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
++idx;
|
++idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,13 @@ namespace IDE.ui
|
||||||
{
|
{
|
||||||
public class PanelHeader : Widget
|
public class PanelHeader : Widget
|
||||||
{
|
{
|
||||||
|
public enum Kind
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
WrongHash
|
||||||
|
}
|
||||||
|
|
||||||
|
public Kind mKind;
|
||||||
public List<DarkButton> mButtons = new List<DarkButton>() ~ delete _;
|
public List<DarkButton> mButtons = new List<DarkButton>() ~ delete _;
|
||||||
public String mLabel ~ delete _;
|
public String mLabel ~ delete _;
|
||||||
public String mTooltipText ~ delete _;
|
public String mTooltipText ~ delete _;
|
||||||
|
|
|
@ -2232,6 +2232,15 @@ namespace IDE.ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SourceElementType prevElementType = SourceElementType.Normal;
|
||||||
|
char8 prevChar = 0;
|
||||||
|
int cursorTextPos = CursorTextPos;
|
||||||
|
if (cursorTextPos != 0)
|
||||||
|
{
|
||||||
|
prevElementType = (SourceElementType)mData.mText[cursorTextPos - 1].mDisplayTypeId;
|
||||||
|
prevChar = mData.mText[cursorTextPos - 1].mChar;
|
||||||
|
}
|
||||||
|
|
||||||
if (((theChar == '\n') || (theChar == '\r')) && (mIsMultiline) && (!CheckReadOnly()))
|
if (((theChar == '\n') || (theChar == '\r')) && (mIsMultiline) && (!CheckReadOnly()))
|
||||||
{
|
{
|
||||||
UndoBatchStart undoBatchStart = new UndoBatchStart("newline");
|
UndoBatchStart undoBatchStart = new UndoBatchStart("newline");
|
||||||
|
@ -2333,6 +2342,17 @@ namespace IDE.ui
|
||||||
|
|
||||||
mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
|
mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
|
||||||
|
|
||||||
|
if ((prevElementType == .Normal) &&
|
||||||
|
((prevChar == '.') || (prevChar == '(')))
|
||||||
|
{
|
||||||
|
// Leave it be
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (mAutoComplete != null)
|
||||||
|
mAutoComplete.CloseListWindow();
|
||||||
|
}
|
||||||
|
|
||||||
mAutoComplete?.UpdateAsyncInfo();
|
mAutoComplete?.UpdateAsyncInfo();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -2343,15 +2363,6 @@ namespace IDE.ui
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceElementType prevElementType = SourceElementType.Normal;
|
|
||||||
char8 prevChar = 0;
|
|
||||||
int cursorTextPos = CursorTextPos;
|
|
||||||
if (cursorTextPos != 0)
|
|
||||||
{
|
|
||||||
prevElementType = (SourceElementType)mData.mText[cursorTextPos - 1].mDisplayTypeId;
|
|
||||||
prevChar = mData.mText[cursorTextPos - 1].mChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevElementType == SourceElementType.Comment)
|
if (prevElementType == SourceElementType.Comment)
|
||||||
{
|
{
|
||||||
if ((cursorTextPos < mData.mTextLength - 1) && (mData.mText[cursorTextPos - 1].mChar == '\n'))
|
if ((cursorTextPos < mData.mTextLength - 1) && (mData.mText[cursorTextPos - 1].mChar == '\n'))
|
||||||
|
@ -2565,7 +2576,7 @@ namespace IDE.ui
|
||||||
mIsInKeyChar = false;
|
mIsInKeyChar = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((theChar == '\b') || (theChar >= (char8)32))
|
if ((theChar == '\b') || (theChar == '\r') || (theChar >= (char8)32))
|
||||||
{
|
{
|
||||||
bool isHighPri = (theChar == '(') || (theChar == '.');
|
bool isHighPri = (theChar == '(') || (theChar == '.');
|
||||||
bool needsFreshAutoComplete = ((isHighPri) /*|| (!mAsyncAutocomplete)*/ || (mAutoComplete == null) || (mAutoComplete.mAutoCompleteListWidget == null));
|
bool needsFreshAutoComplete = ((isHighPri) /*|| (!mAsyncAutocomplete)*/ || (mAutoComplete == null) || (mAutoComplete.mAutoCompleteListWidget == null));
|
||||||
|
@ -3022,12 +3033,34 @@ namespace IDE.ui
|
||||||
Menu menu = new Menu();
|
Menu menu = new Menu();
|
||||||
if (!DoSpellingPopup(menu))
|
if (!DoSpellingPopup(menu))
|
||||||
{
|
{
|
||||||
|
bool hasText = false;
|
||||||
|
bool hasSelection = HasSelection();
|
||||||
|
if (hasSelection)
|
||||||
|
hasText = true;
|
||||||
|
else if ((GetLineCharAtCoord(x, y, var line, var lineChar, var overflowX)) || (overflowX < 2))
|
||||||
|
{
|
||||||
|
int textIdx = GetTextIdx(line, lineChar);
|
||||||
|
for (int checkIdx = textIdx; checkIdx < Math.Min(textIdx + 1, mData.mTextLength); checkIdx++)
|
||||||
|
{
|
||||||
|
if (mData.mText[checkIdx].mDisplayTypeId != (uint8)BfSourceElementType.Comment)
|
||||||
|
{
|
||||||
|
char8 c = mData.mText[checkIdx].mChar;
|
||||||
|
if (!c.IsWhiteSpace)
|
||||||
|
hasText = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mSourceViewPanel.mIsSourceCode)
|
if (mSourceViewPanel.mIsSourceCode)
|
||||||
{
|
{
|
||||||
var menuItem = menu.AddItem("Go to Definition");
|
Menu menuItem;
|
||||||
|
|
||||||
|
menuItem = menu.AddItem("Go to Definition");
|
||||||
|
menuItem.SetDisabled(!hasText);
|
||||||
menuItem.mOnMenuItemSelected.Add(new (evt) => IDEApp.sApp.GoToDefinition());
|
menuItem.mOnMenuItemSelected.Add(new (evt) => IDEApp.sApp.GoToDefinition());
|
||||||
|
|
||||||
menuItem = menu.AddItem("Add Watch");
|
menuItem = menu.AddItem("Add Watch");
|
||||||
|
menuItem.SetDisabled(!hasText);
|
||||||
menuItem.mOnMenuItemSelected.Add(new (evt) =>
|
menuItem.mOnMenuItemSelected.Add(new (evt) =>
|
||||||
{
|
{
|
||||||
int line, lineChar;
|
int line, lineChar;
|
||||||
|
@ -3046,7 +3079,6 @@ namespace IDE.ui
|
||||||
(textIdx >= mSelection.Value.MinPos) &&
|
(textIdx >= mSelection.Value.MinPos) &&
|
||||||
(textIdx < mSelection.Value.MaxPos))
|
(textIdx < mSelection.Value.MaxPos))
|
||||||
{
|
{
|
||||||
//CDH TODO this doesn't actually get hit right now because right-click context menus lose the selection
|
|
||||||
GetSelectionText(debugExpr);
|
GetSelectionText(debugExpr);
|
||||||
}
|
}
|
||||||
else if (bfSystem != null)
|
else if (bfSystem != null)
|
||||||
|
@ -3077,15 +3109,87 @@ namespace IDE.ui
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var debugger = IDEApp.sApp.mDebugger;
|
// Fixits
|
||||||
if (debugger.IsPaused())
|
|
||||||
{
|
{
|
||||||
|
ResolveParams resolveParams = scope .();
|
||||||
|
mSourceViewPanel.DoClassify(ResolveType.GetFixits, resolveParams, true);
|
||||||
|
menuItem = menu.AddItem("Fixit");
|
||||||
|
|
||||||
|
if (resolveParams.mNavigationData != null)
|
||||||
|
{
|
||||||
|
int32 fixitIdx = 0;
|
||||||
|
for (let str in resolveParams.mNavigationData.Split('\n'))
|
||||||
|
{
|
||||||
|
var strItr = str.Split('\t');
|
||||||
|
let cmd = strItr.GetNext().Value;
|
||||||
|
if (cmd != "fixit")
|
||||||
|
continue;
|
||||||
|
let arg = strItr.GetNext().Value;
|
||||||
|
var fixitItem = menuItem.AddItem(arg);
|
||||||
|
|
||||||
|
var infoCopy = new String(resolveParams.mNavigationData);
|
||||||
|
fixitItem.mOnMenuItemSelected.Add(new (menu) =>
|
||||||
|
{
|
||||||
|
mAutoComplete?.Close();
|
||||||
|
var autoComplete = new AutoComplete(mEditWidget);
|
||||||
|
autoComplete.SetInfo(infoCopy);
|
||||||
|
autoComplete.mAutoCompleteListWidget.mSelectIdx = fixitIdx;
|
||||||
|
autoComplete.InsertSelection(0);
|
||||||
|
autoComplete.Close();
|
||||||
|
}
|
||||||
|
~
|
||||||
|
{
|
||||||
|
delete infoCopy;
|
||||||
|
});
|
||||||
|
fixitIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!menuItem.IsParent)
|
||||||
|
{
|
||||||
|
menuItem.IsParent = true;
|
||||||
|
menuItem.SetDisabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.AddItem();
|
||||||
|
menuItem = menu.AddItem("Cut|Ctrl+X");
|
||||||
|
menuItem.mOnMenuItemSelected.Add(new (menu) =>
|
||||||
|
{
|
||||||
|
CutText();
|
||||||
|
});
|
||||||
|
menuItem.SetDisabled(!hasSelection);
|
||||||
|
|
||||||
|
menuItem = menu.AddItem("Copy|Ctrl+C");
|
||||||
|
menuItem.mOnMenuItemSelected.Add(new (menu) =>
|
||||||
|
{
|
||||||
|
CopyText();
|
||||||
|
});
|
||||||
|
menuItem.SetDisabled(!hasSelection);
|
||||||
|
|
||||||
|
menuItem = menu.AddItem("Paste|Ctrl+V");
|
||||||
|
menuItem.mOnMenuItemSelected.Add(new (menu) =>
|
||||||
|
{
|
||||||
|
PasteText();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Debugger options
|
||||||
|
menu.AddItem();
|
||||||
|
var debugger = IDEApp.sApp.mDebugger;
|
||||||
|
bool isPaused = debugger.IsPaused();
|
||||||
menuItem = menu.AddItem("Show Disassembly");
|
menuItem = menu.AddItem("Show Disassembly");
|
||||||
|
menuItem.SetDisabled(!isPaused);
|
||||||
menuItem.mOnMenuItemSelected.Add(new (evt) => IDEApp.sApp.ShowDisassemblyAtCursor());
|
menuItem.mOnMenuItemSelected.Add(new (evt) => IDEApp.sApp.ShowDisassemblyAtCursor());
|
||||||
|
|
||||||
var stepIntoSpecificMenu = menu.AddItem("Step into Specific");
|
var stepIntoSpecificMenu = menu.AddItem("Step into Specific");
|
||||||
|
stepIntoSpecificMenu.SetDisabled(isPaused);
|
||||||
|
stepIntoSpecificMenu.IsParent = true;
|
||||||
var stepFilterMenu = menu.AddItem("Step Filter");
|
var stepFilterMenu = menu.AddItem("Step Filter");
|
||||||
|
stepFilterMenu.SetDisabled(isPaused);
|
||||||
|
stepFilterMenu.IsParent = true;
|
||||||
|
|
||||||
|
if (isPaused)
|
||||||
|
{
|
||||||
int addr;
|
int addr;
|
||||||
String file = scope String();
|
String file = scope String();
|
||||||
String stackFrameInfo = scope String();
|
String stackFrameInfo = scope String();
|
||||||
|
@ -3187,10 +3291,10 @@ namespace IDE.ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stepIntoSpecificMenu.mDisabled = stepIntoSpecificMenu.mItems.Count == 0;
|
|
||||||
stepFilterMenu.mDisabled = stepFilterMenu.mItems.Count == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stepIntoSpecificMenu.mDisabled |= stepIntoSpecificMenu.mItems.Count == 0;
|
||||||
|
stepFilterMenu.mDisabled |= stepFilterMenu.mItems.Count == 0;
|
||||||
}
|
}
|
||||||
else // (!mSourceViewPanel.mIsSourceCode)
|
else // (!mSourceViewPanel.mIsSourceCode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -933,7 +933,7 @@ namespace IDE.ui
|
||||||
|
|
||||||
public bool Classify(ResolveType resolveType, ResolveParams resolveParams = null)
|
public bool Classify(ResolveType resolveType, ResolveParams resolveParams = null)
|
||||||
{
|
{
|
||||||
if (resolveType == .Autocomplete)
|
if (resolveType == .GetFixits)
|
||||||
{
|
{
|
||||||
NOP!();
|
NOP!();
|
||||||
}
|
}
|
||||||
|
@ -1605,7 +1605,11 @@ namespace IDE.ui
|
||||||
}
|
}
|
||||||
else if ((resolveType == .Autocomplete) || (resolveType == .GetFixits))
|
else if ((resolveType == .Autocomplete) || (resolveType == .GetFixits))
|
||||||
{
|
{
|
||||||
if ((resolveParams == null) || (!resolveParams.mCancelled))
|
if ((resolveParams != null) && (resolveType == .GetFixits))
|
||||||
|
{
|
||||||
|
resolveParams.mNavigationData = new String(autocompleteInfo);
|
||||||
|
}
|
||||||
|
else if ((resolveParams == null) || (!resolveParams.mCancelled))
|
||||||
{
|
{
|
||||||
bool changedAfterInfo = (resolveParams != null) && (resolveParams.mTextVersion != Content.mData.mCurTextVersionId);
|
bool changedAfterInfo = (resolveParams != null) && (resolveParams.mTextVersion != Content.mData.mCurTextVersionId);
|
||||||
|
|
||||||
|
@ -3113,6 +3117,7 @@ namespace IDE.ui
|
||||||
CloseHeader();
|
CloseHeader();
|
||||||
|
|
||||||
mPanelHeader = new PanelHeader();
|
mPanelHeader = new PanelHeader();
|
||||||
|
mPanelHeader.mKind = .WrongHash;
|
||||||
String fileName = scope String();
|
String fileName = scope String();
|
||||||
Path.GetFileName(mFilePath, fileName);
|
Path.GetFileName(mFilePath, fileName);
|
||||||
String headerStr = scope String();
|
String headerStr = scope String();
|
||||||
|
@ -5345,6 +5350,18 @@ namespace IDE.ui
|
||||||
|
|
||||||
EnsureReady();
|
EnsureReady();
|
||||||
|
|
||||||
|
if (mPanelHeader != null)
|
||||||
|
{
|
||||||
|
if (mPanelHeader.mKind == .WrongHash)
|
||||||
|
{
|
||||||
|
if (!gApp.mDebugger.mIsRunning)
|
||||||
|
{
|
||||||
|
// No longer makes sense if we're not even running
|
||||||
|
CloseHeader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mProjectSource == null)
|
if (mProjectSource == null)
|
||||||
{
|
{
|
||||||
if (mEditData != null)
|
if (mEditData != null)
|
||||||
|
|
|
@ -2670,6 +2670,8 @@ namespace IDE.ui
|
||||||
|
|
||||||
if (listViewItem != null)
|
if (listViewItem != null)
|
||||||
{
|
{
|
||||||
|
var clickedHoverItem = listViewItem.GetSubItem(0) as HoverWatch.HoverListViewItem;
|
||||||
|
|
||||||
var watchEntry = listViewItem.mWatchEntry;
|
var watchEntry = listViewItem.mWatchEntry;
|
||||||
if (listViewItem.mParentItem != listView.GetRoot())
|
if (listViewItem.mParentItem != listView.GetRoot())
|
||||||
{
|
{
|
||||||
|
@ -2750,13 +2752,20 @@ namespace IDE.ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WithSelected(Action<ListViewItem> func)
|
||||||
|
{
|
||||||
|
var root = listView.GetRoot();
|
||||||
|
root.WithSelectedItems(func);
|
||||||
|
|
||||||
|
if (clickedHoverItem != null)
|
||||||
|
func(clickedHoverItem);
|
||||||
|
}
|
||||||
|
|
||||||
anItem = menu.AddItem("Copy Value");
|
anItem = menu.AddItem("Copy Value");
|
||||||
anItem.mOnMenuItemSelected.Add(new (evt) =>
|
anItem.mOnMenuItemSelected.Add(new (evt) =>
|
||||||
{
|
{
|
||||||
String selectedText = scope String();
|
String selectedText = scope String();
|
||||||
|
WithSelected(scope (listViewItem) =>
|
||||||
var root = listView.GetRoot();
|
|
||||||
root.WithSelectedItems(scope (listViewItem) =>
|
|
||||||
{
|
{
|
||||||
if (!selectedText.IsEmpty)
|
if (!selectedText.IsEmpty)
|
||||||
{
|
{
|
||||||
|
@ -2772,9 +2781,7 @@ namespace IDE.ui
|
||||||
anItem.mOnMenuItemSelected.Add(new (evt) =>
|
anItem.mOnMenuItemSelected.Add(new (evt) =>
|
||||||
{
|
{
|
||||||
String selectedText = scope String();
|
String selectedText = scope String();
|
||||||
|
WithSelected(scope (listViewItem) =>
|
||||||
var root = listView.GetRoot();
|
|
||||||
root.WithSelectedItems(scope (listViewItem) =>
|
|
||||||
{
|
{
|
||||||
String evalStr = scope String();
|
String evalStr = scope String();
|
||||||
CompactChildExpression((WatchListViewItem)listViewItem, evalStr);
|
CompactChildExpression((WatchListViewItem)listViewItem, evalStr);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue