#include "console.hpp" #include #include #include #include #include #include "../mwscript/extensions.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" namespace MWGui { class ConsoleInterpreterContext : public MWScript::InterpreterContext { Console& mConsole; public: ConsoleInterpreterContext (Console& console, MWWorld::Ptr reference); virtual void report (const std::string& message); }; ConsoleInterpreterContext::ConsoleInterpreterContext (Console& console, MWWorld::Ptr reference) : MWScript::InterpreterContext ( reference.isEmpty() ? 0 : &reference.getRefData().getLocals(), reference), mConsole (console) {} void ConsoleInterpreterContext::report (const std::string& message) { mConsole.printOK (message); } bool Console::compile (const std::string& cmd, Compiler::Output& output) { try { ErrorHandler::reset(); std::istringstream input (cmd + '\n'); Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions()); Compiler::LineParser parser (*this, mCompilerContext, output.getLocals(), output.getLiterals(), output.getCode(), true); scanner.scan (parser); return isGood(); } catch (const Compiler::SourceException&) { // error has already been reported via error handler } catch (const std::exception& error) { printError (std::string ("Error: ") + error.what()); } return false; } void Console::report (const std::string& message, const Compiler::TokenLoc& loc, Type type) { std::ostringstream error; error << "column " << loc.mColumn << " (" << loc.mLiteral << "):"; printError (error.str()); printError ((type==ErrorMessage ? "error: " : "warning: ") + message); } void Console::report (const std::string& message, Type type) { printError ((type==ErrorMessage ? "error: " : "warning: ") + message); } void Console::listNames() { if (mNames.empty()) { // keywords std::istringstream input (""); Compiler::Scanner scanner (*this, input, mCompilerContext.getExtensions()); scanner.listKeywords (mNames); // identifier const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); for (MWWorld::ESMStore::iterator it = store.begin(); it != store.end(); ++it) { it->second->listIdentifier (mNames); } // exterior cell names aren't technically identifiers, but since the COC function accepts them, // we should list them too for (MWWorld::Store::iterator it = store.get().extBegin(); it != store.get().extEnd(); ++it) { if (!it->mName.empty()) mNames.push_back(it->mName); } // sort std::sort (mNames.begin(), mNames.end()); // remove duplicates mNames.erase( std::unique( mNames.begin(), mNames.end() ), mNames.end() ); } } Console::Console(int w, int h, bool consoleOnlyScripts) : WindowBase("openmw_console.layout"), mCompilerContext (MWScript::CompilerContext::Type_Console), mConsoleOnlyScripts (consoleOnlyScripts) { setCoord(10,10, w-10, h/2); getWidget(mCommandLine, "edit_Command"); getWidget(mHistory, "list_History"); // Set up the command line box mCommandLine->eventEditSelectAccept += newDelegate(this, &Console::acceptCommand); mCommandLine->eventKeyButtonPressed += newDelegate(this, &Console::keyPress); // Set up the log window mHistory->setOverflowToTheLeft(true); // compiler Compiler::registerExtensions (mExtensions, mConsoleOnlyScripts); mCompilerContext.setExtensions (&mExtensions); } void Console::onOpen() { // Give keyboard focus to the combo box whenever the console is // turned on MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } void Console::setFont(const std::string &fntName) { mHistory->setFontName(fntName); mCommandLine->setFontName(fntName); } void Console::print(const std::string &msg, const std::string& color) { mHistory->addText(color + MyGUI::TextIterator::toTagsString(msg)); } void Console::printOK(const std::string &msg) { print(msg + "\n", "#FF00FF"); } void Console::printError(const std::string &msg) { print(msg + "\n", "#FF2222"); } void Console::execute (const std::string& command) { // Log the command print("> " + command + "\n"); Compiler::Locals locals; Compiler::Output output (locals); if (compile (command + "\n", output)) { try { ConsoleInterpreterContext interpreterContext (*this, mPtr); /* Start of tes3mp addition Mark this InterpreterContext as having a CONSOLE context, so that packets sent by the Interpreter can have their origin determined by serverside scripts */ interpreterContext.setContextType(Interpreter::Context::CONSOLE); /* End of tes3mp addition */ Interpreter::Interpreter interpreter; MWScript::installOpcodes (interpreter, mConsoleOnlyScripts); std::vector code; output.getCode (code); interpreter.run (&code[0], code.size(), interpreterContext); } catch (const std::exception& error) { printError (std::string ("Error: ") + error.what()); } } } void Console::executeFile (const std::string& path) { namespace bfs = boost::filesystem; bfs::ifstream stream ((bfs::path(path))); if (!stream.is_open()) printError ("failed to open file: " + path); else { std::string line; while (std::getline (stream, line)) execute (line); } } void Console::clear() { resetReference(); } void Console::keyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char) { if( key == MyGUI::KeyCode::Tab) { std::vector matches; listNames(); std::string oldCaption = mCommandLine->getCaption(); std::string newCaption = complete( mCommandLine->getOnlyText(), matches ); mCommandLine->setCaption(newCaption); // List candidates if repeatedly pressing tab if (oldCaption == newCaption && !matches.empty()) { int i = 0; printOK(""); for(std::vector::iterator it=matches.begin(); it < matches.end(); ++it,++i ) { printOK( *it ); if( i == 50 ) break; } } } if(mCommandHistory.empty()) return; // 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(); if(mCurrent != mCommandHistory.begin()) { --mCurrent; mCommandLine->setCaption(*mCurrent); } } else if(key == MyGUI::KeyCode::ArrowDown) { if(mCurrent != mCommandHistory.end()) { ++mCurrent; if(mCurrent != mCommandHistory.end()) mCommandLine->setCaption(*mCurrent); else // Restore the edit string mCommandLine->setCaption(mEditString); } } } void Console::acceptCommand(MyGUI::EditBox* _sender) { const std::string &cm = mCommandLine->getOnlyText(); 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); mCurrent = mCommandHistory.end(); mEditString.clear(); // 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(""); execute (cm); } std::string Console::complete( std::string input, std::vector &matches ) { 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? */ if( tmp.find('"') != std::string::npos ) { int numquotes=0; 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; 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; 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::vector::iterator it=mNames.begin(); it < mNames.end();++it) { bool string_different=false; /* Is the string shorter than the input string? If yes skip it. */ if( (*it).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=(*it).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(*it); } /* 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*/ if( ( matches.front().find(' ') != std::string::npos ) ) { if( !has_front_quote ) output.append(std::string("\"")); return output.append(matches.front() + std::string("\" ")); } else if( has_front_quote ) { return output.append(matches.front() + std::string("\" ")); } else { 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::vector::iterator it=matches.begin(); it < matches.end();++it) { if( Misc::StringUtils::toLower((*it)[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::onResChange(int width, int height) { setCoord(10,10, width-10, height/2); } void Console::setSelectedObject(const MWWorld::Ptr& object) { if (!object.isEmpty()) { if (object == mPtr) { setTitle("#{sConsoleTitle}"); mPtr=MWWorld::Ptr(); } else { /* Start of tes3mp change (major) Display the selected object's refNum and mpNum alongside its refId in the title of the console window, for easier debugging of almost everything */ setTitle("#{sConsoleTitle} (" + object.getCellRef().getRefId() + ", " + std::to_string(object.getCellRef().getRefNum().mIndex) + "-" + std::to_string(object.getCellRef().getMpNum()) + ")"); /* End of tes3mp change (major) */ mPtr = object; } // User clicked on an object. Restore focus to the console command line. MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } else { setTitle("#{sConsoleTitle}"); mPtr = MWWorld::Ptr(); } } /* Start of tes3mp addition Allow the direct setting of a console's Ptr, without the assumption that an object was clicked and that key focus should be restored to the console window, for console commands executed via server scripts */ void Console::setPtr(const MWWorld::Ptr& object) { mPtr = object; } /* End of tes3mp addition */ void Console::onReferenceUnavailable() { setSelectedObject(MWWorld::Ptr()); } void Console::resetReference() { ReferenceInterface::resetReference(); setSelectedObject(MWWorld::Ptr()); } }