From 820b37721bf5be5297af465ad8b4eddb2a3ba883 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 11 Oct 2020 00:08:07 +0200 Subject: [PATCH] - moved tab completion code into 'common' as well. --- source/CMakeLists.txt | 2 + source/common/console/c_commandbuffer.cpp | 343 +++++++++++++++++++++ source/common/console/c_commandbuffer.h | 360 ++-------------------- source/common/console/c_console.h | 4 +- source/common/console/c_tabcomplete.cpp | 322 +++++++++++++++++++ source/common/console/c_tabcomplete.h | 9 + source/core/console/c_console.cpp | 283 ----------------- 7 files changed, 702 insertions(+), 621 deletions(-) create mode 100644 source/common/console/c_commandbuffer.cpp create mode 100644 source/common/console/c_tabcomplete.cpp create mode 100644 source/common/console/c_tabcomplete.h diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 671c1d117..d98aa111c 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -876,6 +876,8 @@ set (PCH_SOURCES common/console/c_consolebuffer.cpp common/console/c_cvars.cpp common/console/c_dispatch.cpp + common/console/c_commandbuffer.cpp + common/console/c_tabcomplete.cpp common/console/c_expr.cpp common/utility/engineerrors.cpp common/utility/i_module.cpp diff --git a/source/common/console/c_commandbuffer.cpp b/source/common/console/c_commandbuffer.cpp new file mode 100644 index 000000000..313df7567 --- /dev/null +++ b/source/common/console/c_commandbuffer.cpp @@ -0,0 +1,343 @@ +/* +** c_commandbuffer.cpp +** +**--------------------------------------------------------------------------- +** Copyright 1998-2006 Randy Heit +** Copyright 2010-2020 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ +#include "c_commandbuffer.h" +#include "v_draw.h" +#include "v_2ddrawer.h" +#include "v_font.h" +#include "utf8.h" + +FCommandBuffer CmdLine; + +FCommandBuffer::FCommandBuffer(const FCommandBuffer &o) +{ + Text = o.Text; + CursorPos = o.CursorPos; + StartPos = o.StartPos; +} + +FString FCommandBuffer::GetText() const +{ + FString build; + for (auto chr : Text) build.AppendCharacter(chr); + return build; +} + +void FCommandBuffer::Draw(int x, int y, int scale, bool cursor) +{ + if (scale == 1) + { + DrawChar(twod, CurrentConsoleFont, CR_ORANGE, x, y, '\x1c', TAG_DONE); + DrawText(twod, CurrentConsoleFont, CR_ORANGE, x + CurrentConsoleFont->GetCharWidth(0x1c), y, + &Text[StartPos], TAG_DONE); + + if (cursor) + { + DrawChar(twod, CurrentConsoleFont, CR_YELLOW, + x + CurrentConsoleFont->GetCharWidth(0x1c) + (CursorPosCells - StartPosCells) * CurrentConsoleFont->GetCharWidth(0xb), + y, '\xb', TAG_DONE); + } + } + else + { + DrawChar(twod, CurrentConsoleFont, CR_ORANGE, x, y, '\x1c', + DTA_VirtualWidth, twod->GetWidth() / scale, + DTA_VirtualHeight, twod->GetHeight() / scale, + DTA_KeepRatio, true, TAG_DONE); + + DrawText(twod, CurrentConsoleFont, CR_ORANGE, x + CurrentConsoleFont->GetCharWidth(0x1c), y, + &Text[StartPos], + DTA_VirtualWidth, twod->GetWidth() / scale, + DTA_VirtualHeight, twod->GetHeight() / scale, + DTA_KeepRatio, true, TAG_DONE); + + if (cursor) + { + DrawChar(twod, CurrentConsoleFont, CR_YELLOW, + x + CurrentConsoleFont->GetCharWidth(0x1c) + (CursorPosCells - StartPosCells) * CurrentConsoleFont->GetCharWidth(0xb), + y, '\xb', + DTA_VirtualWidth, twod->GetWidth() / scale, + DTA_VirtualHeight, twod->GetHeight() / scale, + DTA_KeepRatio, true, TAG_DONE); + } + } +} + +unsigned FCommandBuffer::CalcCellSize(unsigned length) +{ + unsigned cellcount = 0; + for (unsigned i = 0; i < length; i++) + { + int w; + NewConsoleFont->GetChar(Text[i], CR_UNTRANSLATED, &w); + cellcount += w / 9; + } + return cellcount; + +} + +unsigned FCommandBuffer::CharsForCells(unsigned cellin, bool *overflow) +{ + unsigned chars = 0; + int cells = cellin; + while (cells > 0) + { + int w; + NewConsoleFont->GetChar(Text[chars++], CR_UNTRANSLATED, &w); + cells -= w / 9; + } + *overflow = (cells < 0); + return chars; +} + + +void FCommandBuffer::MakeStartPosGood() +{ + // Make sure both values point to something valid. + if (CursorPos > Text.length()) CursorPos = (unsigned)Text.length(); + if (StartPos > Text.length()) StartPos = (unsigned)Text.length(); + + CursorPosCells = CalcCellSize(CursorPos); + StartPosCells = CalcCellSize(StartPos); + unsigned LengthCells = CalcCellSize((unsigned)Text.length()); + + int n = StartPosCells; + unsigned cols = ConCols / active_con_scale(twod); + + if (StartPosCells >= LengthCells) + { // Start of visible line is beyond end of line + n = CursorPosCells - cols + 2; + } + if ((CursorPosCells - StartPosCells) >= cols - 2) + { // The cursor is beyond the visible part of the line + n = CursorPosCells - cols + 2; + } + if (StartPosCells > CursorPosCells) + { // The cursor is in front of the visible part of the line + n = CursorPosCells; + } + StartPosCells = std::max(0, n); + bool overflow; + StartPos = CharsForCells(StartPosCells, &overflow); + if (overflow) + { + // We ended up in the middle of a double cell character, so set the start to the following character. + StartPosCells++; + StartPos = CharsForCells(StartPosCells, &overflow); + } +} + +void FCommandBuffer::CursorStart() +{ + CursorPos = 0; + StartPos = 0; + CursorPosCells = 0; + StartPosCells = 0; +} + +void FCommandBuffer::CursorEnd() +{ + CursorPos = (unsigned)Text.length(); + MakeStartPosGood(); +} + +void FCommandBuffer::CursorLeft() +{ + if (CursorPos > 0) + { + MoveCursorLeft(); + MakeStartPosGood(); + } +} + +void FCommandBuffer::CursorRight() +{ + if (CursorPos < Text.length()) + { + MoveCursorRight(); + MakeStartPosGood(); + } +} + +void FCommandBuffer::CursorWordLeft() +{ + if (CursorPos > 0) + { + do MoveCursorLeft(); + while (CursorPos > 0 && Text[CursorPos - 1] != ' '); + MakeStartPosGood(); + } +} + +void FCommandBuffer::CursorWordRight() +{ + if (CursorPos < Text.length()) + { + do MoveCursorRight(); + while (CursorPos < Text.length() && Text[CursorPos] != ' '); + MakeStartPosGood(); + } +} + +void FCommandBuffer::DeleteLeft() +{ + if (CursorPos > 0) + { + MoveCursorLeft(); + Text.erase(CursorPos, 1); + MakeStartPosGood(); + } +} + +void FCommandBuffer::DeleteRight() +{ + if (CursorPos < Text.length()) + { + Text.erase(CursorPos, 1); + MakeStartPosGood(); + } +} + +void FCommandBuffer::DeleteWordLeft() +{ + if (CursorPos > 0) + { + auto now = CursorPos; + + CursorWordLeft(); + + if (AppendToYankBuffer) { + YankBuffer = Text.substr(CursorPos, now - CursorPos) + YankBuffer; + } else { + YankBuffer = Text.substr(CursorPos, now - CursorPos); + } + Text.erase(CursorPos, now - CursorPos); + MakeStartPosGood(); + } +} + +void FCommandBuffer::DeleteLineLeft() +{ + if (CursorPos > 0) + { + if (AppendToYankBuffer) { + YankBuffer = Text.substr(0, CursorPos) + YankBuffer; + } else { + YankBuffer = Text.substr(0, CursorPos); + } + Text.erase(0, CursorPos); + CursorStart(); + } +} + +void FCommandBuffer::DeleteLineRight() +{ + if (CursorPos < Text.length()) + { + if (AppendToYankBuffer) { + YankBuffer += Text.substr(CursorPos, Text.length() - CursorPos); + } else { + YankBuffer = Text.substr(CursorPos, Text.length() - CursorPos); + } + Text.resize(CursorPos); + CursorEnd(); + } +} + +void FCommandBuffer::AddChar(int character) +{ + if (Text.length() == 0) + { + Text += character; + } + else + { + Text.insert(CursorPos, 1, character); + } + CursorPos++; + MakeStartPosGood(); +} + +void FCommandBuffer::AddString(FString clip) +{ + if (clip.IsNotEmpty()) + { + // Only paste the first line. + long brk = clip.IndexOfAny("\r\n\b"); + std::u32string build; + if (brk >= 0) + { + clip.Truncate(brk); + } + auto strp = (const uint8_t*)clip.GetChars(); + while (auto chr = GetCharFromString(strp)) build += chr; + + if (Text.length() == 0) + { + Text = build; + } + else + { + Text.insert(CursorPos, build); + } + CursorPos += (unsigned)build.length(); + MakeStartPosGood(); + } +} + +void FCommandBuffer::SetString(const FString &str) +{ + Text.clear(); + auto strp = (const uint8_t*)str.GetChars(); + while (auto chr = GetCharFromString(strp)) Text += chr; + + CursorEnd(); + MakeStartPosGood(); +} + +void FCommandBuffer::AddYankBuffer() +{ + if (YankBuffer.length() > 0) + { + if (Text.length() == 0) + { + Text = YankBuffer; + } + else + { + Text.insert(CursorPos, YankBuffer); + } + CursorPos += (unsigned)YankBuffer.length(); + MakeStartPosGood(); + } +} diff --git a/source/common/console/c_commandbuffer.h b/source/common/console/c_commandbuffer.h index a1fddd9aa..be257fb24 100644 --- a/source/common/console/c_commandbuffer.h +++ b/source/common/console/c_commandbuffer.h @@ -1,38 +1,5 @@ -/* -** c_commandbuffer.cpp -** -**--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit -** Copyright 2010-2020 Christoph Oelckers -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -// for inclusion in c_console.cpp. +#include +#include "zstring.h" struct FCommandBuffer { @@ -51,142 +18,20 @@ public: FCommandBuffer() = default; - FCommandBuffer(const FCommandBuffer &o) - { - Text = o.Text; - CursorPos = o.CursorPos; - StartPos = o.StartPos; - } - - FString GetText() const - { - FString build; - for (auto chr : Text) build.AppendCharacter(chr); - return build; - } + FCommandBuffer(const FCommandBuffer &o); + FString GetText() const; size_t TextLength() const { return Text.length(); } - void Draw(int x, int y, int scale, bool cursor) - { - if (scale == 1) - { - DrawChar(twod, CurrentConsoleFont, CR_ORANGE, x, y, '\x1c', TAG_DONE); - DrawText(twod, CurrentConsoleFont, CR_ORANGE, x + CurrentConsoleFont->GetCharWidth(0x1c), y, - &Text[StartPos], TAG_DONE); - - if (cursor) - { - DrawChar(twod, CurrentConsoleFont, CR_YELLOW, - x + CurrentConsoleFont->GetCharWidth(0x1c) + (CursorPosCells - StartPosCells) * CurrentConsoleFont->GetCharWidth(0xb), - y, '\xb', TAG_DONE); - } - } - else - { - DrawChar(twod, CurrentConsoleFont, CR_ORANGE, x, y, '\x1c', - DTA_VirtualWidth, twod->GetWidth() / scale, - DTA_VirtualHeight, twod->GetHeight() / scale, - DTA_KeepRatio, true, TAG_DONE); - - DrawText(twod, CurrentConsoleFont, CR_ORANGE, x + CurrentConsoleFont->GetCharWidth(0x1c), y, - &Text[StartPos], - DTA_VirtualWidth, twod->GetWidth() / scale, - DTA_VirtualHeight, twod->GetHeight() / scale, - DTA_KeepRatio, true, TAG_DONE); - - if (cursor) - { - DrawChar(twod, CurrentConsoleFont, CR_YELLOW, - x + CurrentConsoleFont->GetCharWidth(0x1c) + (CursorPosCells - StartPosCells) * CurrentConsoleFont->GetCharWidth(0xb), - y, '\xb', - DTA_VirtualWidth, twod->GetWidth() / scale, - DTA_VirtualHeight, twod->GetHeight() / scale, - DTA_KeepRatio, true, TAG_DONE); - } - } - } - - unsigned CalcCellSize(unsigned length) - { - unsigned cellcount = 0; - for (unsigned i = 0; i < length; i++) - { - int w; - NewConsoleFont->GetChar(Text[i], CR_UNTRANSLATED, &w); - cellcount += w / 9; - } - return cellcount; - - } - - unsigned CharsForCells(unsigned cellin, bool *overflow) - { - unsigned chars = 0; - int cells = cellin; - while (cells > 0) - { - int w; - NewConsoleFont->GetChar(Text[chars++], CR_UNTRANSLATED, &w); - cells -= w / 9; - } - *overflow = (cells < 0); - return chars; - } - - - void MakeStartPosGood() - { - // Make sure both values point to something valid. - if (CursorPos > Text.length()) CursorPos = (unsigned)Text.length(); - if (StartPos > Text.length()) StartPos = (unsigned)Text.length(); - - CursorPosCells = CalcCellSize(CursorPos); - StartPosCells = CalcCellSize(StartPos); - unsigned LengthCells = CalcCellSize((unsigned)Text.length()); - - int n = StartPosCells; - unsigned cols = ConCols / active_con_scale(twod); - - if (StartPosCells >= LengthCells) - { // Start of visible line is beyond end of line - n = CursorPosCells - cols + 2; - } - if ((CursorPosCells - StartPosCells) >= cols - 2) - { // The cursor is beyond the visible part of the line - n = CursorPosCells - cols + 2; - } - if (StartPosCells > CursorPosCells) - { // The cursor is in front of the visible part of the line - n = CursorPosCells; - } - StartPosCells = std::max(0, n); - bool overflow; - StartPos = CharsForCells(StartPosCells, &overflow); - if (overflow) - { - // We ended up in the middle of a double cell character, so set the start to the following character. - StartPosCells++; - StartPos = CharsForCells(StartPosCells, &overflow); - } - } - - void CursorStart() - { - CursorPos = 0; - StartPos = 0; - CursorPosCells = 0; - StartPosCells = 0; - } - - void CursorEnd() - { - CursorPos = (unsigned)Text.length(); - MakeStartPosGood(); - } + void Draw(int x, int y, int scale, bool cursor); + unsigned CalcCellSize(unsigned length); + unsigned CharsForCells(unsigned cellin, bool *overflow); + void MakeStartPosGood();; + void CursorStart(); + void CursorEnd(); private: void MoveCursorLeft() @@ -200,174 +45,19 @@ private: } public: - void CursorLeft() - { - if (CursorPos > 0) - { - MoveCursorLeft(); - MakeStartPosGood(); - } - } - - void CursorRight() - { - if (CursorPos < Text.length()) - { - MoveCursorRight(); - MakeStartPosGood(); - } - } - - void CursorWordLeft() - { - if (CursorPos > 0) - { - do MoveCursorLeft(); - while (CursorPos > 0 && Text[CursorPos - 1] != ' '); - MakeStartPosGood(); - } - } - - void CursorWordRight() - { - if (CursorPos < Text.length()) - { - do MoveCursorRight(); - while (CursorPos < Text.length() && Text[CursorPos] != ' '); - MakeStartPosGood(); - } - } - - void DeleteLeft() - { - if (CursorPos > 0) - { - MoveCursorLeft(); - Text.erase(CursorPos, 1); - MakeStartPosGood(); - } - } - - void DeleteRight() - { - if (CursorPos < Text.length()) - { - Text.erase(CursorPos, 1); - MakeStartPosGood(); - } - } - - void DeleteWordLeft() - { - if (CursorPos > 0) - { - auto now = CursorPos; - - CursorWordLeft(); - - if (AppendToYankBuffer) { - YankBuffer = Text.substr(CursorPos, now - CursorPos) + YankBuffer; - } else { - YankBuffer = Text.substr(CursorPos, now - CursorPos); - } - Text.erase(CursorPos, now - CursorPos); - MakeStartPosGood(); - } - } - - void DeleteLineLeft() - { - if (CursorPos > 0) - { - if (AppendToYankBuffer) { - YankBuffer = Text.substr(0, CursorPos) + YankBuffer; - } else { - YankBuffer = Text.substr(0, CursorPos); - } - Text.erase(0, CursorPos); - CursorStart(); - } - } - - void DeleteLineRight() - { - if (CursorPos < Text.length()) - { - if (AppendToYankBuffer) { - YankBuffer += Text.substr(CursorPos, Text.length() - CursorPos); - } else { - YankBuffer = Text.substr(CursorPos, Text.length() - CursorPos); - } - Text.resize(CursorPos); - CursorEnd(); - } - } - - void AddChar(int character) - { - if (Text.length() == 0) - { - Text += character; - } - else - { - Text.insert(CursorPos, 1, character); - } - CursorPos++; - MakeStartPosGood(); - } - - void AddString(FString clip) - { - if (clip.IsNotEmpty()) - { - // Only paste the first line. - long brk = clip.IndexOfAny("\r\n\b"); - std::u32string build; - if (brk >= 0) - { - clip.Truncate(brk); - } - auto strp = (const uint8_t*)clip.GetChars(); - while (auto chr = GetCharFromString(strp)) build += chr; - - if (Text.length() == 0) - { - Text = build; - } - else - { - Text.insert(CursorPos, build); - } - CursorPos += (unsigned)build.length(); - MakeStartPosGood(); - } - } - - void SetString(const FString &str) - { - Text.clear(); - auto strp = (const uint8_t*)str.GetChars(); - while (auto chr = GetCharFromString(strp)) Text += chr; - - CursorEnd(); - MakeStartPosGood(); - } - - void AddYankBuffer() - { - if (YankBuffer.length() > 0) - { - if (Text.length() == 0) - { - Text = YankBuffer; - } - else - { - Text.insert(CursorPos, YankBuffer); - } - CursorPos += (unsigned)YankBuffer.length(); - MakeStartPosGood(); - } - } + void CursorLeft(); + void CursorRight(); + void CursorWordLeft(); + void CursorWordRight(); + void DeleteLeft(); + void DeleteRight(); + void DeleteWordLeft(); + void DeleteLineLeft(); + void DeleteLineRight(); + void AddChar(int character); + void AddString(FString clip); + void SetString(const FString &str); + void AddYankBuffer(); }; + +extern FCommandBuffer CmdLine; diff --git a/source/common/console/c_console.h b/source/common/console/c_console.h index 5ea3f8f34..88415b1fc 100644 --- a/source/common/console/c_console.h +++ b/source/common/console/c_console.h @@ -36,6 +36,7 @@ #include #include "basics.h" +#include "c_tabcomplete.h" struct event_t; @@ -81,9 +82,6 @@ void C_MidPrint (FFont *font, const char *message, bool bold = false); bool C_Responder (event_t *ev); -void C_AddTabCommand (const char *name); -void C_RemoveTabCommand (const char *name); -void C_ClearTabCommands(); // Removes all tab commands void C_SetNotifyFontScale(double scale); extern const char *console_bar; diff --git a/source/common/console/c_tabcomplete.cpp b/source/common/console/c_tabcomplete.cpp new file mode 100644 index 000000000..ca8339062 --- /dev/null +++ b/source/common/console/c_tabcomplete.cpp @@ -0,0 +1,322 @@ +/* +** c_tabcomplete.cpp +** Tab completion code +** +**--------------------------------------------------------------------------- +** Copyright 1998-2006 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "name.h" +#include "tarray.h" +#include "zstring.h" +#include "c_commandbuffer.h" +#include "c_cvars.h" +#include "c_tabcomplete.h" +#include "printf.h" +#include "c_dispatch.h" +#include "v_draw.h" +#include + + + +struct TabData +{ + int UseCount; + FName TabName; + + TabData() + : UseCount(0), TabName(NAME_None) + { + } + + TabData(const char *name) + : UseCount(1), TabName(name) + { + } + + TabData(const TabData &other) = default; +}; + +static TArray TabCommands (TArray::NoInit); +static int TabPos; // Last TabCommand tabbed to +static int TabStart; // First char in CmdLine to use for tab completion +static int TabSize; // Size of tab string + +bool TabbedLast; // True if last key pressed was tab +bool TabbedList; // True if tab list was shown +CVAR(Bool, con_notablist, false, CVAR_ARCHIVE) + +static bool FindTabCommand (const char *name, int *stoppos, int len) +{ + FName aname(name); + unsigned int i; + int cval = 1; + + for (i = 0; i < TabCommands.Size(); i++) + { + if (TabCommands[i].TabName == aname) + { + *stoppos = i; + return true; + } + cval = strnicmp (TabCommands[i].TabName.GetChars(), name, len); + if (cval >= 0) + break; + } + + *stoppos = i; + + return (cval == 0); +} + +void C_AddTabCommand (const char *name) +{ + int pos; + + if (FindTabCommand (name, &pos, INT_MAX)) + { + TabCommands[pos].UseCount++; + } + else + { + TabData tab(name); + TabCommands.Insert (pos, tab); + } +} + +void C_RemoveTabCommand (const char *name) +{ + if (TabCommands.Size() == 0) + { + // There are no tab commands that can be removed. + // This is important to skip construction of aname + // in case the NameManager has already been destroyed. + return; + } + + FName aname(name, true); + + if (aname == NAME_None) + { + return; + } + for (unsigned int i = 0; i < TabCommands.Size(); ++i) + { + if (TabCommands[i].TabName == aname) + { + if (--TabCommands[i].UseCount == 0) + { + TabCommands.Delete(i); + } + break; + } + } +} + +void C_ClearTabCommands () +{ + TabCommands.Clear(); +} + +static int FindDiffPoint (FName name1, const char *str2) +{ + const char *str1 = name1.GetChars(); + int i; + + for (i = 0; tolower(str1[i]) == tolower(str2[i]); i++) + if (str1[i] == 0 || str2[i] == 0) + break; + + return i; +} + +void C_TabComplete (bool goForward) +{ + unsigned i; + int diffpoint; + + auto CmdLineText = CmdLine.GetText(); + if (!TabbedLast) + { + bool cancomplete; + + + // Skip any spaces at beginning of command line + for (i = 0; i < CmdLineText.Len(); ++i) + { + if (CmdLineText[i] != ' ') + break; + } + if (i == CmdLineText.Len()) + { // Line was nothing but spaces + return; + } + TabStart = i; + + TabSize = (int)CmdLineText.Len() - TabStart; + + if (!FindTabCommand(&CmdLineText[TabStart], &TabPos, TabSize)) + return; // No initial matches + + // Show a list of possible completions, if more than one. + if (TabbedList || con_notablist) + { + cancomplete = true; + } + else + { + cancomplete = C_TabCompleteList (); + TabbedList = true; + } + + if (goForward) + { // Position just before the list of completions so that when TabPos + // gets advanced below, it will be at the first one. + --TabPos; + } + else + { // Find the last matching tab, then go one past it. + while (++TabPos < (int)TabCommands.Size()) + { + if (FindDiffPoint(TabCommands[TabPos].TabName, &CmdLineText[TabStart]) < TabSize) + { + break; + } + } + } + TabbedLast = true; + if (!cancomplete) + { + return; + } + } + + if ((goForward && ++TabPos == (int)TabCommands.Size()) || + (!goForward && --TabPos < 0)) + { + TabbedLast = false; + CmdLineText.Truncate(TabSize); + } + else + { + diffpoint = FindDiffPoint(TabCommands[TabPos].TabName, &CmdLineText[TabStart]); + + if (diffpoint < TabSize) + { + // No more matches + TabbedLast = false; + CmdLineText.Truncate(TabSize - TabStart); + } + else + { + CmdLineText.Truncate(TabStart); + CmdLineText << TabCommands[TabPos].TabName.GetChars() << ' '; + } + } + CmdLine.SetString(CmdLineText); + CmdLine.MakeStartPosGood(); +} + +bool C_TabCompleteList () +{ + int nummatches, i; + size_t maxwidth; + int commonsize = INT_MAX; + + nummatches = 0; + maxwidth = 0; + + auto CmdLineText = CmdLine.GetText(); + for (i = TabPos; i < (int)TabCommands.Size(); ++i) + { + if (FindDiffPoint (TabCommands[i].TabName, &CmdLineText[TabStart]) < TabSize) + { + break; + } + else + { + if (i > TabPos) + { + // This keeps track of the longest common prefix for all the possible + // completions, so we can fill in part of the command for the user if + // the longest common prefix is longer than what the user already typed. + int diffpt = FindDiffPoint (TabCommands[i-1].TabName, TabCommands[i].TabName.GetChars()); + if (diffpt < commonsize) + { + commonsize = diffpt; + } + } + nummatches++; + maxwidth = std::max (maxwidth, strlen (TabCommands[i].TabName.GetChars())); + } + } + if (nummatches > 1) + { + size_t x = 0; + maxwidth += 3; + Printf (TEXTCOLOR_BLUE "Completions for %s:\n", CmdLineText.GetChars()); + for (i = TabPos; nummatches > 0; ++i, --nummatches) + { + // [Dusk] Print console commands blue, CVars green, aliases red. + const char* colorcode = ""; + FConsoleCommand* ccmd; + if (FindCVar (TabCommands[i].TabName.GetChars(), NULL)) + colorcode = TEXTCOLOR_GREEN; + else if ((ccmd = FConsoleCommand::FindByName (TabCommands[i].TabName.GetChars())) != NULL) + { + if (ccmd->IsAlias()) + colorcode = TEXTCOLOR_RED; + else + colorcode = TEXTCOLOR_LIGHTBLUE; + } + + Printf ("%s%-*s", colorcode, int(maxwidth), TabCommands[i].TabName.GetChars()); + x += maxwidth; + if (x > CmdLine.ConCols / active_con_scale(twod) - maxwidth) + { + x = 0; + Printf ("\n"); + } + } + if (x != 0) + { + Printf ("\n"); + } + // Fill in the longest common prefix, if it's longer than what was typed. + if (TabSize != commonsize) + { + TabSize = commonsize; + CmdLineText.Truncate(TabStart); + CmdLineText.AppendCStrPart(TabCommands[TabPos].TabName.GetChars(), commonsize); + CmdLine.SetString(CmdLineText); + } + return false; + } + return true; +} diff --git a/source/common/console/c_tabcomplete.h b/source/common/console/c_tabcomplete.h new file mode 100644 index 000000000..1bef4196b --- /dev/null +++ b/source/common/console/c_tabcomplete.h @@ -0,0 +1,9 @@ +#pragma once + +void C_AddTabCommand (const char *name); +void C_RemoveTabCommand (const char *name); +void C_ClearTabCommands(); // Removes all tab commands +void C_TabComplete(bool goForward); +bool C_TabCompleteList(); +extern bool TabbedLast; // True if last key pressed was tab +extern bool TabbedList; // True if tab list was shown diff --git a/source/core/console/c_console.cpp b/source/core/console/c_console.cpp index 314199365..df38ffa59 100644 --- a/source/core/console/c_console.cpp +++ b/source/core/console/c_console.cpp @@ -88,13 +88,6 @@ CUSTOM_CVAR(Int, con_buffersize, -1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) FConsoleBuffer *conbuffer; -static void C_TabComplete (bool goForward); -static bool C_TabCompleteList (); -static bool TabbedLast; // True if last key pressed was tab -static bool TabbedList; // True if tab list was shown -CVAR(Bool, con_notablist, false, CVAR_ARCHIVE) - - static FTextureID conback; static uint32_t conshade; static bool conline; @@ -179,8 +172,6 @@ struct History FString String; }; -static FCommandBuffer CmdLine; - #define MAXHISTSIZE 50 static struct History *HistHead = NULL, *HistTail = NULL, *HistPos = NULL; static int HistSize; @@ -1546,277 +1537,3 @@ void C_MidPrint (FFont *font, const char *msg, bool bold) #endif } - -/****** Tab completion code ******/ - -struct TabData -{ - int UseCount; - FName TabName; - - TabData() - : UseCount(0), TabName(NAME_None) - { - } - - TabData(const char *name) - : UseCount(1), TabName(name) - { - } - - TabData(const TabData &other) = default; -}; - -static TArray TabCommands (TArray::NoInit); -static int TabPos; // Last TabCommand tabbed to -static int TabStart; // First char in CmdLine to use for tab completion -static int TabSize; // Size of tab string - -static bool FindTabCommand (const char *name, int *stoppos, int len) -{ - FName aname(name); - unsigned int i; - int cval = 1; - - for (i = 0; i < TabCommands.Size(); i++) - { - if (TabCommands[i].TabName == aname) - { - *stoppos = i; - return true; - } - cval = strnicmp (TabCommands[i].TabName.GetChars(), name, len); - if (cval >= 0) - break; - } - - *stoppos = i; - - return (cval == 0); -} - -void C_AddTabCommand (const char *name) -{ - int pos; - - if (FindTabCommand (name, &pos, INT_MAX)) - { - TabCommands[pos].UseCount++; - } - else - { - TabData tab(name); - TabCommands.Insert (pos, tab); - } -} - -void C_RemoveTabCommand (const char *name) -{ - if (TabCommands.Size() == 0) - { - // There are no tab commands that can be removed. - // This is important to skip construction of aname - // in case the NameManager has already been destroyed. - return; - } - - FName aname(name, true); - - if (aname == NAME_None) - { - return; - } - for (unsigned int i = 0; i < TabCommands.Size(); ++i) - { - if (TabCommands[i].TabName == aname) - { - if (--TabCommands[i].UseCount == 0) - { - TabCommands.Delete(i); - } - break; - } - } -} - -void C_ClearTabCommands () -{ - TabCommands.Clear(); -} - -static int FindDiffPoint (FName name1, const char *str2) -{ - const char *str1 = name1.GetChars(); - int i; - - for (i = 0; tolower(str1[i]) == tolower(str2[i]); i++) - if (str1[i] == 0 || str2[i] == 0) - break; - - return i; -} - -static void C_TabComplete (bool goForward) -{ - unsigned i; - int diffpoint; - - auto CmdLineText = CmdLine.GetText(); - if (!TabbedLast) - { - bool cancomplete; - - - // Skip any spaces at beginning of command line - for (i = 0; i < CmdLineText.Len(); ++i) - { - if (CmdLineText[i] != ' ') - break; - } - if (i == CmdLineText.Len()) - { // Line was nothing but spaces - return; - } - TabStart = i; - - TabSize = (int)CmdLineText.Len() - TabStart; - - if (!FindTabCommand(&CmdLineText[TabStart], &TabPos, TabSize)) - return; // No initial matches - - // Show a list of possible completions, if more than one. - if (TabbedList || con_notablist) - { - cancomplete = true; - } - else - { - cancomplete = C_TabCompleteList (); - TabbedList = true; - } - - if (goForward) - { // Position just before the list of completions so that when TabPos - // gets advanced below, it will be at the first one. - --TabPos; - } - else - { // Find the last matching tab, then go one past it. - while (++TabPos < (int)TabCommands.Size()) - { - if (FindDiffPoint(TabCommands[TabPos].TabName, &CmdLineText[TabStart]) < TabSize) - { - break; - } - } - } - TabbedLast = true; - if (!cancomplete) - { - return; - } - } - - if ((goForward && ++TabPos == (int)TabCommands.Size()) || - (!goForward && --TabPos < 0)) - { - TabbedLast = false; - CmdLineText.Truncate(TabSize); - } - else - { - diffpoint = FindDiffPoint(TabCommands[TabPos].TabName, &CmdLineText[TabStart]); - - if (diffpoint < TabSize) - { - // No more matches - TabbedLast = false; - CmdLineText.Truncate(TabSize - TabStart); - } - else - { - CmdLineText.Truncate(TabStart); - CmdLineText << TabCommands[TabPos].TabName.GetChars() << ' '; - } - } - CmdLine.SetString(CmdLineText); - CmdLine.MakeStartPosGood(); -} - -static bool C_TabCompleteList () -{ - int nummatches, i; - size_t maxwidth; - int commonsize = INT_MAX; - - nummatches = 0; - maxwidth = 0; - - auto CmdLineText = CmdLine.GetText(); - for (i = TabPos; i < (int)TabCommands.Size(); ++i) - { - if (FindDiffPoint (TabCommands[i].TabName, &CmdLineText[TabStart]) < TabSize) - { - break; - } - else - { - if (i > TabPos) - { - // This keeps track of the longest common prefix for all the possible - // completions, so we can fill in part of the command for the user if - // the longest common prefix is longer than what the user already typed. - int diffpt = FindDiffPoint (TabCommands[i-1].TabName, TabCommands[i].TabName.GetChars()); - if (diffpt < commonsize) - { - commonsize = diffpt; - } - } - nummatches++; - maxwidth = std::max (maxwidth, strlen (TabCommands[i].TabName.GetChars())); - } - } - if (nummatches > 1) - { - size_t x = 0; - maxwidth += 3; - Printf (TEXTCOLOR_BLUE "Completions for %s:\n", CmdLineText.GetChars()); - for (i = TabPos; nummatches > 0; ++i, --nummatches) - { - // [Dusk] Print console commands blue, CVars green, aliases red. - const char* colorcode = ""; - FConsoleCommand* ccmd; - if (FindCVar (TabCommands[i].TabName.GetChars(), NULL)) - colorcode = TEXTCOLOR_GREEN; - else if ((ccmd = FConsoleCommand::FindByName (TabCommands[i].TabName.GetChars())) != NULL) - { - if (ccmd->IsAlias()) - colorcode = TEXTCOLOR_RED; - else - colorcode = TEXTCOLOR_LIGHTBLUE; - } - - Printf ("%s%-*s", colorcode, int(maxwidth), TabCommands[i].TabName.GetChars()); - x += maxwidth; - if (x > CmdLine.ConCols / active_con_scale(twod) - maxwidth) - { - x = 0; - Printf ("\n"); - } - } - if (x != 0) - { - Printf ("\n"); - } - // Fill in the longest common prefix, if it's longer than what was typed. - if (TabSize != commonsize) - { - TabSize = commonsize; - CmdLineText.Truncate(TabStart); - CmdLineText.AppendCStrPart(TabCommands[TabPos].TabName.GetChars(), commonsize); - CmdLine.SetString(CmdLineText); - } - return false; - } - return true; -}