1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-30 12:32:36 +00:00
OpenMW/apps/openmw/mwgui/console.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

863 lines
29 KiB
C++
Raw Normal View History

2010-07-21 10:08:38 +02:00
#include "console.hpp"
2023-02-22 11:20:50 +01:00
#include <MyGUI_Button.h>
2015-01-10 02:50:43 +01:00
#include <MyGUI_EditBox.h>
#include <MyGUI_InputManager.h>
#include <MyGUI_LayerManager.h>
2015-01-10 02:50:43 +01:00
#include <filesystem>
#include <fstream>
#include <regex>
2010-07-21 10:08:38 +02:00
#include <components/compiler/exception.hpp>
2013-08-07 13:16:20 -04:00
#include <components/compiler/extensions0.hpp>
#include <components/compiler/lineparser.hpp>
#include <components/compiler/locals.hpp>
#include <components/compiler/scanner.hpp>
#include <components/files/conversion.hpp>
#include <components/interpreter/interpreter.hpp>
2023-02-26 13:58:17 +01:00
#include <components/misc/utf8stream.hpp>
#include <components/settings/values.hpp>
2010-07-21 10:08:38 +02:00
#include "apps/openmw/mwgui/textcolours.hpp"
2010-07-21 10:08:38 +02:00
#include "../mwscript/extensions.hpp"
#include "../mwscript/interpretercontext.hpp"
2010-07-21 10:08:38 +02:00
#include "../mwbase/environment.hpp"
2021-04-12 03:15:17 +02:00
#include "../mwbase/luamanager.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/class.hpp"
2014-02-23 20:11:05 +01:00
#include "../mwworld/esmstore.hpp"
2010-07-21 10:08:38 +02:00
namespace MWGui
{
class ConsoleInterpreterContext : public MWScript::InterpreterContext
{
Console& mConsole;
2010-07-21 10:08:38 +02:00
public:
ConsoleInterpreterContext(Console& console, MWWorld::Ptr reference);
void report(const std::string& message) override;
2010-07-21 10:08:38 +02:00
};
2010-07-21 10:08:38 +02:00
ConsoleInterpreterContext::ConsoleInterpreterContext(Console& console, MWWorld::Ptr reference)
2020-11-13 11:39:47 +04:00
: MWScript::InterpreterContext(reference.isEmpty() ? nullptr : &reference.getRefData().getLocals(), reference)
2010-07-21 10:08:38 +02:00
, mConsole(console)
{
}
void ConsoleInterpreterContext::report(const std::string& message)
{
mConsole.printOK(message);
}
2010-07-21 10:08:38 +02:00
bool Console::compile(const std::string& cmd, Compiler::Output& output)
{
try
{
ErrorHandler::reset();
2010-07-21 10:08:38 +02:00
std::istringstream input(cmd + '\n');
2010-07-21 10:08:38 +02:00
Compiler::Scanner scanner(*this, input, mCompilerContext.getExtensions());
2010-07-21 10:08:38 +02:00
Compiler::LineParser parser(
*this, mCompilerContext, output.getLocals(), output.getLiterals(), output.getCode(), true);
2010-07-21 10:08:38 +02:00
scanner.scan(parser);
2010-07-21 10:08:38 +02:00
return isGood();
}
catch (const Compiler::SourceException&)
2010-07-21 10:08:38 +02:00
{
// error has already been reported via error handler
}
catch (const std::exception& error)
{
printError(std::string("Error: ") + error.what());
2010-07-21 10:08:38 +02:00
}
2010-07-21 10:08:38 +02:00
return false;
}
void Console::report(const std::string& message, const Compiler::TokenLoc& loc, Type type)
{
std::ostringstream error;
error << "column " << loc.mColumn << " (" << loc.mLiteral << "):";
2010-07-21 10:08:38 +02:00
printError(error.str());
printError((type == ErrorMessage ? "error: " : "warning: ") + message);
}
void Console::report(const std::string& message, Type type)
{
printError((type == ErrorMessage ? "error: " : "warning: ") + message);
}
2011-01-12 18:24:00 +01:00
void Console::listNames()
{
if (mNames.empty())
{
// keywords
std::istringstream input;
2011-01-12 18:24:00 +01:00
Compiler::Scanner scanner(*this, input, mCompilerContext.getExtensions());
scanner.listKeywords(mNames);
// identifier
2023-04-20 21:07:53 +02:00
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
std::vector<ESM::RefId> ids;
2023-04-08 11:39:38 +02:00
for (const auto* store : esmStore)
{
2023-04-08 11:39:38 +02:00
store->listIdentifier(ids);
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
for (auto id : ids)
{
2023-04-13 17:43:13 +02:00
if (id.is<ESM::StringRefId>())
mNames.push_back(id.getRefIdString());
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
}
ids.clear();
}
2023-07-09 00:59:12 +03:00
// exterior cell names and editor IDs aren't technically identifiers,
// but since the COC function accepts them, we should list them too
2023-04-08 11:39:38 +02:00
for (auto it = esmStore.get<ESM::Cell>().extBegin(); it != esmStore.get<ESM::Cell>().extEnd(); ++it)
{
if (!it->mName.empty())
2023-01-19 17:31:45 +01:00
mNames.push_back(it->mName);
}
2023-07-09 00:59:12 +03:00
for (const auto& cell : esmStore.get<ESM4::Cell>())
{
if (!cell.mEditorId.empty())
mNames.push_back(cell.mEditorId);
}
// sort
2011-01-12 18:24:00 +01:00
std::sort(mNames.begin(), mNames.end());
// remove duplicates
mNames.erase(std::unique(mNames.begin(), mNames.end()), mNames.end());
2011-01-12 18:24:00 +01:00
}
}
Console::Console(int w, int h, bool consoleOnlyScripts, Files::ConfigurationManager& cfgMgr)
: WindowBase("openmw_console.layout")
, mCaseSensitiveSearch(false)
, mRegExSearch(false)
, mCompilerContext(MWScript::CompilerContext::Type_Console)
, mConsoleOnlyScripts(consoleOnlyScripts)
, mCfgMgr(cfgMgr)
2010-07-21 10:08:38 +02:00
{
setCoord(10, 10, w - 10, h / 2);
getWidget(mCommandLine, "edit_Command");
getWidget(mHistory, "list_History");
getWidget(mSearchTerm, "edit_SearchTerm");
getWidget(mNextButton, "button_Next");
getWidget(mPreviousButton, "button_Previous");
getWidget(mCaseSensitiveToggleButton, "button_CaseSensitive");
getWidget(mRegExSearchToggleButton, "button_RegExSearch");
2010-07-21 10:08:38 +02:00
// Set up the command line box
mCommandLine->eventEditSelectAccept += newDelegate(this, &Console::acceptCommand);
mCommandLine->eventKeyButtonPressed += newDelegate(this, &Console::commandBoxKeyPress);
2010-07-21 10:08:38 +02:00
// Set up the search term box
mSearchTerm->eventEditSelectAccept += newDelegate(this, &Console::acceptSearchTerm);
mNextButton->eventMouseButtonClick += newDelegate(this, &Console::findNextOccurrence);
mPreviousButton->eventMouseButtonClick += newDelegate(this, &Console::findPreviousOccurrence);
mCaseSensitiveToggleButton->eventMouseButtonClick += newDelegate(this, &Console::toggleCaseSensitiveSearch);
mRegExSearchToggleButton->eventMouseButtonClick += newDelegate(this, &Console::toggleRegExSearch);
2023-02-26 13:58:17 +01:00
2010-07-21 10:08:38 +02:00
// Set up the log window
mHistory->setOverflowToTheLeft(true);
2010-07-21 10:08:38 +02:00
// compiler
Compiler::registerExtensions(mExtensions, mConsoleOnlyScripts);
mCompilerContext.setExtensions(&mExtensions);
// command history file
initConsoleHistory();
}
Console::~Console()
{
if (mCommandHistoryFile && mCommandHistoryFile.is_open())
mCommandHistoryFile.close();
2010-07-21 10:08:38 +02:00
}
void Console::onOpen()
2010-07-21 10:08:38 +02:00
{
// Give keyboard focus to the combo box whenever the console is
// turned on and place it over other widgets
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine);
MyGUI::LayerManager::getInstance().upLayerItem(mMainWidget);
2010-07-21 10:08:38 +02:00
}
2022-04-09 23:07:57 +02:00
void Console::print(const std::string& msg, std::string_view color)
2010-07-21 10:08:38 +02:00
{
2022-04-09 23:07:57 +02:00
mHistory->addText(std::string(color) + MyGUI::TextIterator::toTagsString(msg));
2010-07-21 10:08:38 +02:00
}
void Console::printOK(const std::string& msg)
{
2022-04-09 23:07:57 +02:00
print(msg + "\n", MWBase::WindowManager::sConsoleColor_Success);
2010-07-21 10:08:38 +02:00
}
void Console::printError(const std::string& msg)
{
2022-04-09 23:07:57 +02:00
print(msg + "\n", MWBase::WindowManager::sConsoleColor_Error);
2010-07-21 10:08:38 +02:00
}
void Console::execute(const std::string& command)
{
// Log the command
2022-04-09 23:07:57 +02:00
if (mConsoleMode.empty())
print("> " + command + "\n");
else
print(mConsoleMode + " " + command + "\n");
if (!mConsoleMode.empty() || (command.size() >= 3 && std::string_view(command).substr(0, 3) == "lua"))
{
MWBase::Environment::get().getLuaManager()->handleConsoleCommand(mConsoleMode, command, mPtr);
return;
}
Compiler::Locals locals;
if (!mPtr.isEmpty())
{
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
const ESM::RefId& script = mPtr.getClass().getScript(mPtr);
if (!script.empty())
locals = MWBase::Environment::get().getScriptManager()->getLocals(script);
}
Compiler::Output output(locals);
if (compile(command + "\n", output))
{
try
{
ConsoleInterpreterContext interpreterContext(*this, mPtr);
Interpreter::Interpreter interpreter;
MWScript::installOpcodes(interpreter, mConsoleOnlyScripts);
const Interpreter::Program program = output.getProgram();
interpreter.run(program, interpreterContext);
}
catch (const std::exception& error)
{
printError(std::string("Error: ") + error.what());
}
}
}
void Console::executeFile(const std::filesystem::path& path)
2012-07-30 12:37:46 +02:00
{
std::ifstream stream(path);
2012-07-30 12:37:46 +02:00
if (!stream.is_open())
{
printError("Failed to open script file \"" + Files::pathToUnicodeString(path)
+ "\": " + std::generic_category().message(errno));
return;
2012-07-30 12:37:46 +02:00
}
std::string line;
while (std::getline(stream, line))
execute(line);
2012-07-30 12:37:46 +02:00
}
void Console::clear()
{
resetReference();
}
bool isWhitespace(char c)
{
return c == ' ' || c == '\t';
}
void Console::commandBoxKeyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char)
2010-07-21 10:08:38 +02:00
{
2019-06-06 22:09:45 +02:00
if (MyGUI::InputManager::getInstance().isControlPressed())
{
if (key == MyGUI::KeyCode::W)
{
auto caption = mCommandLine->getOnlyText();
if (caption.empty())
2019-06-06 22:09:45 +02:00
return;
size_t max = mCommandLine->getTextCursor();
while (max > 0 && (isWhitespace(caption[max - 1]) || caption[max - 1] == '>'))
2019-06-06 22:09:45 +02:00
max--;
while (max > 0 && !isWhitespace(caption[max - 1]) && caption[max - 1] != '>')
2019-06-06 22:09:45 +02:00
max--;
size_t length = mCommandLine->getTextCursor() - max;
if (length > 0)
{
caption.erase(max, length);
mCommandLine->setOnlyText(caption);
mCommandLine->setTextCursor(max);
}
2019-06-06 22:09:45 +02:00
}
else if (key == MyGUI::KeyCode::U)
{
if (mCommandLine->getTextCursor() > 0)
{
auto text = mCommandLine->getOnlyText();
text.erase(0, mCommandLine->getTextCursor());
mCommandLine->setOnlyText(text);
mCommandLine->setTextCursor(0);
}
}
2019-06-06 22:09:45 +02:00
}
2022-04-09 23:07:57 +02:00
else if (key == MyGUI::KeyCode::Tab && mConsoleMode.empty())
{
std::vector<std::string> matches;
listNames();
std::string oldCaption = mCommandLine->getOnlyText();
std::string newCaption = complete(mCommandLine->getOnlyText(), matches);
mCommandLine->setOnlyText(newCaption);
// List candidates if repeatedly pressing tab
2016-02-22 19:06:12 +01:00
if (oldCaption == newCaption && !matches.empty())
{
int i = 0;
printOK({});
for (std::string& match : matches)
{
if (i == 50)
break;
printOK(match);
i++;
}
}
}
if (mCommandHistory.empty())
return;
2010-07-21 10:08:38 +02:00
// Traverse history with up and down arrows
if (key == MyGUI::KeyCode::ArrowUp)
{
// If the user was editing a string, store it for later
if (mCurrent == mCommandHistory.end())
mEditString = mCommandLine->getOnlyText();
2010-07-21 10:08:38 +02:00
if (mCurrent != mCommandHistory.begin())
2010-07-21 10:08:38 +02:00
{
2013-07-31 18:46:32 +02:00
--mCurrent;
mCommandLine->setOnlyText(*mCurrent);
2010-07-21 10:08:38 +02:00
}
}
else if (key == MyGUI::KeyCode::ArrowDown)
{
if (mCurrent != mCommandHistory.end())
2010-07-21 10:08:38 +02:00
{
2013-11-23 22:48:39 +01:00
++mCurrent;
2010-07-21 10:08:38 +02:00
if (mCurrent != mCommandHistory.end())
mCommandLine->setOnlyText(*mCurrent);
2010-07-21 10:08:38 +02:00
else
// Restore the edit string
mCommandLine->setOnlyText(mEditString);
2010-07-21 10:08:38 +02:00
}
}
}
void Console::acceptCommand(MyGUI::EditBox* _sender)
2010-07-21 10:08:38 +02:00
{
const std::string& cm = mCommandLine->getOnlyText();
2010-07-21 10:08:38 +02:00
if (cm.empty())
return;
// Add the command to the history, and set the current pointer to
// the end of the list
if (mCommandHistory.empty() || mCommandHistory.back() != cm)
{
mCommandHistory.push_back(cm);
if (mCommandHistoryFile && mCommandHistoryFile.good())
mCommandHistoryFile << cm << std::endl;
}
mCurrent = mCommandHistory.end();
mEditString.clear();
mHistory->setTextCursor(mHistory->getTextLength());
// Reset the command line before the command execution.
// It prevents the re-triggering of the acceptCommand() event for the same command
// during the actual command execution
mCommandLine->setCaption({});
2010-07-21 10:08:38 +02:00
execute(cm);
2010-07-21 10:08:38 +02:00
}
void Console::toggleCaseSensitiveSearch(MyGUI::Widget* _sender)
{
mCaseSensitiveSearch = !mCaseSensitiveSearch;
// Reset console search highlight position search parameters have changed
mCurrentOccurrenceIndex = std::string::npos;
// Adjust color to reflect toggled status
const TextColours& textColours{ MWBase::Environment::get().getWindowManager()->getTextColours() };
mCaseSensitiveToggleButton->setTextColour(mCaseSensitiveSearch ? textColours.link : textColours.normal);
}
void Console::toggleRegExSearch(MyGUI::Widget* _sender)
{
mRegExSearch = !mRegExSearch;
// Reset console search highlight position search parameters have changed
mCurrentOccurrenceIndex = std::string::npos;
// Adjust color to reflect toggled status
const TextColours& textColours{ MWBase::Environment::get().getWindowManager()->getTextColours() };
mRegExSearchToggleButton->setTextColour(mRegExSearch ? textColours.link : textColours.normal);
// RegEx searches are always case sensitive
mCaseSensitiveSearch = mRegExSearch;
// Dim case sensitive and set disabled if regex search toggled on, restore when toggled off
mCaseSensitiveToggleButton->setTextColour(mCaseSensitiveSearch ? textColours.linkPressed : textColours.normal);
mCaseSensitiveToggleButton->setEnabled(!mRegExSearch);
}
void Console::acceptSearchTerm(MyGUI::EditBox* _sender)
{
const std::string& searchTerm = mSearchTerm->getOnlyText();
2023-02-22 20:15:45 +01:00
if (searchTerm.empty())
{
return;
}
std::string newSearchTerm = mCaseSensitiveSearch ? searchTerm : Utf8Stream::lowerCaseUtf8(searchTerm);
// If new search term reset position, otherwise continue from current position
if (newSearchTerm != mCurrentSearchTerm)
{
2024-01-09 16:11:44 +04:00
mCurrentSearchTerm = std::move(newSearchTerm);
mCurrentOccurrenceIndex = std::string::npos;
}
findNextOccurrence(nullptr);
}
enum class Console::SearchDirection
{
Forward,
Reverse
};
void Console::findNextOccurrence(MyGUI::Widget* _sender)
{
findOccurrence(SearchDirection::Forward);
}
void Console::findPreviousOccurrence(MyGUI::Widget* _sender)
{
findOccurrence(SearchDirection::Reverse);
}
void Console::findOccurrence(const SearchDirection direction)
{
2023-02-22 20:15:45 +01:00
if (mCurrentSearchTerm.empty())
{
return;
}
const auto historyText{ mCaseSensitiveSearch ? mHistory->getOnlyText().asUTF8()
: Utf8Stream::lowerCaseUtf8(mHistory->getOnlyText().asUTF8()) };
// Setup default search range
size_t firstIndex{ 0 };
size_t lastIndex{ historyText.length() };
2023-02-21 15:01:16 +01:00
// If search is not the first adjust the range based on the direction and previous occurrence.
if (mCurrentOccurrenceIndex != std::string::npos)
{
if (direction == SearchDirection::Forward && mCurrentOccurrenceIndex > 1)
{
firstIndex = mCurrentOccurrenceIndex + mCurrentOccurrenceLength;
}
else if (direction == SearchDirection::Reverse
&& (historyText.length() - mCurrentOccurrenceIndex) > mCurrentOccurrenceLength)
{
lastIndex = mCurrentOccurrenceIndex - 1;
}
}
findInHistoryText(historyText, direction, firstIndex, lastIndex);
2023-02-21 15:01:16 +01:00
// If the last search did not find anything AND...
if (mCurrentOccurrenceIndex == std::string::npos)
2023-02-21 15:01:16 +01:00
{
if (direction == SearchDirection::Forward && firstIndex != 0)
{
// ... We didn't start at the beginning, we apply the search to the other half of the text.
findInHistoryText(historyText, direction, 0, firstIndex);
}
else if (direction == SearchDirection::Reverse && lastIndex != historyText.length())
{
// ... We didn't search to the end, we apply the search to the other half of the text.
findInHistoryText(historyText, direction, lastIndex, historyText.length());
}
}
// Only scroll & select if we actually found something
if (mCurrentOccurrenceIndex != std::string::npos)
{
markOccurrence(mCurrentOccurrenceIndex, mCurrentOccurrenceLength);
}
2023-02-21 15:01:16 +01:00
else
{
markOccurrence(0, 0);
}
}
void Console::findInHistoryText(const std::string& historyText, const SearchDirection direction,
const size_t firstIndex, const size_t lastIndex)
{
if (mRegExSearch)
{
findWithRegex(historyText, direction, firstIndex, lastIndex);
}
else
{
findWithStringSearch(historyText, direction, firstIndex, lastIndex);
}
}
void Console::findWithRegex(const std::string& historyText, const SearchDirection direction,
const size_t firstIndex, const size_t lastIndex)
{
// Search text for regex match in given interval
const std::regex pattern{ mCurrentSearchTerm };
std::sregex_iterator match{ (historyText.cbegin() + firstIndex), (historyText.cbegin() + lastIndex), pattern };
const std::sregex_iterator end{};
2023-02-21 15:01:16 +01:00
// If reverse search get last result in interval
if (direction == SearchDirection::Reverse)
{
std::sregex_iterator lastMatch{ end };
while (match != end)
{
lastMatch = match;
++match;
}
match = lastMatch;
}
// If regex match is found in text, set new current occurrence values
if (match != end)
{
mCurrentOccurrenceIndex = match->position() + firstIndex;
mCurrentOccurrenceLength = match->length();
}
else
{
mCurrentOccurrenceIndex = std::string::npos;
mCurrentOccurrenceLength = 0;
}
}
2023-02-21 15:01:16 +01:00
void Console::findWithStringSearch(const std::string& historyText, const SearchDirection direction,
const size_t firstIndex, const size_t lastIndex)
{
// Search in given text interval for search term
const size_t substringLength{ (lastIndex - firstIndex) + 1 };
const std::string_view historyTextView((historyText.c_str() + firstIndex), substringLength);
if (direction == SearchDirection::Forward)
{
mCurrentOccurrenceIndex = historyTextView.find(mCurrentSearchTerm);
}
else
2023-02-21 15:01:16 +01:00
{
mCurrentOccurrenceIndex = historyTextView.rfind(mCurrentSearchTerm);
}
// If search term is found in text, set new current occurrence values
if (mCurrentOccurrenceIndex != std::string::npos)
{
mCurrentOccurrenceIndex += firstIndex;
mCurrentOccurrenceLength = mCurrentSearchTerm.length();
}
2023-02-21 15:01:16 +01:00
else
{
mCurrentOccurrenceLength = 0;
2023-02-21 15:01:16 +01:00
}
}
void Console::markOccurrence(const size_t textPosition, const size_t length)
{
2023-02-21 15:01:16 +01:00
if (textPosition == 0 && length == 0)
{
mHistory->setTextSelection(0, 0);
mHistory->setVScrollPosition(mHistory->getVScrollRange());
return;
}
const auto historyText = mHistory->getOnlyText();
const size_t upperLimit = std::min(historyText.length(), textPosition);
// Since MyGUI::EditBox.setVScrollPosition() works on pixels instead of text positions
// we need to calculate the actual pixel position by counting lines.
size_t lineNumber = 0;
for (size_t i = 0; i < upperLimit; i++)
{
if (historyText[i] == '\n')
{
lineNumber++;
}
}
// Make some space before the actual result
if (lineNumber >= 2)
{
lineNumber -= 2;
}
mHistory->setTextSelection(textPosition, textPosition + length);
mHistory->setVScrollPosition(mHistory->getFontHeight() * lineNumber);
}
std::string Console::complete(std::string input, std::vector<std::string>& matches)
{
2013-04-17 18:56:48 -04:00
std::string output = input;
std::string tmp = input;
bool has_front_quote = false;
/* Does the input string contain things that don't have to be completed? If yes erase them. */
/* Erase a possible call to an explicit reference. */
size_t explicitPos = tmp.find("->");
if (explicitPos != std::string::npos)
{
tmp.erase(0, explicitPos + 2);
}
/* Are there quotation marks? */
2013-04-17 18:56:48 -04:00
if (tmp.find('"') != std::string::npos)
{
int numquotes = 0;
2013-04-17 18:56:48 -04:00
for (std::string::iterator it = tmp.begin(); it < tmp.end(); ++it)
{
if (*it == '"')
numquotes++;
}
/* Is it terminated?*/
if (numquotes % 2)
{
tmp.erase(0, tmp.rfind('"') + 1);
has_front_quote = true;
}
else
{
size_t pos;
2013-04-17 18:56:48 -04:00
if ((((pos = tmp.rfind(' ')) != std::string::npos)) && (pos > tmp.rfind('"')))
{
tmp.erase(0, tmp.rfind(' ') + 1);
}
else
{
tmp.clear();
}
has_front_quote = false;
}
}
/* No quotation marks. Are there spaces?*/
else
{
size_t rpos;
2013-04-17 18:56:48 -04:00
if ((rpos = tmp.rfind(' ')) != std::string::npos)
{
if (rpos == 0)
{
tmp.clear();
}
else
{
tmp.erase(0, rpos + 1);
}
}
}
/* Erase the input from the output string so we can easily append the completed form later. */
output.erase(output.end() - tmp.length(), output.end());
/* Is there still something in the input string? If not just display all commands and return the unchanged
* input. */
if (tmp.length() == 0)
{
matches = mNames;
return input;
}
/* Iterate through the vector. */
for (std::string& name : mNames)
{
bool string_different = false;
/* Is the string shorter than the input string? If yes skip it. */
if (name.length() < tmp.length())
continue;
/* Is the beginning of the string different from the input string? If yes skip it. */
for (std::string::iterator iter = tmp.begin(), iter2 = name.begin(); iter < tmp.end(); ++iter, ++iter2)
{
if (Misc::StringUtils::toLower(*iter) != Misc::StringUtils::toLower(*iter2))
{
string_different = true;
break;
}
}
if (string_different)
continue;
/* The beginning of the string matches the input string, save it for the next test. */
matches.push_back(name);
}
/* There are no matches. Return the unchanged input. */
if (matches.empty())
{
return input;
}
/* Only one match. We're done. */
if (matches.size() == 1)
{
/* Adding quotation marks when the input string started with a quotation mark or has spaces in it*/
2013-04-17 18:56:48 -04:00
if ((matches.front().find(' ') != std::string::npos))
{
if (!has_front_quote)
output += '"';
2013-04-17 18:56:48 -04:00
return output.append(matches.front() + std::string("\" "));
}
else if (has_front_quote)
{
2013-04-17 18:56:48 -04:00
return output.append(matches.front() + std::string("\" "));
}
else
{
2013-04-17 18:56:48 -04:00
return output.append(matches.front() + std::string(" "));
}
}
/* Check if all matching strings match further than input. If yes complete to this match. */
int i = tmp.length();
for (std::string::iterator iter = matches.front().begin() + tmp.length(); iter < matches.front().end();
++iter, ++i)
{
for (std::string& match : matches)
{
if (Misc::StringUtils::toLower(match[i]) != Misc::StringUtils::toLower(*iter))
{
/* Append the longest match to the end of the output string*/
output.append(matches.front().substr(0, i));
return output;
}
}
}
/* All keywords match with the shortest. Append it to the output string and return it. */
return output.append(matches.front());
}
void Console::updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr)
{
if (mPtr == currentPtr)
mPtr = newPtr;
}
void Console::setSelectedObject(const MWWorld::Ptr& object)
{
2013-04-29 21:19:13 +02:00
if (!object.isEmpty())
{
if (object == mPtr)
2022-04-09 23:07:57 +02:00
mPtr = MWWorld::Ptr();
2013-04-29 21:19:13 +02:00
else
mPtr = object;
// User clicked on an object. Restore focus to the console command line.
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine);
2013-04-29 21:19:13 +02:00
}
else
2013-04-29 21:19:13 +02:00
mPtr = MWWorld::Ptr();
2022-04-09 23:07:57 +02:00
updateConsoleTitle();
}
void Console::updateConsoleTitle()
{
std::string title = "#{OMWEngine:ConsoleWindow}";
2022-04-09 23:07:57 +02:00
if (!mConsoleMode.empty())
title = mConsoleMode + " " + title;
if (!mPtr.isEmpty())
title.append(" (" + mPtr.getCellRef().getRefId().toDebugString() + ")");
2022-04-09 23:07:57 +02:00
setTitle(title);
}
void Console::setConsoleMode(std::string_view mode)
{
mConsoleMode = std::string(mode);
updateConsoleTitle();
}
void Console::onReferenceUnavailable()
{
setSelectedObject(MWWorld::Ptr());
}
void Console::resetReference()
{
ReferenceInterface::resetReference();
setSelectedObject(MWWorld::Ptr());
}
void Console::initConsoleHistory()
{
const auto filePath = mCfgMgr.getUserConfigPath() / "console_history.txt";
const size_t retrievalLimit = Settings::general().mConsoleHistoryBufferSize;
// Read the previous session's commands from the file
2023-03-14 19:10:00 +08:00
if (retrievalLimit > 0)
{
2023-03-14 19:10:00 +08:00
std::ifstream historyFile(filePath);
std::string line;
while (std::getline(historyFile, line))
{
// Truncate the list if it exceeds the retrieval limit
if (mCommandHistory.size() >= retrievalLimit)
mCommandHistory.pop_front();
mCommandHistory.push_back(line);
}
historyFile.close();
}
mCurrent = mCommandHistory.end();
try
{
mCommandHistoryFile.exceptions(std::fstream::failbit | std::fstream::badbit);
mCommandHistoryFile.open(filePath, std::ios_base::trunc);
// Update the history file
for (const auto& histObj : mCommandHistory)
mCommandHistoryFile << histObj << std::endl;
mCommandHistoryFile.close();
mCommandHistoryFile.open(filePath, std::ios_base::app);
}
catch (const std::ios_base::failure& e)
{
Log(Debug::Error) << "Error: Failed to write to console history file " << filePath << " : " << e.what()
<< " : " << std::generic_category().message(errno);
}
}
2010-07-21 10:08:38 +02:00
}