mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-14 14:24:10 +02:00
IDE ui issues
Fixed an autocomplete issue with path edits Made renaming symbols aware of file/project locking Fixed saving of Settings after running test Fixed async autocomplete update when not terminated with ')'
This commit is contained in:
parent
faa1dbd45e
commit
ff610742c5
7 changed files with 141 additions and 96 deletions
|
@ -243,6 +243,7 @@ namespace IDE
|
|||
public bool mStepOverExternalFiles;
|
||||
|
||||
public bool mRunningTestScript;
|
||||
public bool mStartedWithTestScript;
|
||||
public bool mExitWhenTestScriptDone = true;
|
||||
public ScriptManager mScriptManager = new ScriptManager() ~ delete _;
|
||||
public TestManager mTestManager;
|
||||
|
@ -564,7 +565,7 @@ namespace IDE
|
|||
public ~this()
|
||||
{
|
||||
#if !CLI
|
||||
if (!mRunningTestScript)
|
||||
if (!mStartedWithTestScript)
|
||||
{
|
||||
mSettings.Save();
|
||||
SaveDefaultLayoutData();
|
||||
|
@ -1424,6 +1425,8 @@ namespace IDE
|
|||
{
|
||||
if (tabWidget.mContent is SourceViewPanel)
|
||||
continue;
|
||||
if (tabWidget.mContent is DisassemblyPanel)
|
||||
continue;
|
||||
}
|
||||
|
||||
using (data.CreateObject())
|
||||
|
@ -2083,7 +2086,9 @@ namespace IDE
|
|||
if ((loadUserData) && (!mRunningTestScript))
|
||||
{
|
||||
if (!LoadWorkspaceUserData())
|
||||
{
|
||||
CreateDefaultLayout();
|
||||
}
|
||||
}
|
||||
|
||||
WorkspaceLoaded();
|
||||
|
@ -2094,6 +2099,8 @@ namespace IDE
|
|||
|
||||
ShowPanel(mOutputPanel, false);
|
||||
mMainFrame.RehupSize();
|
||||
|
||||
ShowStartupFile();
|
||||
}
|
||||
|
||||
void CloseWorkspaceAndSetupNew()
|
||||
|
@ -6303,7 +6310,7 @@ namespace IDE
|
|||
}
|
||||
else
|
||||
mWorkspace.mDir = fullDir;
|
||||
case "-open":
|
||||
case "-path":
|
||||
String.NewOrSet!(mDeferredOpenFileName, value);
|
||||
if (mDeferredOpenFileName.EndsWith(".bfdbg", .OrdinalIgnoreCase))
|
||||
mDeferredOpen = .DebugSession;
|
||||
|
@ -9421,6 +9428,27 @@ namespace IDE
|
|||
});
|
||||
}
|
||||
|
||||
void ShowStartupFile()
|
||||
{
|
||||
if (mWorkspace.mStartupProject != null)
|
||||
{
|
||||
bool didShow = false;
|
||||
|
||||
mWorkspace.mStartupProject.WithProjectItems(scope [&] (item) =>
|
||||
{
|
||||
if (didShow)
|
||||
return;
|
||||
|
||||
if ((item.mName.Equals("main.bf", .OrdinalIgnoreCase)) ||
|
||||
(item.mName.Equals("program.bf", .OrdinalIgnoreCase)))
|
||||
{
|
||||
ShowProjectItem(item, false);
|
||||
didShow = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateDefaultLayout()
|
||||
{
|
||||
//TODO:
|
||||
|
@ -9466,7 +9494,7 @@ namespace IDE
|
|||
SetupTab(watchTabbedView, "Memory", 150, mMemoryPanel, false);
|
||||
|
||||
SetupTab(outputTabbedView, "Call Stack", 150, mCallStackPanel, false);
|
||||
SetupTab(outputTabbedView, "Threads", 150, mThreadPanel, false);
|
||||
SetupTab(outputTabbedView, "Threads", 150, mThreadPanel, false);
|
||||
}
|
||||
|
||||
protected void CreateBfSystems()
|
||||
|
@ -9526,6 +9554,8 @@ namespace IDE
|
|||
{
|
||||
scope AutoBeefPerf("IDEApp.Init");
|
||||
|
||||
mStartedWithTestScript = mRunningTestScript;
|
||||
|
||||
mCommands.Init();
|
||||
EnableGCCollect = mEnableGCCollect;
|
||||
|
||||
|
@ -9765,6 +9795,7 @@ namespace IDE
|
|||
|
||||
ShowPanel(mOutputPanel, false);
|
||||
UpdateRecentFileMenuItems();
|
||||
ShowStartupFile();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -1258,11 +1258,6 @@ namespace IDE.ui
|
|||
else if (char8Data.mChar == ')')
|
||||
{
|
||||
openDepth--;
|
||||
if (openDepth == 0)
|
||||
{
|
||||
mInvokeSrcPositions.Add(checkIdx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ((char8Data.mChar == ',') && (openDepth == 1))
|
||||
{
|
||||
|
@ -1271,6 +1266,12 @@ namespace IDE.ui
|
|||
}
|
||||
else if (!((char8)char8Data.mChar).IsWhiteSpace)
|
||||
HadContent();
|
||||
|
||||
if (openDepth == 0)
|
||||
{
|
||||
mInvokeSrcPositions.Add(checkIdx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (char8Data.mDisplayPassId != (.)SourceElementType.Comment)
|
||||
{
|
||||
|
@ -1487,11 +1488,6 @@ namespace IDE.ui
|
|||
//if (selectString == null)
|
||||
if (changedAfterInfo)
|
||||
{
|
||||
if (curString == "Hey")
|
||||
{
|
||||
NOP!();
|
||||
}
|
||||
|
||||
mAutoCompleteListWidget.mSelectIdx = -1;
|
||||
|
||||
if ((curString.Length == 0) && (!mIsMember) && (mInvokeSrcPositions == null))
|
||||
|
@ -1923,6 +1919,7 @@ namespace IDE.ui
|
|||
infoSections[i].ToString(str);
|
||||
mInvokeSrcPositions.Add(int32.Parse(str));
|
||||
}
|
||||
Debug.WriteLine("Invoke size: {}", mInvokeSrcPositions.Count);
|
||||
}
|
||||
case "invokeLeftParen":
|
||||
{
|
||||
|
|
|
@ -11,7 +11,7 @@ using Beefy.theme;
|
|||
|
||||
namespace IDE.ui
|
||||
{
|
||||
public class NewProjectDialog : DarkDialog
|
||||
public class NewProjectDialog : IDEDialog
|
||||
{
|
||||
public PathEditWidget mDirectoryEdit;
|
||||
public EditWidget mNameEdit;
|
||||
|
@ -25,8 +25,9 @@ namespace IDE.ui
|
|||
public bool mDirChanged;
|
||||
public String mDirBase ~ delete _;
|
||||
|
||||
public this() : base("Create New Project", "")
|
||||
public this()
|
||||
{
|
||||
mTitle = new String("Create New Project");
|
||||
}
|
||||
|
||||
public override void CalcSize()
|
||||
|
@ -68,7 +69,7 @@ namespace IDE.ui
|
|||
if (!isNameValid)
|
||||
{
|
||||
mNameEdit.SetFocus();
|
||||
app.Fail("Invalid project name. The project name can only consist of alphanumeric char8acters, spaces, dashes, and underscores.");
|
||||
app.Fail("Invalid project name. The project name can only consist of alphanumeric characters, spaces, dashes, and underscores.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@ namespace IDE.ui
|
|||
#endif
|
||||
}
|
||||
});
|
||||
mEditWidgetContent.mTextInsets.mRight += GS!(20);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,9 +210,10 @@ namespace IDE.ui
|
|||
public override void Resize(float x, float y, float width, float height)
|
||||
{
|
||||
base.Resize(x, y, width, height);
|
||||
let btnSize = (int)(height - GS!(4));
|
||||
if (mBrowseButton != null)
|
||||
{
|
||||
mBrowseButton.Resize(mWidth - DarkTheme.sUnitSize - GS!(2), GS!(2), DarkTheme.sUnitSize, DarkTheme.sUnitSize);
|
||||
mBrowseButton.Resize(mWidth - btnSize - GS!(2), GS!(2), btnSize, btnSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace IDE.ui
|
|||
{
|
||||
public FileEditData mFileEditData;
|
||||
public List<ReplaceSpan> mSpans = new List<ReplaceSpan>() ~ delete _;
|
||||
public bool mIsLocked;
|
||||
}
|
||||
|
||||
enum BackgroundKind
|
||||
|
@ -65,6 +66,7 @@ namespace IDE.ui
|
|||
String mReplaceStr = new String() ~ delete _;
|
||||
String mOrigReplaceStr = new String() ~ delete _;
|
||||
String mModifiedParsers ~ delete _;
|
||||
bool mRenameHadChange;
|
||||
bool mIgnoreTextChanges;
|
||||
bool mTextChanged;
|
||||
int32 mStartIdx;
|
||||
|
@ -289,7 +291,6 @@ namespace IDE.ui
|
|||
bool wantsLine = false;
|
||||
|
||||
var editWidgetContent = editData.mEditWidget.mEditWidgetContent;
|
||||
|
||||
for (int32 idx < editWidgetContent.mData.mTextLength)
|
||||
{
|
||||
char8 c = editWidgetContent.mData.mText[idx].mChar;
|
||||
|
@ -437,6 +438,23 @@ namespace IDE.ui
|
|||
replaceSymbolData.mFileEditData = editData;
|
||||
//replaceSymbolData.mProjectSource = projectSource;
|
||||
|
||||
if (mKind == .Rename)
|
||||
{
|
||||
if (let sourceEditWidgetContent = editData.mEditWidget.mEditWidgetContent as SourceEditWidgetContent)
|
||||
{
|
||||
if (sourceEditWidgetContent.mSourceViewPanel != null)
|
||||
{
|
||||
replaceSymbolData.mIsLocked = sourceEditWidgetContent.mSourceViewPanel.IsReadOnly;
|
||||
}
|
||||
|
||||
for (var projectSource in editData.mProjectSources)
|
||||
{
|
||||
if (projectSource.mProject.mLocked)
|
||||
replaceSymbolData.mIsLocked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var spanData = parserData.GetNext().Get().Split(' ');
|
||||
int count = 0;
|
||||
while (true)
|
||||
|
@ -507,22 +525,31 @@ namespace IDE.ui
|
|||
return;
|
||||
|
||||
mIgnoreTextChanges = true;
|
||||
//var bfSystem = IDEApp.sApp.mBfResolveSystem;
|
||||
//var bfCompiler = IDEApp.sApp.mBfResolveCompiler;
|
||||
|
||||
|
||||
String prevReplaceStr = scope String();
|
||||
prevReplaceStr.Set(mReplaceStr);
|
||||
|
||||
GlobalUndoData globalUndoData = null;
|
||||
if (mKind == Kind.Rename)
|
||||
globalUndoData = IDEApp.sApp.mGlobalUndoManager.CreateUndoData();
|
||||
|
||||
int32 strLenDiff = (int32)(mNewReplaceStr.Length - mReplaceStr.Length);
|
||||
mReplaceStr.Set(mNewReplaceStr);
|
||||
|
||||
var activeSourceEditWidgetContent = (SourceEditWidgetContent)mSourceViewPanel.mEditWidget.Content;
|
||||
|
||||
String newStr = mReplaceStr;
|
||||
bool didUndo = false;
|
||||
|
||||
if (mKind == .Rename)
|
||||
{
|
||||
if (!mRenameHadChange)
|
||||
{
|
||||
if (newStr != mOrigReplaceStr)
|
||||
mRenameHadChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalUndoData globalUndoData = null;
|
||||
if (mKind == Kind.Rename)
|
||||
globalUndoData = IDEApp.sApp.mGlobalUndoManager.CreateUndoData();
|
||||
|
||||
List<int32> cursorPositions = scope List<int32>();
|
||||
for (var replaceSymbolData in mUpdatingProjectSources)
|
||||
{
|
||||
|
@ -537,19 +564,24 @@ namespace IDE.ui
|
|||
cursorPositions.Add((int32)sourceEditWidgetContent.CursorTextPos);
|
||||
}
|
||||
|
||||
var sourceEditWidgetContent = (SourceEditWidgetContent)mSourceViewPanel.mEditWidget.Content;
|
||||
var prevSelection = sourceEditWidgetContent.mSelection;
|
||||
var prevSelection = activeSourceEditWidgetContent.mSelection;
|
||||
|
||||
for (int sourceIdx = 0; sourceIdx < mUpdatingProjectSources.Count; sourceIdx++)
|
||||
{
|
||||
var replaceSymbolData = mUpdatingProjectSources[sourceIdx];
|
||||
if (replaceSymbolData.mIsLocked)
|
||||
continue;
|
||||
|
||||
var editData = replaceSymbolData.mFileEditData;
|
||||
if (editData.mEditWidget == null)
|
||||
continue;
|
||||
|
||||
for (var projectSource in editData.mProjectSources)
|
||||
if ((mKind == Kind.Rename) && (mRenameHadChange))
|
||||
{
|
||||
projectSource.HasChangedSinceLastCompile = true;
|
||||
for (var projectSource in editData.mProjectSources)
|
||||
{
|
||||
projectSource.HasChangedSinceLastCompile = true;
|
||||
}
|
||||
}
|
||||
|
||||
int32 cursorPos = cursorPositions[sourceIdx];
|
||||
|
@ -591,7 +623,8 @@ namespace IDE.ui
|
|||
int32 spanStart = replaceSymbolData.mSpans[i].mSpanStart + idxOffset;
|
||||
int32 spanLen = replaceSymbolData.mSpans[i].mSpanLength;
|
||||
|
||||
if (mKind == Kind.Rename)
|
||||
if ((mKind == Kind.Rename) &&
|
||||
((mRenameHadChange) || (editWidgetContent == activeSourceEditWidgetContent)))
|
||||
{
|
||||
editWidgetContent.CursorTextPos = spanStart;
|
||||
var deleteCharAction = new EditWidgetContent.DeleteCharAction(editWidgetContent, 0, spanLen);
|
||||
|
@ -631,10 +664,7 @@ namespace IDE.ui
|
|||
|
||||
if ((mUpdateTextCount == 0) && (mKind == Kind.Rename))
|
||||
{
|
||||
sourceEditWidgetContent.mSelection = prevSelection;
|
||||
//var sourceEditWidgetContent = (SourceEditWidgetContent)mSourceViewPanel.mEditWidget.Content;
|
||||
//sourceEditWidgetContent.CursorTextPos = mEndIdx;
|
||||
//sourceEditWidgetContent.mSelection = EditSelection(mStartIdx, mEndIdx);
|
||||
activeSourceEditWidgetContent.mSelection = prevSelection;
|
||||
}
|
||||
|
||||
mUpdateTextCount++;
|
||||
|
@ -680,7 +710,6 @@ namespace IDE.ui
|
|||
|
||||
for (var replaceSymbolData in mUpdatingProjectSources)
|
||||
{
|
||||
//var editData = IDEApp.sApp.GetEditData(replaceSymbolData.mProjectSource, true);
|
||||
var editData = replaceSymbolData.mFileEditData;
|
||||
if (editData.mEditWidget == null)
|
||||
continue;
|
||||
|
@ -690,7 +719,6 @@ namespace IDE.ui
|
|||
editWidgetContent.mData.mText[i].mDisplayFlags &= 0xFF ^ (uint8)(SourceElementFlags.SymbolReference);
|
||||
}
|
||||
|
||||
//var projectSource = replaceSymbolData.mProjectSource;
|
||||
using (gApp.mMonitor.Enter())
|
||||
editData.SetSavedData(null, IdSpan());
|
||||
var app = IDEApp.sApp;
|
||||
|
@ -718,7 +746,9 @@ namespace IDE.ui
|
|||
{
|
||||
for (var replaceSymbolData in mUpdatingProjectSources)
|
||||
{
|
||||
//var editData = IDEApp.sApp.GetEditData(replaceSymbolData.mProjectSource, true);
|
||||
if (replaceSymbolData.mIsLocked)
|
||||
continue;
|
||||
|
||||
var editData = replaceSymbolData.mFileEditData;
|
||||
var editWidgetContent = editData.mEditWidget.Content;
|
||||
|
||||
|
@ -833,9 +863,32 @@ namespace IDE.ui
|
|||
if (mKind == Kind.ShowFileReferences)
|
||||
return;
|
||||
|
||||
int symCount = 0;
|
||||
int readOnlyRefCount = 0;
|
||||
int lockedFileCount = 0;
|
||||
if (mUpdatingProjectSources != null)
|
||||
{
|
||||
for (var replaceSymbolData in mUpdatingProjectSources)
|
||||
{
|
||||
symCount += replaceSymbolData.mSpans.Count;
|
||||
if (replaceSymbolData.mIsLocked)
|
||||
{
|
||||
lockedFileCount++;
|
||||
readOnlyRefCount += replaceSymbolData.mSpans.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float boxHeight = mHeight - GS!(8);
|
||||
|
||||
if (lockedFileCount > 0)
|
||||
{
|
||||
boxHeight += GS!(14);
|
||||
}
|
||||
|
||||
base.Draw(g);
|
||||
using (g.PushColor(0xFFFFFFFF))
|
||||
g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Menu), 0, 0, mWidth - GS!(8), mHeight - GS!(8));
|
||||
using (g.PushColor((lockedFileCount == 0) ? 0xFFFFFFFF : 0xFFF0B0B0))
|
||||
g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Menu), 0, 0, mWidth - GS!(8), boxHeight);
|
||||
|
||||
using (g.PushColor(0xFFE0E0E0))
|
||||
{
|
||||
|
@ -861,22 +914,24 @@ namespace IDE.ui
|
|||
}
|
||||
|
||||
if (drawStr != null)
|
||||
g.DrawString(drawStr, border, GS!(5), FontAlign.Centered, mWidth - border * 2 - GS!(8), FontOverflowMode.Ellipsis);
|
||||
g.DrawString(drawStr, border, GS!(5), FontAlign.Centered, mWidth - border * 2 - GS!(8), FontOverflowMode.Ellipsis);
|
||||
|
||||
if (mUpdatingProjectSources != null)
|
||||
{
|
||||
int symCount = 0;
|
||||
for (var replaceSymbolData in mUpdatingProjectSources)
|
||||
{
|
||||
symCount += replaceSymbolData.mSpans.Count;
|
||||
}
|
||||
|
||||
g.SetFont(DarkTheme.sDarkTheme.mSmallFont);
|
||||
var drawString = scope String();
|
||||
drawString.AppendF("{0} {1} in {2} {3}",
|
||||
symCount, (symCount == 1) ? "reference" : "references",
|
||||
mUpdatingProjectSources.Count, (mUpdatingProjectSources.Count == 1) ? "file" : "files");
|
||||
g.DrawString(drawString, GS!(8), GS!(22), FontAlign.Centered, mWidth - GS!(8) - GS!(16));
|
||||
|
||||
if (lockedFileCount > 0)
|
||||
{
|
||||
g.SetFont(DarkTheme.sDarkTheme.mSmallBoldFont);
|
||||
using (g.PushColor(((mUpdateCnt > 200) || (mUpdateCnt / 10 % 2 == 0)) ? 0xFFFF7070 : 0xFFFFB0B0))
|
||||
g.DrawString(scope String()..AppendF("{} {} LOCKED!", lockedFileCount, (lockedFileCount == 1) ? "FILE" : "FILES"),
|
||||
GS!(8), GS!(38), FontAlign.Centered, mWidth - GS!(8) - GS!(16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -913,31 +968,6 @@ namespace IDE.ui
|
|||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
if ((mReplaceStr == "") && (mUpdatingProjectSources != null))
|
||||
{
|
||||
for (int32 sourceIdx = 0; sourceIdx < mUpdatingProjectSources.Count; sourceIdx++)
|
||||
{
|
||||
var replaceSymbolData = mUpdatingProjectSources[sourceIdx];
|
||||
|
||||
int32 idxOffset = 0;
|
||||
for (int32 i = 0; i < replaceSymbolData.mSpans.Count; i++)
|
||||
{
|
||||
int32 spanStart = replaceSymbolData.mSpans[i].mSpanStart + idxOffset;
|
||||
int32 spanLen = replaceSymbolData.mSpans[i].mSpanLength;
|
||||
|
||||
if ((index == spanStart) && (spanLen == 0))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*if (hadSel)
|
||||
{
|
||||
Update();
|
||||
}*/
|
||||
}
|
||||
|
||||
public void SourcePreRemoveText(SourceEditWidgetContent sourceEditWidgetContent, int index, int length)
|
||||
|
@ -971,29 +1001,6 @@ namespace IDE.ui
|
|||
|
||||
if (mSkipNextUpdate)
|
||||
{
|
||||
/*string newStr = mReplaceStr;
|
||||
for (int sourceIdx = 0; sourceIdx < mUpdatingProjectSources.Count; sourceIdx++)
|
||||
{
|
||||
var replaceSymbolData = mUpdatingProjectSources[sourceIdx];
|
||||
var projectSource = replaceSymbolData.mProjectSource;
|
||||
var editData = IDEApp.sApp.GetEditData(replaceSymbolData.mProjectSource);
|
||||
var editWidgetContent = editData.mEditWidget.Content;
|
||||
|
||||
int idxOffset = 0;
|
||||
for (int i = 0; i < replaceSymbolData.mSpans.Count; i++)
|
||||
{
|
||||
int spanStart = replaceSymbolData.mSpans[i].mSpanStart + idxOffset;
|
||||
int spanLen = replaceSymbolData.mSpans[i].mSpanLength;
|
||||
|
||||
for (int attrIdx = spanStart; attrIdx < spanStart + newStr.Length; attrIdx++)
|
||||
{
|
||||
editWidgetContent.mText[attrIdx].mDisplayFlags |= (byte)(SourceElementFlags.SymbolReference);
|
||||
}
|
||||
|
||||
idxOffset += newStr.Length - spanLen;
|
||||
}
|
||||
}*/
|
||||
|
||||
mSkipNextUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -4373,7 +4373,9 @@ namespace IDE.ui
|
|||
|
||||
public void RenameSymbol()
|
||||
{
|
||||
ShowSymbolReferenceHelper(SymbolReferenceHelper.Kind.Rename);
|
||||
var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content;
|
||||
if (!sourceEditWidgetContent.CheckReadOnly())
|
||||
ShowSymbolReferenceHelper(SymbolReferenceHelper.Kind.Rename);
|
||||
}
|
||||
|
||||
public void FindAllReferences()
|
||||
|
|
|
@ -189,7 +189,12 @@ namespace IDE.ui
|
|||
: base(new ExpressionEditWidgetContent())
|
||||
{
|
||||
var editWidgetContent = (ExpressionEditWidgetContent)mEditWidgetContent;
|
||||
editWidgetContent.mOnGenerateAutocomplete = new (keyChar, options) => UpdateText(keyChar, true);
|
||||
editWidgetContent.mOnGenerateAutocomplete = new (keyChar, options) =>
|
||||
{
|
||||
if ((editWidgetContent.mAutoComplete?.mIsDocumentationPass).GetValueOrDefault())
|
||||
return;
|
||||
UpdateText(keyChar, true);
|
||||
};
|
||||
editWidgetContent.mScrollToStartOnLostFocus = true;
|
||||
mScrollContentInsets.Set(GS!(4), GS!(3), GS!(1), GS!(3));
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue