├── LICENSE ├── README.md ├── buildtestpuclua.sh ├── include └── LuaConsole │ ├── LuaConsoleExport.hpp │ ├── LuaConsoleModel.hpp │ ├── LuaPointerOwner.hpp │ ├── LuaSFMLConsoleInput.hpp │ └── LuaSFMLConsoleView.hpp ├── luaconsoleinit.lua ├── src └── LuaConsole │ ├── LuaCompletion.cpp │ ├── LuaCompletion.hpp │ ├── LuaConsoleModel.cpp │ ├── LuaHeader.hpp │ ├── LuaSFMLConsoleInput.cpp │ └── LuaSFMLConsoleView.cpp └── sshot └── sshot0.png /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Błażej Dariusz Roszkowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BLuaConsole 2 | 3 | ![sshot0](sshot/sshot0.png) 4 | 5 | This is a simple but powerful terminal to use with the [Lua](http://www.lua.org) language. 6 | 7 | The main classes behind all of the functionality except for input and rendering use only C++98 features and a common subset of Lua 5.3, 5.2 and 5.1 APIs. 8 | 9 | There is an implementation of input and rendering for SFML 2.x provided in this repo - one class for each of these tasks, four file total. 10 | 11 | ### Demo 12 | There is a well commented demo (including a compiled exe) of basic usage and using pprint for pretty printing (and more screenshots) at [LuaConsoleProgram](https://github.com/FRex/LuaConsoleProgram). 13 | 14 | ## Features: 15 | * Easy to interface with any font rendering and key input 16 | * Works with both Lua 5.3, 5.2 and 5.1, including LuaJIT 17 | * (Optionally) Loads and runs an init script from luaconsoleinit.lua 18 | * (Optionally) Loads and saves commands history from luaconsolehistory.txt 19 | * Allows loading and saving commands history from plaintext file or setting each line directly (for custom filesystems etc.) 20 | * Allows completing or hinting possible completions based on what is in the prompt line and in the Lua state currently 21 | * Automatically checks if entered chunk of code is not complete and catches lines entered from prompt untill a full chunk is ready, just like standalone commandline Lua does 22 | * Allows colorful text in console for different kinds of messages and comes with sane defaults for errors, code, hints, etc. 23 | * Allows echoing to console, including colored text: both colored per line and colored per character 24 | * Exports a single 'echo()' function, that echos single string in default echo color, to state it is attached to 25 | * Puts itself into the registry table, using a pointer to private global int as light userdata key, and provides a way to get pointer to itself (or null if it's not in this Lua state or was reset to another one already) in a typesafe way 26 | * Well commented out API and code 27 | * Pretty printing hook for the eval part 28 | 29 | See the LuaConsoleModel.hpp and the comments above each function of the API for full list of features. 30 | 31 | ### Feedback 32 | 1. Forum thread on SFML forum: [link](http://en.sfml-dev.org/forums/index.php?topic=15962.0) 33 | 2. Creating an issue in this repo. 34 | 35 | ### Key config 36 | These are the key controls for SFML input but they are inspired by bash, gdb, etc. so they are a good set of keys and shortcuts to use both in this implementation and in future ones. There is nothing stopping you from changing them or writing an input class that allows customizing the key bindings. It's actually very easy to do that. 37 | 38 | * Characters - put characters in the prompt line 39 | * Backspace - delete characters before cursor 40 | * Delete - delete characters under cursor 41 | * Enter - send code from prompt 42 | * Left - move one character to left 43 | * Right - move one character to right 44 | * End - move cursor to end of prompt line 45 | * Home - move cursor to begining of prompt line 46 | * Up - move one line back in history 47 | * Down - move one line forward in history 48 | * Tab - trigger code completion 49 | * Ctrl + Left - jump a word left 50 | * Ctrl + Right - jump a word left 51 | * Ctrl + Up - scroll up 52 | * Ctrl + Down - scroll down 53 | * Ctrl + PageUp - scroll up one page 54 | * Ctrl + PageDown - scroll down one page 55 | * Ctrl + Home - scroll to first line 56 | * Ctrl + End - scroll to last line 57 | 58 | ### Licensing 59 | It's licensed under MIT license, see LICENSE file. 60 | -------------------------------------------------------------------------------- /buildtestpuclua.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LUAVERSIONS=' 4 | 5.1.5 5 | 5.2.4 6 | 5.3.4 7 | ' 8 | 9 | if [ $# -ne 1 ] 10 | then 11 | echo "Launch in LuaConsole repo root dir to try to" 12 | echo "download PUC-Rio Lua and build console with it." 13 | echo "Usage: $0 luaversion" 14 | echo "Available versions: $LUAVERSIONS" 15 | exit 1 16 | fi 17 | 18 | PICKEDVER="$1" 19 | echo "$LUAVERSIONS" | grep -qe "^${PICKEDVER}$" 20 | 21 | if [ $? -ne 0 ] 22 | then 23 | echo "No such version" 24 | echo "Available versions: $LUAVERSIONS" 25 | exit 2 26 | fi 27 | 28 | LUATGZURL="https://www.lua.org/ftp/lua-$PICKEDVER.tar.gz" 29 | LUATGZFILE="lua-$PICKEDVER.tgz" 30 | curl -s "$LUATGZURL" > "$LUATGZFILE" 31 | 32 | if [ $? -ne 0 ] 33 | then 34 | echo "Download failed: $LUATGZURL" 35 | exit 3 36 | fi 37 | 38 | echo "Download OK: $LUATGZURL" 39 | 40 | LUADIR=$(tar --exclude '*/*' -tf "$LUATGZFILE") 41 | rm -rf "$LUADIR" 42 | tar -xf "$LUATGZFILE" 43 | cd "$LUADIR" 44 | make ansi -sj 3 || make c89 -sj 3 #5.3 renamed ansi to c89 45 | echo "Lua make returned: $?" 46 | echo ' 47 | extern "C" { 48 | #include "lua.h" 49 | #include "lualib.h" 50 | #include "lauxlib.h" 51 | } 52 | ' > 'src/lua.hpp' 53 | cd .. 54 | 55 | CONSOLEEXENAME="console-$PICKEDVER" 56 | g++ examples/*.cpp -Iinclude -Isrc -I"${LUADIR}/src/" src/LuaConsole/*.cpp -lsfml-system -lsfml-window -lsfml-graphics "${LUADIR}/src/liblua.a" -o "$CONSOLEEXENAME" 57 | echo "Buidling returned: $?" 58 | -------------------------------------------------------------------------------- /include/LuaConsole/LuaConsoleExport.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LUACONSOLEEXPORT_HPP 2 | #define LUACONSOLEEXPORT_HPP 3 | 4 | #ifdef LUACONSOLESTATICLIB 5 | #define LUACONSOLEAPI 6 | #else 7 | #ifdef _WIN32 8 | #ifdef LUACONSOLE_EXPORTS 9 | #define LUACONSOLEAPI __declspec(dllexport) 10 | #else 11 | #define LUACONSOLEAPI __declspec(dllimport) 12 | #endif 13 | #else 14 | #define LUACONSOLEAPI 15 | #endif 16 | #endif //LUACONSOLESTATICLIB 17 | 18 | #endif //LUACONSOLEEXPORT_HPP 19 | -------------------------------------------------------------------------------- /include/LuaConsole/LuaConsoleModel.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LuaConsoleModel.hpp 3 | * Author: frex 4 | * 5 | * Created on July 19, 2014, 11:34 PM 6 | */ 7 | 8 | #ifndef LUACONSOLEMODEL_HPP 9 | #define LUACONSOLEMODEL_HPP 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | struct lua_State; 18 | 19 | namespace blua { 20 | 21 | //type of callback function, use this as hooks to implement own history, etc. 22 | //void pointer is user data that you pass when setting the hook, it can be null 23 | //will not be touched at all, just passed to you along with 'this' of calling LuaConsoleModel 24 | class LuaConsoleModel; 25 | typedef void (*CallbackFunc)(LuaConsoleModel*, void*); 26 | 27 | 28 | //type used to pass color of lines, both internally and in one of public echo functions 29 | typedef std::basic_string ColorString; 30 | 31 | 32 | namespace priv { 33 | 34 | //internal structure to hold line of text and line of assigned colors 35 | 36 | class LUACONSOLEAPI ColoredLine 37 | { 38 | public: 39 | std::string Text; 40 | ColorString Color; 41 | 42 | void resizeColorToFitText(unsigned fill) 43 | { 44 | Color.resize(Text.size(), fill); 45 | } 46 | 47 | }; 48 | 49 | } //priv 50 | 51 | 52 | //constants to move by to get to end or start of prompt line, for use with home/end keys 53 | const int kCursorHome = -100000; 54 | const int kCursorEnd = 100000; 55 | 56 | //constants to scroll to first or last line in scrollLines 57 | const int kScrollLinesEnd = 1000000000; 58 | const int kScrollLinesBegin = -1000000000; 59 | 60 | //console options passed at construction, they are immutable for lifetime of console 61 | //to simplify code, so - when in doubt - opt out 62 | 63 | enum ECONSOLE_OPTION 64 | { 65 | ECO_HISTORY = 1, //load and save history in plaintext file - luaconsolehistory.txt 66 | ECO_INIT = 2, //load init file - luaconsoleinit.lua 67 | ECO_START_VISIBLE = 4, //start visible, this will likely get overwritten by init and so on 68 | 69 | 70 | //keep last: 71 | ECO_DEFAULT = 7, //do all of these helpful things above 72 | ECO_NONE = 0 //do none of the helpful things, ALL is up to user now 73 | }; 74 | 75 | 76 | //specific purpose colors of the console 77 | 78 | enum ECONSOLE_COLOR 79 | { 80 | ECC_ERROR = 0, //color of lua errors, default red 81 | ECC_HINT = 1, //color of hints, default green 82 | ECC_CODE = 2, //color of code typed into console, default yellow 83 | ECC_ECHO = 3, //color of echo'd text, default white 84 | ECC_PROMPT = 4, //color of the prompt line text, default white 85 | ECC_TITLE = 5, //color of the title, default white 86 | ECC_FRAME = 6, //color of the frame, default darkgrey 87 | ECC_BACKGROUND = 7, //color of the background, default halfcyan - 0x007f7f7f which is half of cyan 88 | ECC_CURSOR = 8, //color of the cursor, default cyan 89 | ECC_EVAL = 9, //color of evals, default darkgrey 90 | ECC_HISTORY = 10, //color of history in comment command, default dark golden rod (0xb8860bff) 91 | 92 | ECONSOLE_COLOR_COUNT //count, keep last 93 | }; 94 | 95 | 96 | //types of console callbacks 97 | 98 | enum ECALLBACK_TYPE 99 | { 100 | //called AFTER new line is in history but BEFORE passing it to lua state 101 | //so even if it crashes, destroys the process, etc. you can get and save it 102 | //use it as hook for own history handling 103 | ECT_NEWHISTORY = 0, 104 | 105 | //called when user uses --quit comment command, see setCommentCommands 106 | //documentation in LuaConsoleModel for more info about these commands 107 | ECT_QUIT, 108 | 109 | ECALLBACK_TYPE_COUNT //count, keep last 110 | }; 111 | 112 | //for use instead of bool in moveCursorOneWord 113 | 114 | enum EMOVE_DIRECTION 115 | { 116 | EMD_RIGHT, 117 | EMD_LEFT 118 | }; 119 | 120 | //possible outcomes of parsing a line 121 | 122 | enum ELINE_PARSE_RESULT 123 | { 124 | ELPR_OK = 0, //it parsed and ran 125 | ELPR_MORE, //it parsed but it's not a complete chunk yet 126 | ELPR_PARSE_ERROR, //it didn't parse 127 | ELPR_RUNTIME_ERROR, //it parsed but didn't run 128 | ELPR_NO_LUA //lua state ptr is not set 129 | }; 130 | 131 | //a single UTF-32 character* in console, with its' color 132 | //*so far it can only be an ascii char but this might change 133 | 134 | class LUACONSOLEAPI ScreenCell 135 | { 136 | public: 137 | unsigned Char; 138 | unsigned Color; 139 | 140 | }; 141 | 142 | class LUACONSOLEAPI LuaConsoleModel 143 | { 144 | public: 145 | //GENERAL API:////////////////////////////////////////////////////////////// 146 | 147 | //get the console attached to this state, might return null, relatively typesafe 148 | static LuaConsoleModel * getFromRegistry(lua_State * L); 149 | 150 | //as above, but lua errors instead of returning null 151 | static LuaConsoleModel * checkFromRegistry(lua_State * L); 152 | 153 | //ctor, does NOT allocate lua state, takes a bitflag of ECONSOLE_OPTION values 154 | LuaConsoleModel(unsigned options = ECO_DEFAULT); 155 | 156 | //dtor 157 | ~LuaConsoleModel(); 158 | 159 | //sets the lua state and attaches console to it, unattaches from last state 160 | //if not called or called with null console will print errors on usage 161 | void setL(lua_State * L); 162 | 163 | //print line to console in the default echo color (see ECC_ECHO) 164 | //this is accessible from lua too, as 'echo' and takes single string there too 165 | void echo(const std::string& str); 166 | 167 | //print line to console with all characters in the specified color 168 | void echoColored(const std::string& str, unsigned textcolor); 169 | 170 | //print line to console, with each character having a color set individually 171 | //if there are more they are ignored, if there are less, the rest are printed 172 | //in default echo color (see ECC_ECHO) 173 | void echoLine(const std::string& str, const ColorString& colors); 174 | 175 | //get the title, by default console has empty ("") title 176 | const std::string& getTitle() const; 177 | 178 | //set title of console, itll appear in top frame bar and be in ECC_TITLE color 179 | void setTitle(const std::string& title); 180 | 181 | //set callback for certain events, see ECALLBACK_TYPE enum for more info 182 | //void * data is userdata and will not be touched, only passed to your func 183 | //use this to implement own history, etc. 184 | //pass null func to disable the specific callback 185 | //only one callback per type is available 186 | //new one overwrites the old one 187 | //by default there are no callbacks 188 | void setCallback(ECALLBACK_TYPE type, CallbackFunc func, void * data); 189 | 190 | //set whether or not console is visible, invisible console shouldnt process 191 | //any input and shouldn't draw 192 | void setVisible(bool visible); 193 | 194 | //check whether or not the console is visible 195 | bool isVisible() const; 196 | 197 | //toggle visibility of the console, equivalent to setVisible(!isVisible()) 198 | void toggleVisible(); 199 | 200 | //set one of the console colors, see ECONSOLE_COLOR enum for more info 201 | void setColor(ECONSOLE_COLOR which, unsigned color); 202 | 203 | //get one of the console colors 204 | unsigned getColor(ECONSOLE_COLOR which) const; 205 | 206 | //set whether or not pressing enter with prompt empty runs the last line again 207 | //by default this is enabled, if you want to send empty line with this enabled 208 | //then send a single space instead 209 | void setEnterRepeatLast(bool eer); 210 | 211 | //check whether or not pressing enter with empty prompt repeats last history command 212 | bool getEnterRepeatLast() const; 213 | 214 | //set new size of history buffer 215 | void setHistorySize(std::size_t newsize); 216 | 217 | //set certain item of history buffer to a value 218 | void setHistoryItem(std::size_t index, const std::string& item); 219 | 220 | //get current size of history buffer 221 | std::size_t getHistorySize() const; 222 | 223 | //get content of certain item of history buffer 224 | const std::string& getHistoryItem(std::size_t index) const; 225 | 226 | //load history from a file, this uses ifstream and loads last history size 227 | //lines from the file if you want own fs handling, different behaviour, etc. 228 | //then use get/set history item/size 229 | bool loadHistoryFromFile(const std::string& filename); 230 | 231 | //save history to file, this uses ofstream and either appends all lines to 232 | //specified file or overwrites the file and fills it with the history lines 233 | //bool append controls whether to append or overwrite 234 | void saveHistoryToFile(const std::string& filename, bool append = true); 235 | 236 | //set characters that jumping over words won't consider part of a word 237 | //see moveCursorOneWord for explanation about what exactly happens then 238 | //PS: this is set to a sane default for lua 239 | void setSkipCharacters(const std::string& chars); 240 | 241 | //get characters that are not considered parts of words when jumping words 242 | //see moveCursorOneWord for explanation about what exactly happens then 243 | //PS: this is set to a sane default for lua 244 | const std::string& getSkipCharacters() const; 245 | 246 | //set whether or not all code typed by hand should print its' return 247 | //values to console, this is on by default 248 | //it means that where there are return values (even if they are nil) it 249 | //will print them to console with ECC_EVAL color 250 | //(when there are no values (0) there is no printing done) 251 | void setPrintEval(bool print); 252 | 253 | //check whether or not evals print to console 254 | bool getPrintEval() const; 255 | 256 | //this will always try evalute with "return " added to the string first 257 | //to allow returning values easily without typing return in user code 258 | void setAddReturn(bool add); 259 | 260 | //check whether or not we try add a return 261 | bool getAddReturn() const; 262 | 263 | //when enabled this feature will make it so some special comments entered 264 | //alone into prompt when the console is not mid chunk will triggers special 265 | //operations like displaying history or clearing the screen 266 | void setCommentCommands(bool enable); 267 | 268 | //check whether or not comment commands are enabled 269 | bool getCommentCommands() const; 270 | 271 | //clear the console screen space messages (but not the history) 272 | void clearScreen(); 273 | 274 | //set the prettifier for eval printing to function on top of the stack 275 | //if the value on top is a function its the new prettifier function 276 | //if the value on top is a nil then prettifying is disabled 277 | //otherwise (gettop(L) == 0 or non nil/function on top) does nothing 278 | //if you want to use a __call then wrap the call in a function 279 | //if there is an error in the prettifier function then it will be 280 | //displayed and the original value printed instead 281 | //the prettifier function will be passed one argument and only first of 282 | //its return values is used and it can be of any type 283 | //this function doesn't affect print eval, see getPrintEval/setPrintEval for that 284 | void setPrintEvalPrettifier(lua_State * L); 285 | 286 | //get the eval prettifier function (or nil if it's not set) 287 | void getPrintEvalPrettifier(lua_State * L) const; 288 | 289 | //API FOR CONTROLLER://///////////////////////////////////////////////////// 290 | 291 | //move cursor by given amount of characters, itll be clipped to [0,lastlinesize] 292 | //automatically, pass kCursorEnd or kCursorHome to go to lastline or 0 respectively 293 | //use this for Home/End key press and for Left/Right arrow keys 294 | void moveCursor(int move); 295 | 296 | //scroll lines by given amount, this is clipped to never scroll past first 297 | //and last line, use this for ctrl + pageup/pagedown/up/down/home/end 298 | //pass kScrollLinesEnd or kScrollLinesBegin to go as much back/forth as possible 299 | void scrollLines(int amount); 300 | 301 | //move cursor by one word left or right (see EMOVE_DIRECTION) like 302 | //bash (at least KDE and xfce terminals) does, that is: 303 | //when moving left, skip a word and land on its' first char 304 | //when moving right, skip a word and land just after its' last char 305 | //if there is no word to skip then cursor jumps to end(right) or start(left) of prompt line 306 | void moveCursorOneWord(EMOVE_DIRECTION move); 307 | 308 | //change the index of history and set last line to currently selected item 309 | //if index is equal to history size then we set an empty line 310 | //use this (pass -1 and 1) for Up/Down arrows to get bash-like history behavior 311 | void readHistory(int change); 312 | 313 | //send last line to lua state (or print error), incomplete chunks are handled OK too 314 | //it returns whether code chunk ran, had errors in parse/run or is not yet completed 315 | //use this for Enter/Return key press 316 | ELINE_PARSE_RESULT parseLastLine(); 317 | 318 | //add character to last line, use this for normal typing, etc. 319 | void addChar(char c); 320 | 321 | //delete character before cursor, use this for Backspace key 322 | void backspace(); 323 | 324 | //delete character under cursor, use this for Delete key 325 | void del(); 326 | 327 | //try and complete code or print hints (or errors) based on what is available 328 | //in current lua state and what is in the last prompt line, use this for Tab 329 | void tryComplete(); 330 | 331 | 332 | //API FOR VIEW:///////////////////////////////////////////////////////////// 333 | 334 | //get dirtyness, if this is different (usually it's more, but just testing for 335 | //difference is better) than last time checked, then something has changed 336 | unsigned getDirtyness()const; 337 | 338 | //get current position of cursor in the prompt line 339 | int getCurPos() const; 340 | 341 | //get the screen buffer, its' size is width x height, width columns, height lines, first width 342 | //elements are first line, next width are second line, etc. 343 | //there are no newlines in it so dont look for them 344 | const ScreenCell * getScreenBuffer() const; 345 | 346 | //set console size, must be at least 10x4 347 | void setConsoleSize(unsigned width, unsigned height); 348 | 349 | //get console width and height 350 | unsigned getConsoleWidth() const; 351 | unsigned getConsoleHeight() const; 352 | 353 | private: 354 | ScreenCell * getCells(int x, int y) const; 355 | const std::string& getWideMsg(int index) const; 356 | const ColorString& getWideColor(int index) const; 357 | void updateBuffer() const; 358 | void printLuaStackInColor(int first, int last, unsigned color); 359 | bool tryEval(bool addreturn); 360 | void checkSpecialComments(); 361 | void ensureCurInView(); 362 | bool applyPrettifier(int index); 363 | 364 | CallbackFunc m_callbackfuncs[ECALLBACK_TYPE_COUNT]; //callbakcs called on certain events 365 | void * m_callbackdata[ECALLBACK_TYPE_COUNT]; //data for callbacks 366 | unsigned m_dirtyness; //our current dirtyness 367 | mutable unsigned m_lastupdate; //when was last update of buffer 368 | std::string m_lastline; //the prompt line, colorless 369 | int m_cur; //position of cursor in last line 370 | std::string m_buffcmd; //command buffer for uncompleted chunks 371 | lua_State * L; //lua state we are talking with 372 | std::vector m_history; //the history buffer 373 | int m_hindex; //index in history 374 | std::vector m_msg; //actual messages that got echoed 375 | std::vector m_widemsg; //messages adjusted/split to fit width of console 376 | const priv::ColoredLine m_empty; //empty line constant 377 | const unsigned m_options; //options passed at construction 378 | bool m_visible; //are we visible? 379 | unsigned m_colors[ECONSOLE_COLOR_COUNT]; //colors of various kinds of text 380 | bool m_emptyenterrepeat; //should pressing enter with empty prompt repeat last line? 381 | mutable std::vector m_screen; //screen buff = chars && colors 382 | std::string m_title; //title of the console 383 | LuaPointerOwner m_luaptr; //the lua pointer of ours that handles two way deletions 384 | std::string m_skipchars; //characters we don't consider part of a word when jumping over words 385 | int m_firstmsg; //offset of first message - for scrolling 386 | bool m_printeval; //do we print returned values of handtyped scripts? 387 | bool m_addreturn; //do we try to add 'return ' to code to try return evaluated expressions 388 | std::string m_savedlastline; //last line saved when scrolling history 389 | bool m_commentcommands; //do we use special comments in prompt to trigger console commands 390 | unsigned m_lastlineoffset; //offset of last line when it's longer than term width 391 | unsigned m_width; 392 | unsigned m_height; 393 | 394 | }; 395 | 396 | } //blua 397 | 398 | #endif /* LUACONSOLEMODEL_HPP */ 399 | 400 | -------------------------------------------------------------------------------- /include/LuaConsole/LuaPointerOwner.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LuaPointerOwner.hpp 3 | * Author: frex 4 | * 5 | * Created on July 23, 2014, 3:44 AM 6 | */ 7 | 8 | #ifndef LUAPOINTEROWNER_HPP 9 | #define LUAPOINTEROWNER_HPP 10 | 11 | #include 12 | 13 | namespace blua { 14 | 15 | //this class can be inherited or used as a member variable 16 | //it will null out the pointer it is point to at destruction 17 | //useful to prevent accessing invalid pointer that was pushed to lua 18 | //it is noncopyable as that would cause errors 19 | // 20 | //it's used internally by lua console model but it's not in 'priv' namespace 21 | //because it can just as well be used by anyone, since it has no console 22 | //specific features/constraints 23 | //since it requires using full userdata and makes class uncopyable 24 | //it's not suitable for small light classes 25 | 26 | template class LuaPointerOwner 27 | { 28 | public: 29 | 30 | //default ctor, with null 'unarmed' pointer 31 | 32 | LuaPointerOwner() : m_luaptr(0x0) { } 33 | 34 | //destructor, will null out the pointer if we have one so anyone holding 35 | //the full userdata and trying to see the ptr in it will see null 36 | 37 | ~LuaPointerOwner() 38 | { 39 | clearLuaPointer(); 40 | } 41 | 42 | //set lua ptr, use this with full userdata in which you store the pointer itself 43 | //use as such: setLuaPointer(static_cast(lua_newuserdata(L, sizeof (T*))) 44 | 45 | void setLuaPointer(T ** ptr) 46 | { 47 | clearLuaPointer(); 48 | m_luaptr = ptr; 49 | } 50 | 51 | //method to call when __gc is ran, to not try null out memory no longer in use 52 | //later when we ourselves are destroyed 53 | 54 | void disarmLuaPointer() 55 | { 56 | m_luaptr = 0x0; 57 | } 58 | 59 | //method to call when we want to 'unlink' the instance we are managing 60 | //after that, anyone inspecting the ptr in our userdata will see null and can 61 | //react (or crash...) easily thanks to that 62 | 63 | void clearLuaPointer() 64 | { 65 | if(m_luaptr) 66 | (*m_luaptr) = 0x0; 67 | } 68 | 69 | private: 70 | //delete copy and assignment to forbid copying (leads to errors) 71 | LuaPointerOwner(const LuaPointerOwner& other); 72 | LuaPointerOwner& operator=(const LuaPointerOwner& other); 73 | 74 | T ** m_luaptr; 75 | 76 | }; 77 | 78 | } //blua 79 | 80 | #endif /* LUAPOINTEROWNER_HPP */ 81 | 82 | -------------------------------------------------------------------------------- /include/LuaConsole/LuaSFMLConsoleInput.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LuaSFMLConsoleInput.hpp 3 | * Author: frex 4 | * 5 | * Created on September 20, 2014, 7:07 PM 6 | */ 7 | 8 | #ifndef LUASFMLCONSOLEINPUT_HPP 9 | #define LUASFMLCONSOLEINPUT_HPP 10 | 11 | #include 12 | #include 13 | 14 | namespace blua{ 15 | 16 | class LuaConsoleModel; 17 | 18 | class LUACONSOLEAPI LuaSFMLConsoleInput 19 | { 20 | public: 21 | LuaSFMLConsoleInput(LuaConsoleModel * model = 0x0); 22 | void setModel(LuaConsoleModel * model); 23 | LuaConsoleModel * getModel() const; 24 | bool handleEvent(sf::Event event); 25 | void setToggleKey(sf::Keyboard::Key key); 26 | sf::Keyboard::Key getToggleKey() const; 27 | 28 | private: 29 | void handleKeyEvent(sf::Event eve); 30 | void handleCtrlKeyEvent(sf::Event eve); 31 | 32 | LuaConsoleModel * m_model; 33 | sf::Keyboard::Key m_togglekey; 34 | 35 | }; 36 | 37 | } //blua 38 | 39 | #endif /* LUASFMLCONSOLEINPUT_HPP */ 40 | 41 | -------------------------------------------------------------------------------- /include/LuaConsole/LuaSFMLConsoleView.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LuaSFMLConsoleView.hpp 3 | * Author: frex 4 | * 5 | * Created on July 19, 2014, 11:34 PM 6 | */ 7 | 8 | #ifndef LUASFMLCONSOLEVIEW_HPP 9 | #define LUASFMLCONSOLEVIEW_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace blua { 17 | 18 | class LuaConsoleModel; 19 | 20 | class LUACONSOLEAPI LuaSFMLConsoleView : public sf::Drawable 21 | { 22 | public: 23 | LuaSFMLConsoleView(bool defaultfont = true); 24 | ~LuaSFMLConsoleView(); 25 | void setFont(const sf::Font * font); 26 | const sf::Font * getFont() const; 27 | void geoRebuild(const LuaConsoleModel * model); //keep last 28 | 29 | private: 30 | void draw(sf::RenderTarget& target, sf::RenderStates states) const; 31 | 32 | unsigned m_lastdirtyness; //for caching/laziness 33 | const sf::Font * m_font; 34 | bool m_ownfont; 35 | sf::VertexArray m_vertices; //vertices with font 36 | bool m_defaultfont; 37 | bool m_modelvisible; 38 | 39 | }; 40 | 41 | } //blua 42 | 43 | #endif /* LUASFMLCONSOLEVIEW_HPP */ 44 | 45 | -------------------------------------------------------------------------------- /luaconsoleinit.lua: -------------------------------------------------------------------------------- 1 | local oldecho = echo --we need old echo to send string we format in echo 2 | function echo(...) --echo takes one str arg, we define better one here 3 | local arg = {...} 4 | local count = select('#', ...) 5 | for i=1, count do arg[i] = tostring(arg[i]) end --we need strs for concat 6 | oldecho(table.concat(arg, ' ')) 7 | end 8 | 9 | function printf(format, ...) --just for convinence 10 | oldecho(format:format(...)) 11 | end 12 | 13 | return true --return false or nothing to not show console on init 14 | -------------------------------------------------------------------------------- /src/LuaConsole/LuaCompletion.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace blua { 7 | namespace priv { 8 | //TODO: these still need some work to be more accurate 9 | 10 | //this fetches the compacted string of table names so 11 | //for "for i=1, vars . blabla : lul" 12 | //it will return "vars.blabla.lul" and so on 13 | //so the main table pushing code just needs to split the str on . and not worry 14 | //about anything else 15 | 16 | static std::string cleanTableList(const std::string& str) 17 | { 18 | std::string ret; 19 | 20 | //remove spaces before and after dots, change : to . 21 | bool gotdot = false; 22 | std::size_t whitespacestart = 0u; 23 | bool gotwhite = false; 24 | for(std::size_t i = 0u; i < str.size(); ++i) 25 | { 26 | const char c = str[i] == ':'?'.':str[i]; 27 | if(!gotwhite && c == ' ') 28 | { 29 | gotwhite = true; 30 | whitespacestart = i; 31 | } 32 | if(c == '.' && gotwhite) 33 | { 34 | for(std::size_t j = 0u; j < (i - whitespacestart); ++j) 35 | ret.erase(--ret.end()); 36 | } 37 | if(c != ' ') gotwhite = false; 38 | if(c != ' ' || !gotdot) ret += c; 39 | if(c == '.') gotdot = true; 40 | if(c != '.' && c != ' ') gotdot = false; 41 | } 42 | 43 | //change 'special' chars to spaces (needed below) 44 | const std::string specials = "()[]{}\"'+-=/*^%#~,"; 45 | for(std::size_t i = 0u; i < specials.size(); ++i) 46 | std::replace(ret.begin(), ret.end(), specials[i], ' '); 47 | 48 | //return everything starting at last space(exclusive) 49 | ret = ret.substr(ret.find_last_of(' ') + 1u); 50 | // std::printf("got '%s' out of '%s'\n", ret.c_str(), str.c_str()); 51 | return ret; 52 | } 53 | 54 | void prepareHints(lua_State * L, std::string str, std::string& last) 55 | { 56 | str = cleanTableList(str); //fetch clean list of tables separated by . 57 | 58 | //split that list by . 59 | std::vector tables; //list of tables except final one 60 | int begin = 0; 61 | for(std::size_t i = 0u; i < str.size(); ++i) 62 | { 63 | if(str[i] == '.') 64 | { 65 | tables.push_back(str.substr(begin, i - begin)); 66 | begin = i + 1; //? 67 | } 68 | } 69 | last = str.substr(begin); //name of last table that we must hint to complete, might be empty 70 | 71 | bla_lua_pushglobaltable(L); 72 | for(std::size_t i = 0u; i < tables.size(); ++i) 73 | { 74 | if(lua_type(L, -1) != LUA_TTABLE) //if not a table, try fetch meta now 75 | { 76 | lua_getmetatable(L, -1); 77 | } 78 | 79 | if(lua_type(L, -1) != LUA_TTABLE && !luaL_getmetafield(L, -1, "__index") && !lua_getmetatable(L, -1)) break; 80 | if(lua_type(L, -1) != LUA_TTABLE) break; //no 81 | lua_pushlstring(L, tables[i].c_str(), tables[i].size()); 82 | lua_gettable(L, -2); 83 | } 84 | } 85 | 86 | static bool collectHintsRecurse(lua_State * L, std::vector& possible, const std::string& last, bool usehidden, unsigned left) 87 | { 88 | if(left == 0u) 89 | return true; 90 | 91 | const bool skipunderscore = last.empty() && !usehidden; 92 | 93 | //collect hints from table currently on top 94 | lua_pushnil(L); //prepare iteration 95 | while(lua_next(L, -2)) 96 | { 97 | std::size_t keylen; 98 | const char * key; 99 | bool match = true; 100 | lua_pop(L, 1); //pop the value - we don't care for it yet 101 | lua_pushvalue(L, -1); //need this to not confuse lua_next 102 | key = lua_tolstring(L, -1, &keylen); 103 | 104 | if(last.size() > keylen) //cant match start of this str, we are longer 105 | { 106 | lua_pop(L, 1); 107 | continue; 108 | } 109 | 110 | for(std::size_t i = 0u; i < last.size(); ++i) //compare up to our len = see if we are prefix of that key 111 | if(key[i] != last[i]) match = false; 112 | 113 | if(match && (!skipunderscore || key[0] != '_')) 114 | possible.push_back(key); 115 | 116 | lua_pop(L, 1); //pop our str copy and let lua_next see the key itself 117 | } 118 | 119 | //see if the table has index itself, for chaining metas 120 | if(luaL_getmetafield(L, -1, "__index")) 121 | { 122 | if(lua_istable(L, -1)) 123 | return collectHintsRecurse(L, possible, last, usehidden, left - 1); 124 | 125 | lua_pop(L, 1); //pop it if it's not table 126 | } 127 | lua_pop(L, 1); //pop the table itself 128 | return true; 129 | } 130 | 131 | //replace value at top of the stack with __index TABLE from metatable 132 | 133 | static bool tryReplaceWithMetaIndex(lua_State * L) 134 | { 135 | if(!luaL_getmetafield(L, -1, "__index")) 136 | return false; 137 | 138 | if(lua_type(L, -1) != LUA_TTABLE) 139 | { 140 | lua_pop(L, 2); //pop both value and table 141 | return false; 142 | } 143 | 144 | lua_insert(L, -2); //move our table under the value 145 | lua_pop(L, 1); //and pop the value itself 146 | return true; 147 | } 148 | 149 | bool collectHints(lua_State * L, std::vector& possible, const std::string& last, bool usehidden) 150 | { 151 | if(lua_type(L, -1) != LUA_TTABLE && !tryReplaceWithMetaIndex(L)) 152 | return false; 153 | 154 | //it's a table, so just collect on it 155 | return collectHintsRecurse(L, possible, last, usehidden, 10u); 156 | } 157 | 158 | std::string commonPrefix(const std::vector& possible) 159 | { 160 | std::string ret; 161 | std::size_t maxindex = 1000000000u; 162 | for(std::size_t i = 0u; i < possible.size(); ++i) 163 | maxindex = std::min(maxindex, possible[i].size()); 164 | 165 | for(std::size_t checking = 0u; checking < maxindex; ++checking) 166 | { 167 | const char c = possible[0u][checking]; 168 | for(std::size_t i = 1u; i < possible.size(); ++i) 169 | { 170 | if(c != possible[i][checking]) 171 | { 172 | checking = maxindex; 173 | break; 174 | } 175 | } 176 | if(checking != maxindex) 177 | ret += c; 178 | } 179 | return ret; 180 | } 181 | 182 | } //priv 183 | } //blua 184 | -------------------------------------------------------------------------------- /src/LuaConsole/LuaCompletion.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LuaCompletion.hpp 3 | * Author: frex 4 | * 5 | * Created on July 20, 2014, 1:12 PM 6 | */ 7 | 8 | #ifndef LUACOMPLETION_HPP 9 | #define LUACOMPLETION_HPP 10 | 11 | #include 12 | #include 13 | 14 | struct lua_State; 15 | 16 | namespace blua { 17 | namespace priv { 18 | 19 | //splits the 'str' string, fill the 'last' string with part user needs completed 20 | //and pushes right table/value on top of the stack 21 | void prepareHints(lua_State * L, std::string str, std::string& last); 22 | 23 | //collect hints for 'last' from value at top of the stack L and push them to 'possible' 24 | //it also recurses through metatable chains (with no danger of infinite loop) 25 | //usehidden decides if _names can be hints for empty string 26 | bool collectHints(lua_State * L, std::vector& possible, const std::string& last, bool usehidden); 27 | 28 | //get the common prefix of all strings 29 | std::string commonPrefix(const std::vector& possible); 30 | 31 | } //priv 32 | } //blua 33 | 34 | #endif /* LUACOMPLETION_HPP */ 35 | 36 | -------------------------------------------------------------------------------- /src/LuaConsole/LuaConsoleModel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace blua { 10 | 11 | //name of metatable/type in registry that our console is using 12 | const char * const kMetaname = "bla_LuaConsole"; 13 | 14 | //frame/cursor/special unicode chars: 15 | const unsigned kVerticalBarChar = 0x2550u; 16 | const unsigned kHorizontalBarChar = 0x2551u; 17 | const unsigned kULFrameChar = 0x2554u; 18 | const unsigned kBLFrameChar = 0x255au; 19 | const unsigned kBRFrameChar = 0x255du; 20 | const unsigned kURFrameChar = 0x2557u; 21 | 22 | //how many history items to keep by default 23 | const int kDefaultHistorySize = 100; 24 | 25 | //how many messages(not wide) to keep, this is for internal workings of console mostly 26 | const int kMessagesKeptCount = 3000; 27 | 28 | const char * const kHistoryFilename = "luaconsolehistory.txt"; 29 | const char * const kInitFilename = "luaconsoleinit.lua"; 30 | 31 | //default skipable chars, very sane for lua and similar to bash 32 | const char * const kDefaultSkipChars = " ,.;()[]{}:'\""; 33 | 34 | //check if c is in skipchars 35 | 36 | inline static bool isSkipChar(char c, const char * skipchars) 37 | { 38 | return 0x0 != std::strchr(skipchars, c); 39 | } 40 | 41 | static int findFirstSkipCharAfterNonskip(const std::string& line, const char *skips, int iter, int start) 42 | { 43 | bool gotnonskip = false; 44 | for(int i = start + iter; 0 <= i && i < static_cast(line.size()); i += iter) 45 | { 46 | if(gotnonskip) 47 | { 48 | if(isSkipChar(line[i], skips)) 49 | return i; 50 | } 51 | else 52 | { 53 | if(!isSkipChar(line[i], skips)) 54 | gotnonskip = true; 55 | } 56 | }//for 57 | return -1; 58 | } 59 | 60 | //this is the best way to do it when we assume 1 console per lua state 61 | //plus I don't wanna assume that registry is okay with lua refs 62 | static char varConsoleLightKey; 63 | static char varPrettyPrintFunctionLightKey; 64 | 65 | inline static void * consoleLightKey() 66 | { 67 | return &varConsoleLightKey; 68 | } 69 | 70 | inline static void * prettyPrintFunctionLightKey() 71 | { 72 | return &varPrettyPrintFunctionLightKey; 73 | } 74 | 75 | LuaConsoleModel * LuaConsoleModel::getFromRegistry(lua_State* L) 76 | { 77 | //get our console from the registry 78 | lua_pushlightuserdata(L, consoleLightKey()); 79 | lua_gettable(L, LUA_REGISTRYINDEX); 80 | 81 | //if we fail we return null 82 | LuaConsoleModel * ret = 0x0; 83 | 84 | //if we got userdata and it has a metatable 85 | if(lua_type(L, -1) == LUA_TUSERDATA && lua_getmetatable(L, -1)) 86 | { 87 | //we now got the ud's table pushed (since getmetatable returned true) 88 | //so look up the _real_ console table in registry 89 | lua_getfield(L, LUA_REGISTRYINDEX, kMetaname); 90 | 91 | //if it's equal to what our userdata has, we are 99.9% good to go 92 | //(the 0.01% is for _insanely_ broken state or someone super actively 93 | //*sabotaging* us, we can deal with neither) 94 | if(bla_lua_equal(L, -1, -2)) 95 | { 96 | //get the console itself 97 | ret = *static_cast(lua_touserdata(L, -3)); 98 | } 99 | 100 | //pop both tables, ours and regs 101 | lua_pop(L, 2); 102 | } 103 | 104 | lua_pop(L, 1); //pop the console itself 105 | return ret; 106 | } 107 | 108 | LuaConsoleModel* LuaConsoleModel::checkFromRegistry(lua_State* L) 109 | { 110 | LuaConsoleModel * ret = getFromRegistry(L); 111 | if(!ret) 112 | luaL_error(L, "LuaConsole not attached to this state"); 113 | 114 | return ret; 115 | } 116 | 117 | LuaConsoleModel::LuaConsoleModel(unsigned options) : 118 | m_dirtyness(1u), //because 0u is what view starts at 119 | m_lastupdate(0u), 120 | m_cur(1), 121 | L(0x0), 122 | m_empty(), 123 | m_options(options), 124 | m_visible(options & ECO_START_VISIBLE), 125 | m_emptyenterrepeat(true), 126 | m_skipchars(kDefaultSkipChars), 127 | m_firstmsg(0), 128 | m_printeval(true), 129 | m_addreturn(true), 130 | m_commentcommands(true), 131 | m_lastlineoffset(0u) 132 | { 133 | setConsoleSize(80u, 24u); 134 | 135 | m_colors[ECC_ERROR] = 0xff0000ff; 136 | m_colors[ECC_HINT] = 0x00ff00ff; 137 | m_colors[ECC_CODE] = 0xffff00ff; 138 | m_colors[ECC_ECHO] = 0xffffffff; 139 | m_colors[ECC_PROMPT] = 0xffffffff; 140 | m_colors[ECC_TITLE] = 0xffffffff; 141 | m_colors[ECC_FRAME] = 0xa9a9a9ff; 142 | m_colors[ECC_BACKGROUND] = 0x007f7f7f; 143 | m_colors[ECC_CURSOR] = 0x00ffffff; 144 | m_colors[ECC_EVAL] = 0xa9a9a9ff; 145 | m_colors[ECC_HISTORY] = 0xb8860bff; 146 | 147 | //always give sane history size default, even if not asked for reading it 148 | setHistorySize(kDefaultHistorySize); 149 | 150 | //read history from file if desired 151 | if(m_options & ECO_HISTORY) 152 | loadHistoryFromFile(kHistoryFilename); 153 | 154 | m_hindex = m_history.size(); 155 | 156 | for(int i = 0; i < ECALLBACK_TYPE_COUNT; ++i) 157 | { 158 | m_callbackdata[i] = 0x0; 159 | m_callbackfuncs[i] = 0x0; 160 | } 161 | } 162 | 163 | LuaConsoleModel::~LuaConsoleModel() 164 | { 165 | //save history to file if desired, append 166 | if(m_options & ECO_HISTORY) 167 | saveHistoryToFile(kHistoryFilename, false); 168 | } 169 | 170 | void LuaConsoleModel::moveCursor(int move) 171 | { 172 | const int oldcursor = m_cur; 173 | m_cur += move; 174 | m_cur = std::max(m_cur, 1); 175 | m_cur = std::min(m_lastline.size() + 1, m_cur); 176 | ensureCurInView(); 177 | 178 | //only bump dirtyness if cursor actually moved 179 | if(oldcursor != m_cur) 180 | ++m_dirtyness; 181 | } 182 | 183 | void LuaConsoleModel::scrollLines(int amount) 184 | { 185 | m_firstmsg += amount; 186 | 187 | //below code ensures we go no further than last or first line 188 | m_firstmsg = std::max(m_firstmsg, (m_height - 3) - static_cast(m_widemsg.size())); 189 | m_firstmsg = std::min(m_firstmsg, 0); 190 | ++m_dirtyness; 191 | } 192 | 193 | void LuaConsoleModel::moveCursorOneWord(EMOVE_DIRECTION move) 194 | { 195 | const int iter = (move == EMD_LEFT) ? -1 : 1; 196 | 197 | //see below about why we do 'm_cur - 1' not just 'm_cur' 198 | int targ = findFirstSkipCharAfterNonskip(m_lastline, m_skipchars.c_str(), iter, m_cur - 1); 199 | 200 | //if target is -1 we failed, so just move to home or end, as needed 201 | if(targ == -1) 202 | { 203 | moveCursor(move == EMD_LEFT ? kCursorHome : kCursorEnd); 204 | } 205 | else 206 | { 207 | //add 1 if we moved left, we wanna be on first char of word we just skipped 208 | if(move == EMD_LEFT) 209 | ++targ; 210 | 211 | //unfortunate 'hax' of m_Cur being in 'screen space', not 'line space' 212 | //so 0th char in line is 1 in m_cur, so we add 1 213 | m_cur = targ + 1; 214 | ensureCurInView(); 215 | ++m_dirtyness; 216 | } 217 | } 218 | 219 | void LuaConsoleModel::readHistory(int change) 220 | { 221 | const bool waspromp = static_cast(m_hindex) == m_history.size(); 222 | 223 | m_hindex += change; 224 | m_hindex = std::max(m_hindex, 0); 225 | m_hindex = std::min(m_hindex, m_history.size()); 226 | 227 | if(static_cast(m_hindex) == m_history.size()) 228 | { 229 | //if we came back from history, swap last line in 230 | if(!waspromp) 231 | std::swap(m_lastline, m_savedlastline); 232 | 233 | m_lastlineoffset = 0u; 234 | moveCursor(kCursorEnd); 235 | } 236 | else 237 | { 238 | //if we just entered history, swap out last line 239 | if(waspromp) 240 | std::swap(m_lastline, m_savedlastline); 241 | 242 | m_lastline = m_history[m_hindex]; 243 | m_lastlineoffset = 0u; 244 | moveCursor(kCursorEnd); 245 | } 246 | 247 | ++m_dirtyness; 248 | } 249 | 250 | void LuaConsoleModel::printLuaStackInColor(int first, int last, unsigned color) 251 | { 252 | std::stringstream ss; 253 | 254 | for(int i = first; i <= last; ++i) 255 | { 256 | const bool prettified = applyPrettifier(i); 257 | const char * strpad = prettified ? "" : "'"; 258 | //we pretty print just the way tostring lua call does 259 | switch(lua_type(L, i)) 260 | { 261 | case LUA_TNUMBER: 262 | ss << lua_tostring(L, i); 263 | break; 264 | case LUA_TSTRING: 265 | ss << strpad << lua_tostring(L, i) << strpad; 266 | break; 267 | case LUA_TBOOLEAN: 268 | ss << (lua_toboolean(L, i) ? "true" : "false"); 269 | break; 270 | case LUA_TNIL: 271 | ss << "nil"; 272 | break; 273 | default: 274 | ss << luaL_typename(L, i) << ": " << lua_topointer(L, i); 275 | break; 276 | } //switch lua type i 277 | ss << ' '; 278 | }//for i = first to last 279 | echoColored(ss.str(), color); 280 | } 281 | 282 | //NOTE: we can't do dostring here because we would confuse runtime and parse 283 | //errors then, in example: 284 | //"a .. 10" when a is nil will fail at runtime with return added because it 285 | //tries to concat nil but it DOES parse, so the error should be about nil a 286 | //but if we did dostring we would go on (since return added version failed) 287 | //and try "a .. 10" itself which would would fail to parse so the error would 288 | //be parse error which is wrong, because we want concat error from return 289 | //added version then 290 | 291 | bool LuaConsoleModel::tryEval(bool addreturn) 292 | { 293 | if(addreturn) 294 | { 295 | const std::string code = "return " + m_buffcmd; 296 | if(BLA_LUA_OK == luaL_loadstring(L, code.c_str())) 297 | { 298 | return true; 299 | } 300 | else 301 | { 302 | lua_pop(L, 1); //pop error - it doesn't matter with added return 303 | return false; 304 | } 305 | } //if addreturn 306 | else 307 | { 308 | return BLA_LUA_OK == luaL_loadstring(L, m_buffcmd.c_str()); 309 | } 310 | } 311 | 312 | //avoid problems when error gets called with non string 313 | static inline std::string adjustErrStr(lua_State * L, int idx) 314 | { 315 | const int t = lua_type(L, idx); 316 | if(t == LUA_TSTRING) 317 | return lua_tostring(L, idx); 318 | 319 | return std::string("(non string error value - ") + lua_typename(L, t) + ")"; 320 | } 321 | 322 | ELINE_PARSE_RESULT LuaConsoleModel::parseLastLine() 323 | { 324 | ELINE_PARSE_RESULT ret = ELPR_OK; 325 | if(m_lastline.size() == 0u && m_emptyenterrepeat && !m_history.empty()) 326 | m_lastline = m_history.back(); 327 | 328 | echoColored(m_lastline, m_colors[ECC_CODE]); 329 | 330 | //only push history item if last item already in there is not the same 331 | //always push first and then remove, other way around wouldn't be safe 332 | //just make our history always be x lines and do it that way instead of old 333 | //way, no need for 'maxhistory' variable or anything 334 | if(!m_history.empty() && m_history.back() != m_lastline) 335 | { 336 | m_history.push_back(m_lastline); 337 | m_history.erase(m_history.begin()); 338 | } 339 | 340 | //to 'cancel out' previous history browsing 341 | m_hindex = m_history.size(); 342 | 343 | //call before running, in case crash, exit etc. 344 | if(m_callbackfuncs[ECT_NEWHISTORY]) 345 | m_callbackfuncs[ECT_NEWHISTORY](this, m_callbackdata[ECT_NEWHISTORY]); 346 | 347 | //if we are not midchunk then this code is fresh 348 | const bool freshcode = m_buffcmd.empty(); 349 | m_buffcmd += m_lastline; 350 | m_buffcmd += '\n'; 351 | 352 | if(L) 353 | { 354 | const int oldtop = lua_gettop(L); 355 | bool evalok; 356 | if(m_addreturn) 357 | { 358 | evalok = tryEval(true) || tryEval(false); 359 | } 360 | else 361 | { 362 | evalok = tryEval(false); 363 | } 364 | 365 | if(evalok && BLA_LUA_OK == lua_pcall(L, 0, LUA_MULTRET, 0)) 366 | { 367 | m_buffcmd.clear(); //worked & done - clear it 368 | if(m_printeval && oldtop != lua_gettop(L)) 369 | printLuaStackInColor(oldtop + 1, lua_gettop(L), m_colors[ECC_EVAL]); 370 | 371 | lua_settop(L, oldtop); 372 | } 373 | else 374 | { 375 | const std::string err = adjustErrStr(L, -1); 376 | ret = ELPR_MORE; 377 | if(evalok || !blua::incompleteChunkError(err.c_str(), err.length())) 378 | { 379 | m_buffcmd.clear(); //failed normally - clear it 380 | echoColored(err, m_colors[ECC_ERROR]); 381 | ret = evalok ? ELPR_RUNTIME_ERROR : ELPR_PARSE_ERROR; 382 | } 383 | lua_pop(L, 1); 384 | }//got an error, real or /incomplete chunk one 385 | }//L is not null 386 | else 387 | { 388 | //say kindly we are kind of in trouble 389 | echoColored("Lua state pointer is NULL, commands have no effect", m_colors[ECC_ERROR]); 390 | ret = ELPR_NO_LUA; 391 | }//L is null 392 | 393 | //if this line was freshcode and cmd commands feature is enabled, check it 394 | if(freshcode && m_commentcommands) 395 | checkSpecialComments(); 396 | 397 | m_lastline.clear(); 398 | m_cur = 1; 399 | m_lastlineoffset = 0u; 400 | ++m_dirtyness; 401 | return ret; 402 | } 403 | 404 | void LuaConsoleModel::checkSpecialComments() 405 | { 406 | if(m_lastline == "--clear") 407 | clearScreen(); 408 | 409 | if(m_lastline == "--history") 410 | for(std::size_t i = 0u; i < m_history.size(); ++i) 411 | echoColored(m_history[i], m_colors[ECC_HISTORY]); 412 | 413 | if(m_lastline == "--quit") 414 | if(m_callbackfuncs[ECT_QUIT]) 415 | m_callbackfuncs[ECT_QUIT](this, m_callbackdata[ECT_QUIT]); 416 | } 417 | 418 | void LuaConsoleModel::addChar(char c) 419 | { 420 | if(c < ' ' || c >= 127) 421 | return; 422 | 423 | m_lastline.insert(m_lastline.begin() + (m_cur - 1), c); 424 | ++m_cur; 425 | ensureCurInView(); 426 | ++m_dirtyness; 427 | } 428 | 429 | void LuaConsoleModel::backspace() 430 | { 431 | if(m_cur > 1) 432 | { 433 | --m_cur; 434 | m_lastline.erase(m_cur - 1, 1); 435 | ensureCurInView(); 436 | ++m_dirtyness; 437 | } 438 | } 439 | 440 | void LuaConsoleModel::del() 441 | { 442 | m_lastline.erase(m_cur - 1, 1); 443 | ++m_dirtyness; 444 | } 445 | 446 | unsigned LuaConsoleModel::getDirtyness() const 447 | { 448 | return m_dirtyness; 449 | } 450 | 451 | //split str on newlines and to fit 'width' length and push to given vector (if not null) 452 | //returns how many messages str was split into 453 | 454 | static std::size_t pushWideMessages(const priv::ColoredLine& str, std::vector* widemsgs, unsigned width) 455 | { 456 | std::size_t ret = 0u; 457 | std::size_t charcount = 0u; 458 | std::size_t start = 0u; 459 | 460 | //push pieces of str if they go over width or if we encounter a newline 461 | for(std::size_t i = 0u; i < str.Text.size(); ++i) 462 | { 463 | ++charcount; 464 | if(str.Text[i] == '\n' || charcount >= width) 465 | { 466 | if(str.Text[i] == '\n') --charcount; 467 | if(widemsgs) 468 | { 469 | priv::ColoredLine line; 470 | line.Text = str.Text.substr(start, charcount); 471 | line.Color = str.Color.substr(start, charcount); 472 | widemsgs->push_back(line); 473 | } 474 | ++ret; 475 | start = i + 1u; 476 | charcount = 0u; 477 | } 478 | } 479 | 480 | //push last piece if loop didn't 481 | if(charcount != 0u) 482 | { 483 | if(widemsgs) 484 | { 485 | priv::ColoredLine line; 486 | line.Text = str.Text.substr(start, charcount); 487 | line.Color = str.Color.substr(start, charcount); 488 | widemsgs->push_back(line); 489 | } 490 | ++ret; 491 | } 492 | return ret; 493 | } 494 | 495 | void LuaConsoleModel::echo(const std::string& str) 496 | { 497 | echoColored(str, m_colors[ECC_ECHO]); 498 | } 499 | 500 | void LuaConsoleModel::echoColored(const std::string& str, unsigned textcolor) 501 | { 502 | const ColorString color(str.size(), textcolor); 503 | echoLine(str, color); 504 | } 505 | 506 | void LuaConsoleModel::echoLine(const std::string& str, const ColorString& colors) 507 | { 508 | if(str.empty()) return echoLine(" ", colors); //workaround for a bug?? 509 | 510 | priv::ColoredLine line; 511 | line.Text = str; 512 | line.Color = colors; 513 | line.resizeColorToFitText(m_colors[ECC_ECHO]); 514 | 515 | m_msg.push_back(line); 516 | 517 | pushWideMessages(line, &m_widemsg, m_width - 2); 518 | 519 | if(m_msg.size() > kMessagesKeptCount) 520 | { 521 | const std::size_t msgs = pushWideMessages(*m_msg.begin(), 0x0, m_width - 2); 522 | m_msg.erase(m_msg.begin()); 523 | m_widemsg.erase(m_widemsg.begin(), m_widemsg.begin() + msgs); 524 | } 525 | 526 | scrollLines(kScrollLinesEnd); //make this conditional? 527 | ++m_dirtyness; 528 | } 529 | 530 | const std::string& LuaConsoleModel::getWideMsg(int index) const 531 | { 532 | if(index < 0) index = m_widemsg.size() + index; 533 | index += m_firstmsg; 534 | if(index < 0 || static_cast(index) >= m_widemsg.size()) return m_empty.Text; 535 | 536 | return m_widemsg[index].Text; 537 | } 538 | 539 | const ColorString& LuaConsoleModel::getWideColor(int index) const 540 | { 541 | if(index < 0) index = m_widemsg.size() + index; 542 | index += m_firstmsg; 543 | if(index < 0 || static_cast(index) >= m_widemsg.size()) return m_empty.Color; 544 | 545 | return m_widemsg[index].Color; 546 | } 547 | 548 | int LuaConsoleModel::getCurPos() const 549 | { 550 | return m_cur - m_lastlineoffset; 551 | } 552 | 553 | static int ConsoleModel_echo(lua_State * L) 554 | { 555 | LuaConsoleModel * m = *static_cast(lua_touserdata(L, lua_upvalueindex(1))); 556 | if(m) 557 | m->echo(luaL_checkstring(L, 1)); 558 | 559 | return 0; 560 | } 561 | 562 | static int ConsoleModel_gc(lua_State * L) 563 | { 564 | LuaConsoleModel * m = *static_cast(lua_touserdata(L, 1)); 565 | //rationale: assume if our instance in registry and all references of it 566 | //got destroyed we are not able to or supposed to use the same L anymore 567 | if(m) 568 | m->setL(0x0); 569 | 570 | return 0; 571 | } 572 | 573 | void LuaConsoleModel::setL(lua_State * L) 574 | { 575 | //if not in gc then kill our prettifier function register key too 576 | if(this->L) 577 | { 578 | lua_pushnil(this->L); 579 | setPrintEvalPrettifier(this->L); 580 | } 581 | 582 | //TODO: add support for more L's being linked/using echos at once?? 583 | this->L = L; 584 | 585 | //clear and disarm, so the state that used us before is no longer able to 586 | m_luaptr.clearLuaPointer(); 587 | m_luaptr.disarmLuaPointer(); 588 | 589 | if(!L) 590 | return; 591 | 592 | LuaConsoleModel ** ptr = static_cast(lua_newuserdata(L, sizeof(LuaConsoleModel*))); 593 | (*ptr) = this; 594 | m_luaptr.setLuaPointer(ptr); 595 | 596 | //make and set new metatable with gc in it 597 | 598 | luaL_newmetatable(L, kMetaname); //table 599 | lua_pushliteral(L, "__gc"); 600 | lua_pushcfunction(L, &ConsoleModel_gc); 601 | lua_settable(L, -3); //table[gc]=ConsoleModel_gc 602 | lua_setmetatable(L, -2); 603 | 604 | lua_pushlightuserdata(L, consoleLightKey()); 605 | lua_pushvalue(L, -2); 606 | lua_settable(L, LUA_REGISTRYINDEX); 607 | 608 | lua_pushcclosure(L, &ConsoleModel_echo, 1); 609 | lua_setglobal(L, "echo"); 610 | 611 | //set eval prettifier to nil before ECO_INIT in case it resets it 612 | lua_pushnil(L); 613 | setPrintEvalPrettifier(L); 614 | 615 | if(m_options & ECO_INIT) 616 | { 617 | if(luaL_loadfile(L, kInitFilename) || lua_pcall(L, 0, 1, 0)) 618 | { 619 | echoColored(lua_tostring(L, -1), m_colors[ECC_ERROR]); 620 | lua_pop(L, 1); //pop the error message 621 | m_visible = true; //crapped up init is important so show console right away 622 | } 623 | else 624 | { 625 | m_visible = lua_toboolean(L, -1); 626 | lua_pop(L, 1); //pop that boolean 627 | } 628 | } 629 | } 630 | 631 | void LuaConsoleModel::tryComplete() 632 | { 633 | if(!L) 634 | { 635 | //be nice and say there won't be any completions 636 | echoColored("Lua state pointer is NULL, no completion available", m_colors[ECC_ERROR]); 637 | return; 638 | } 639 | 640 | std::vector possible; //possible matches 641 | std::string last; 642 | 643 | //last line part before and after the cursor 644 | const std::string lastbeg = m_lastline.substr(0, m_cur - 1); 645 | const std::string lastend = m_lastline.substr(m_cur - 1); 646 | priv::prepareHints(L, lastbeg, last); 647 | if(!priv::collectHints(L, possible, last, false)) 648 | { 649 | //if no hints, assume we want _any_ completion and use global table 650 | bla_lua_pushglobaltable(L); 651 | priv::collectHints(L, possible, last, false); 652 | } 653 | 654 | lua_settop(L, 0); //pop all trash we put on the stack 655 | 656 | if(possible.size() > 1u) 657 | { 658 | const std::string commonprefix = priv::commonPrefix(possible); 659 | //if no common prefix or if we already have it or more 660 | if(commonprefix.empty() || commonprefix.size() <= last.size()) 661 | { 662 | std::string msg = possible[0]; 663 | for(std::size_t i = 1u; i < possible.size(); ++i) 664 | msg += " " + possible[i]; 665 | 666 | echoColored(msg, m_colors[ECC_HINT]); 667 | } 668 | else 669 | { 670 | const std::string added = commonprefix.substr(last.size()); 671 | m_lastline = lastbeg + added + lastend; 672 | ++m_dirtyness; 673 | moveCursor(added.size()); 674 | } //commonprefix is not empty 675 | } 676 | else if(possible.size() == 1) 677 | { 678 | const std::string added = possible[0].substr(last.size()); 679 | m_lastline = lastbeg + added + lastend; 680 | ++m_dirtyness; 681 | moveCursor(added.size()); 682 | } 683 | } 684 | 685 | void LuaConsoleModel::setCallback(ECALLBACK_TYPE type, CallbackFunc func, void* data) 686 | { 687 | if(type == ECALLBACK_TYPE_COUNT) 688 | return; 689 | 690 | m_callbackfuncs[type] = func; 691 | m_callbackdata[type] = data; 692 | } 693 | 694 | void LuaConsoleModel::setVisible(bool visible) 695 | { 696 | if(m_visible != visible) 697 | ++m_dirtyness; 698 | 699 | m_visible = visible; 700 | } 701 | 702 | bool LuaConsoleModel::isVisible() const 703 | { 704 | return m_visible; 705 | } 706 | 707 | void LuaConsoleModel::toggleVisible() 708 | { 709 | m_visible = !m_visible; 710 | ++m_dirtyness; 711 | } 712 | 713 | void LuaConsoleModel::setColor(ECONSOLE_COLOR which, unsigned color) 714 | { 715 | if(which != ECONSOLE_COLOR_COUNT && m_colors[which] != color) 716 | { 717 | m_colors[which] = color; 718 | ++m_dirtyness; 719 | } 720 | } 721 | 722 | unsigned LuaConsoleModel::getColor(ECONSOLE_COLOR which) const 723 | { 724 | if(which == ECONSOLE_COLOR_COUNT) 725 | return 0xffffffff; 726 | 727 | return m_colors[which]; 728 | } 729 | 730 | void LuaConsoleModel::setEnterRepeatLast(bool eer) 731 | { 732 | m_emptyenterrepeat = eer; 733 | } 734 | 735 | bool LuaConsoleModel::getEnterRepeatLast() const 736 | { 737 | return m_emptyenterrepeat; 738 | } 739 | 740 | ScreenCell * LuaConsoleModel::getCells(int x, int y) const 741 | { 742 | assert(0 < x); 743 | assert(static_cast(x + 1) < m_width); 744 | assert(0 < y); 745 | assert(static_cast(y + 1) < m_height); 746 | return &m_screen[x + m_width * y]; 747 | } 748 | 749 | const ScreenCell * LuaConsoleModel::getScreenBuffer() const 750 | { 751 | updateBuffer(); 752 | assert(!m_screen.empty()); 753 | return &m_screen[0]; 754 | } 755 | 756 | void LuaConsoleModel::setConsoleSize(unsigned width, unsigned height) 757 | { 758 | if(width < 10u || height < 4u) 759 | return; 760 | 761 | if(m_width == width && m_height == height) 762 | return; 763 | 764 | //if width changed then redo wide message 765 | if(m_width != width) 766 | { 767 | m_widemsg.clear(); 768 | for(unsigned i = 0u; i < m_msg.size(); ++i) 769 | pushWideMessages(m_msg[i], &m_widemsg, width - 2); 770 | } 771 | 772 | ++m_dirtyness; 773 | m_width = width; 774 | m_height = height; 775 | m_screen.assign(width * height, ScreenCell()); 776 | 777 | //work space 778 | for(unsigned i = 0; i < m_width * m_height; ++i) 779 | { 780 | m_screen[i].Char = ' '; //0x2588 781 | m_screen[i].Color = 0xffffffff; 782 | } 783 | 784 | //vertical 785 | for(unsigned i = 0; i < m_width; ++i) 786 | { 787 | m_screen[i + m_width * 0].Char = kVerticalBarChar; 788 | m_screen[i + m_width * (m_height - 1)].Char = kVerticalBarChar; 789 | } 790 | 791 | //horizontal 792 | for(unsigned i = 0; i < m_height; ++i) 793 | { 794 | m_screen[0 + m_width * i].Char = kHorizontalBarChar; 795 | m_screen[m_width - 1 + m_width * i].Char = kHorizontalBarChar; 796 | } 797 | 798 | //corners 799 | m_screen[0 + m_width * 0].Char = kULFrameChar; 800 | m_screen[0 + m_width * (m_height - 1)].Char = kBLFrameChar; 801 | m_screen[m_width - 1 + m_width * (m_height - 1)].Char = kBRFrameChar; 802 | m_screen[m_width - 1 + m_width * 0].Char = kURFrameChar; 803 | } 804 | 805 | unsigned LuaConsoleModel::getConsoleWidth() const 806 | { 807 | return m_width; 808 | } 809 | 810 | unsigned LuaConsoleModel::getConsoleHeight() const 811 | { 812 | return m_height; 813 | } 814 | 815 | void LuaConsoleModel::updateBuffer() const 816 | { 817 | if(m_lastupdate == m_dirtyness) 818 | return; 819 | 820 | m_lastupdate = m_dirtyness; 821 | 822 | //first we clear out the top bar 823 | for(unsigned i = 1; i < m_width - 1; ++i) 824 | m_screen[i].Char = kVerticalBarChar; 825 | 826 | //then we ensure frame is all OK colored, since setting title overwrites colors 827 | for(unsigned i = 0; i < m_width; ++i) 828 | { 829 | m_screen[i + 0 * m_width].Color = m_colors[ECC_FRAME]; 830 | m_screen[i + (m_height - 1) * m_width].Color = m_colors[ECC_FRAME]; 831 | } 832 | for(unsigned i = 0; i < m_height - 1; ++i) 833 | { 834 | m_screen[0 + i * m_width].Color = m_colors[ECC_FRAME]; 835 | m_screen[m_width - 1 + i * m_width].Color = m_colors[ECC_FRAME]; 836 | } 837 | 838 | //now we can set the title and its' color 839 | for(int i = 1; i < std::min(m_width - 1, m_title.length() + 1); ++i) 840 | { 841 | m_screen[i].Char = m_title[i - 1]; 842 | m_screen[i].Color = m_colors[ECC_TITLE]; 843 | } 844 | 845 | 846 | for(unsigned i = 1; i < (m_height - 2); ++i) 847 | { 848 | const std::string& l = getWideMsg(i - (m_height - 2)); 849 | const ColorString& c = getWideColor(i - (m_height - 2)); 850 | 851 | ScreenCell * a = getCells(1, i); 852 | 853 | for(unsigned x = 0; x < (m_width - 2); ++x) 854 | { 855 | a[x].Char = ' '; 856 | a[x].Color = 0xffffffff; 857 | } 858 | 859 | for(std::size_t x = 0u; x < l.size(); ++x) 860 | { 861 | a[x].Char = l[x]; 862 | a[x].Color = c[x]; 863 | } 864 | } 865 | 866 | ScreenCell * a = getCells(1, m_height - 2); 867 | 868 | for(unsigned x = 0; x < (m_width - 2); ++x) 869 | { 870 | a[x].Char = ' '; 871 | a[x].Color = m_colors[ECC_PROMPT]; 872 | } 873 | 874 | for(std::size_t x = 0; x < (m_width - 2) && (m_lastlineoffset + x) < m_lastline.size(); ++x) 875 | { 876 | a[x].Char = m_lastline[m_lastlineoffset + x]; 877 | } 878 | } 879 | 880 | const std::string& LuaConsoleModel::getTitle() const 881 | { 882 | return m_title; 883 | } 884 | 885 | void LuaConsoleModel::setTitle(const std::string& title) 886 | { 887 | if(m_title != title) 888 | ++m_dirtyness; 889 | 890 | m_title = title; 891 | } 892 | 893 | void LuaConsoleModel::setHistorySize(std::size_t newsize) 894 | { 895 | m_history.resize(newsize); 896 | } 897 | 898 | void LuaConsoleModel::setHistoryItem(std::size_t index, const std::string& item) 899 | { 900 | if(index < m_history.size()) 901 | m_history[index] = item; 902 | } 903 | 904 | std::size_t LuaConsoleModel::getHistorySize() const 905 | { 906 | return m_history.size(); 907 | } 908 | 909 | const std::string& LuaConsoleModel::getHistoryItem(std::size_t index) const 910 | { 911 | if(index < m_history.size()) 912 | return m_history[index]; 913 | 914 | return m_empty.Text; 915 | } 916 | 917 | bool LuaConsoleModel::loadHistoryFromFile(const std::string& filename) 918 | { 919 | std::ifstream file(filename.c_str()); 920 | if(!file.is_open()) 921 | return false; 922 | 923 | if(getHistorySize() == 0u) 924 | return false; 925 | 926 | std::vector buf(getHistorySize()); 927 | std::string line; 928 | std::size_t iter = 0u; 929 | 930 | while(std::getline(file, line)) 931 | { 932 | buf[iter % getHistorySize()] = line; 933 | ++iter; 934 | } 935 | 936 | if(iter == 0u) 937 | return false; 938 | 939 | for(std::size_t i = 0; i < getHistorySize(); ++i) 940 | m_history[i] = buf[(iter + i) % getHistorySize()]; 941 | 942 | return true; 943 | } 944 | 945 | void LuaConsoleModel::saveHistoryToFile(const std::string& filename, bool append) 946 | { 947 | std::ofstream file(filename.c_str(), append ? std::ios::app : std::ios::trunc); 948 | for(std::size_t i = 0u; i < getHistorySize(); ++i) 949 | file << getHistoryItem(i) << std::endl; 950 | } 951 | 952 | void LuaConsoleModel::setSkipCharacters(const std::string& chars) 953 | { 954 | m_skipchars = chars; 955 | } 956 | 957 | const std::string& LuaConsoleModel::getSkipCharacters() const 958 | { 959 | return m_skipchars; 960 | } 961 | 962 | void LuaConsoleModel::setPrintEval(bool print) 963 | { 964 | m_printeval = print; 965 | } 966 | 967 | bool LuaConsoleModel::getPrintEval() const 968 | { 969 | return m_printeval; 970 | } 971 | 972 | void LuaConsoleModel::setAddReturn(bool add) 973 | { 974 | m_addreturn = add; 975 | } 976 | 977 | bool LuaConsoleModel::getAddReturn() const 978 | { 979 | return m_addreturn; 980 | } 981 | 982 | void LuaConsoleModel::setCommentCommands(bool enable) 983 | { 984 | m_commentcommands = enable; 985 | } 986 | 987 | bool LuaConsoleModel::getCommentCommands() const 988 | { 989 | return m_commentcommands; 990 | } 991 | 992 | void LuaConsoleModel::clearScreen() 993 | { 994 | m_firstmsg = 0; 995 | m_msg.clear(); 996 | m_widemsg.clear(); 997 | m_dirtyness += 1; 998 | } 999 | 1000 | void LuaConsoleModel::setPrintEvalPrettifier(lua_State * L) 1001 | { 1002 | if(lua_gettop(L) == 0) 1003 | return; 1004 | 1005 | const int t = lua_type(L, -1); 1006 | if(!(t == LUA_TFUNCTION || t == LUA_TNIL)) 1007 | return; 1008 | 1009 | lua_pushlightuserdata(L, prettyPrintFunctionLightKey()); 1010 | lua_insert(L, -2); 1011 | lua_settable(L, LUA_REGISTRYINDEX); 1012 | } 1013 | 1014 | void LuaConsoleModel::getPrintEvalPrettifier(lua_State * L) const 1015 | { 1016 | lua_pushlightuserdata(L, prettyPrintFunctionLightKey()); 1017 | lua_gettable(L, LUA_REGISTRYINDEX); 1018 | } 1019 | 1020 | void LuaConsoleModel::ensureCurInView() 1021 | { 1022 | if(static_cast(m_cur) <= m_lastlineoffset) 1023 | { 1024 | m_lastlineoffset = m_cur - 1; 1025 | } 1026 | while(static_cast(m_cur) > m_lastlineoffset + m_width - 2) 1027 | { 1028 | ++m_lastlineoffset; 1029 | } 1030 | } 1031 | 1032 | bool LuaConsoleModel::applyPrettifier(int index) 1033 | { 1034 | getPrintEvalPrettifier(L); 1035 | if(lua_type(L, -1) == LUA_TNIL) 1036 | { 1037 | lua_pop(L, 1); 1038 | return false; 1039 | } 1040 | 1041 | assert(lua_type(L, -1) == LUA_TFUNCTION); 1042 | lua_pushvalue(L, index); 1043 | if(BLA_LUA_OK == lua_pcall(L, 1, 1, 0)) 1044 | { 1045 | lua_remove(L, index); 1046 | lua_insert(L, index); 1047 | return true; 1048 | } 1049 | else 1050 | { 1051 | echoColored(adjustErrStr(L, -1), m_colors[ECC_ERROR]); 1052 | lua_pop(L, 1); 1053 | return false; 1054 | } 1055 | return true; 1056 | } 1057 | 1058 | } //blua 1059 | -------------------------------------------------------------------------------- /src/LuaConsole/LuaHeader.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LuaHeader.hpp 3 | * Author: frex 4 | * 5 | * Created on August 9, 2014, 6:35 PM 6 | */ 7 | 8 | #ifndef LUAHEADER_HPP 9 | #define LUAHEADER_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | //this header is to allow both lua jit (based on 5.1), 5.1 and 5.2 be 16 | //used with the console by defining some very minor things 17 | //NOTE: 18 | //since we define things that might be defined (ie. equal, which is deprecated, 19 | //do missing by default but may be present) we prepend bla_ to them 20 | //this also lets code of model know which functions are 'common' between 51 and 21 | //52 and 53 and which are 'patchwork' to allow this code sharing, 22 | //incompleteChunkError is exempt from that, since it's in our lua namespace, 23 | //it's not a symbol from either of Lua versions and it ended up here just 24 | //because it depends on the version of lua as well 25 | 26 | 27 | //---------------------------------------------------------------------- 28 | //LUA 5.2 AND LUA 5.3 -------------------------------------------------- 29 | 30 | #if ((LUA_VERSION_NUM == 502) || (LUA_VERSION_NUM == 503)) 31 | 32 | namespace blua { 33 | 34 | inline bool incompleteChunkError(const char * err, std::size_t len) 35 | { 36 | return err && (std::strlen(err) >= 5u) && (0 == std::strcmp(err + len - 5u, "")); 37 | } 38 | 39 | } //blua 40 | 41 | //5.2 deprecated lua_equal so we use lua_compare to reimplement it: 42 | 43 | inline int bla_lua_equal(lua_State * L, int index1, int index2) 44 | { 45 | return lua_compare(L, index1, index2, LUA_OPEQ); 46 | } 47 | 48 | #define bla_lua_pushglobaltable lua_pushglobaltable 49 | 50 | #define BLA_LUA_OK LUA_OK 51 | 52 | #endif //LUA 5.2 or LUA 5.3 53 | 54 | //---------------------------------------------------------------------- 55 | //LUA JIT AND 5.1 ------------------------------------------------------ 56 | #if (LUA_VERSION_NUM == 501) 57 | 58 | namespace blua { 59 | 60 | inline bool incompleteChunkError(const char * err, std::size_t len) 61 | { 62 | return err && (std::strlen(err) >= 7u) && (0 == std::strcmp(err + len - 7u, "''")); 63 | } 64 | 65 | } //blua 66 | 67 | #define bla_lua_equal lua_equal 68 | 69 | //pushglobaltable is missing but easy to fake via old method of getting globals: 70 | #define bla_lua_pushglobaltable(L) (lua_pushvalue((L), LUA_GLOBALSINDEX)) 71 | 72 | //LUA_OK is missing but 0 is assumed to be 'success' value in comments, so: 73 | #define BLA_LUA_OK 0 74 | 75 | #endif //LUA 5.1 76 | 77 | #endif /* LUAHEADER_HPP */ 78 | 79 | -------------------------------------------------------------------------------- /src/LuaConsole/LuaSFMLConsoleInput.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace blua { 5 | 6 | LuaSFMLConsoleInput::LuaSFMLConsoleInput(LuaConsoleModel * model) : 7 | m_model(model), 8 | m_togglekey(sf::Keyboard::Unknown) 9 | {} 10 | 11 | void LuaSFMLConsoleInput::setModel(LuaConsoleModel * model) 12 | { 13 | m_model = model; 14 | } 15 | 16 | LuaConsoleModel * LuaSFMLConsoleInput::getModel() const 17 | { 18 | return m_model; 19 | } 20 | 21 | bool LuaSFMLConsoleInput::handleEvent(sf::Event event) 22 | { 23 | if(!m_model) 24 | return false; 25 | 26 | if(m_togglekey != sf::Keyboard::Unknown && event.type == sf::Event::KeyPressed && event.key.code == m_togglekey) 27 | { 28 | m_model->toggleVisible(); 29 | return true; 30 | } 31 | 32 | if(!m_model->isVisible()) 33 | return false; 34 | 35 | if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::L && event.key.control) 36 | { 37 | m_model->clearScreen(); 38 | return true; 39 | } 40 | 41 | switch(event.type) 42 | { 43 | case sf::Event::KeyPressed: 44 | if(event.key.control) 45 | { 46 | handleCtrlKeyEvent(event); 47 | } 48 | else 49 | { 50 | handleKeyEvent(event); 51 | } 52 | return true; 53 | case sf::Event::TextEntered: 54 | m_model->addChar(static_cast(event.text.unicode)); 55 | return true; 56 | default: 57 | return false; 58 | } //eve.type 59 | return false; 60 | } 61 | 62 | void LuaSFMLConsoleInput::handleKeyEvent(sf::Event event) 63 | { 64 | assert(event.type == sf::Event::KeyPressed); 65 | 66 | switch(event.key.code) 67 | { 68 | case sf::Keyboard::BackSpace: 69 | m_model->backspace(); 70 | break; 71 | case sf::Keyboard::Delete: 72 | m_model->del(); 73 | break; 74 | case sf::Keyboard::Return: 75 | m_model->parseLastLine(); 76 | break; 77 | case sf::Keyboard::Left: 78 | m_model->moveCursor(-1); 79 | break; 80 | case sf::Keyboard::Right: 81 | m_model->moveCursor(1); 82 | break; 83 | case sf::Keyboard::End: 84 | m_model->moveCursor(kCursorEnd); 85 | break; 86 | case sf::Keyboard::Home: 87 | m_model->moveCursor(kCursorHome); 88 | break; 89 | case sf::Keyboard::Up: 90 | m_model->readHistory(-1); 91 | break; 92 | case sf::Keyboard::Down: 93 | m_model->readHistory(1); 94 | break; 95 | case sf::Keyboard::Tab: 96 | m_model->tryComplete(); 97 | break; 98 | default: 99 | //TODO:optionally do not consume all keys? 100 | break; 101 | } 102 | } 103 | 104 | void LuaSFMLConsoleInput::handleCtrlKeyEvent(sf::Event event) 105 | { 106 | assert(event.type == sf::Event::KeyPressed); 107 | 108 | switch(event.key.code) 109 | { 110 | case sf::Keyboard::Left: 111 | m_model->moveCursorOneWord(blua::EMD_LEFT); 112 | break; 113 | case sf::Keyboard::Right: 114 | m_model->moveCursorOneWord(blua::EMD_RIGHT); 115 | break; 116 | case sf::Keyboard::Up: 117 | m_model->scrollLines(-1); 118 | break; 119 | case sf::Keyboard::Down: 120 | m_model->scrollLines(1); 121 | break; 122 | case sf::Keyboard::Home: 123 | m_model->scrollLines(kScrollLinesBegin); 124 | break; 125 | case sf::Keyboard::End: 126 | m_model->scrollLines(kScrollLinesEnd); 127 | break; 128 | case sf::Keyboard::PageUp: 129 | m_model->scrollLines(-static_cast(m_model->getConsoleHeight() - 3)); 130 | break; 131 | case sf::Keyboard::PageDown: 132 | m_model->scrollLines((m_model->getConsoleHeight() - 3)); 133 | break; 134 | default: 135 | //TODO:optionally do not consume all keys? (as above) 136 | break; 137 | } 138 | } 139 | 140 | void LuaSFMLConsoleInput::setToggleKey(sf::Keyboard::Key key) 141 | { 142 | m_togglekey = key; 143 | } 144 | 145 | sf::Keyboard::Key LuaSFMLConsoleInput::getToggleKey() const 146 | { 147 | return m_togglekey; 148 | } 149 | 150 | } //blua 151 | -------------------------------------------------------------------------------- /src/LuaConsole/LuaSFMLConsoleView.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace blua { 6 | 7 | const unsigned kFontSize = 18u; 8 | 9 | const char * const kFontName = "DejaVuSansMono.ttf"; 10 | 11 | static sf::Color toColor(unsigned color) 12 | { 13 | sf::Color ret; 14 | ret.r = (color & 0xff000000) >> 24; 15 | ret.g = (color & 0xff0000) >> 16; 16 | ret.b = (color & 0xff00) >> 8; 17 | ret.a = color & 0xff; 18 | return ret; 19 | } 20 | 21 | LuaSFMLConsoleView::LuaSFMLConsoleView(bool defaultfont) : 22 | m_lastdirtyness(0u), 23 | m_font(0x0), 24 | m_ownfont(false), 25 | m_defaultfont(defaultfont), 26 | m_modelvisible(false) 27 | { 28 | m_vertices.setPrimitiveType(sf::Triangles); 29 | 30 | if(m_defaultfont) 31 | { 32 | sf::Font * dfont = new sf::Font; 33 | dfont->loadFromFile(kFontName); 34 | m_font = dfont; 35 | m_ownfont = true; 36 | } 37 | } 38 | 39 | LuaSFMLConsoleView::~LuaSFMLConsoleView() 40 | { 41 | if(m_ownfont) 42 | delete m_font; 43 | } 44 | 45 | void LuaSFMLConsoleView::draw(sf::RenderTarget& target, sf::RenderStates states) const 46 | { 47 | if(!m_font || !m_modelvisible) 48 | return; 49 | 50 | //draw in a single call in a 1:1 view 51 | sf::View v = target.getView(); 52 | target.setView(sf::View(sf::FloatRect(sf::Vector2f(), sf::Vector2f(target.getSize())))); 53 | states.texture = &m_font->getTexture(kFontSize); 54 | target.draw(m_vertices, states); 55 | target.setView(v); 56 | } 57 | 58 | void LuaSFMLConsoleView::setFont(const sf::Font * font) 59 | { 60 | //do something to dirtyness to force rebuilding letters?? 61 | 62 | if(m_ownfont) 63 | delete m_font; 64 | 65 | m_font = font; 66 | m_ownfont = false; //never own a set font 67 | 68 | //if set font is null and we have default option then set it 69 | if(!m_font && m_defaultfont) 70 | { 71 | sf::Font * dfont = new sf::Font; 72 | dfont->loadFromFile(kFontName); 73 | m_font = dfont; 74 | m_ownfont = true; 75 | } 76 | } 77 | 78 | const sf::Font * LuaSFMLConsoleView::getFont() const 79 | { 80 | return m_font; 81 | } 82 | 83 | //code below was taken from SFML Text.cpp and MODIFIED, 84 | //it is NOT the original software, if looking for original software see: 85 | //https://github.com/LaurentGomila/SFML/ 86 | 87 | //////////////////////////////////////////////////////////// 88 | // 89 | // SFML - Simple and Fast Multimedia Library 90 | // Copyright (C) 2007-2014 Laurent Gomila (laurent.gom@gmail.com) 91 | // 92 | // This software is provided 'as-is', without any express or implied warranty. 93 | // In no event will the authors be held liable for any damages arising from the use of this software. 94 | // 95 | // Permission is granted to anyone to use this software for any purpose, 96 | // including commercial applications, and to alter it and redistribute it freely, 97 | // subject to the following restrictions: 98 | // 99 | // 1. The origin of this software must not be misrepresented; 100 | // you must not claim that you wrote the original software. 101 | // If you use this software in a product, an acknowledgment 102 | // in the product documentation would be appreciated but is not required. 103 | // 104 | // 2. Altered source versions must be plainly marked as such, 105 | // and must not be misrepresented as being the original software. 106 | // 107 | // 3. This notice may not be removed or altered from any source distribution. 108 | // 109 | //////////////////////////////////////////////////////////// 110 | 111 | void LuaSFMLConsoleView::geoRebuild(const LuaConsoleModel * model) 112 | { 113 | if(!model) 114 | return; 115 | 116 | //obviously no need to rebuild when model isn't any dirtier 117 | if(m_lastdirtyness == model->getDirtyness()) 118 | return; 119 | 120 | //take dirtyness after font so setting font late works 121 | if(!m_font) 122 | return; 123 | 124 | m_lastdirtyness = model->getDirtyness(); 125 | m_modelvisible = model->isVisible(); 126 | 127 | //dont bother build geo that isnt going to be drawn 128 | if(!m_modelvisible) 129 | return; 130 | 131 | const ScreenCell * screen = model->getScreenBuffer(); 132 | 133 | // Clear the previous geometry 134 | m_vertices.clear(); 135 | 136 | //reserve 4 vertices for background 137 | for(int i = 0; i < 6; ++i) 138 | m_vertices.append(sf::Vertex()); 139 | 140 | // Precompute the variables needed by the algorithm 141 | float hspace = m_font->getGlyph(L' ', kFontSize, false).advance; 142 | float vspace = m_font->getLineSpacing(kFontSize); 143 | float x = 0.f; 144 | float y = kFontSize; 145 | 146 | unsigned prevChar = 0u; 147 | 148 | for(std::size_t i = 0u; i < model->getConsoleWidth() * model->getConsoleHeight(); ++i) 149 | { 150 | const unsigned curChar = screen[i].Char; 151 | 152 | //move the reserved vertices so they won't affect the bounds 153 | if(i == 1u) 154 | for(std::size_t j = 0u; j < 6u; ++j) 155 | m_vertices[j].position = m_vertices[6].position; 156 | 157 | // Apply the kerning offset 158 | x += m_font->getKerning(prevChar, curChar, kFontSize); 159 | prevChar = curChar; 160 | 161 | //add a cursor under the glyph if this is the right position 162 | if(model->getCurPos() + (model->getConsoleWidth() * (model->getConsoleHeight() - 2)) == i) 163 | { 164 | const unsigned kFullBlockChar = 0x2588u; //unicode fullblock 165 | const sf::Glyph g = m_font->getGlyph(kFullBlockChar, kFontSize, false); 166 | const sf::Color cc = toColor(model->getColor(ECC_CURSOR)); 167 | const sf::Vector2f tc = sf::Vector2f(1.f, 1.f); //solid pixel in SFML 168 | 169 | m_vertices.append(sf::Vertex(sf::Vector2f(x + g.bounds.left, y + g.bounds.top), cc, tc)); 170 | m_vertices.append(sf::Vertex(sf::Vector2f(x + g.bounds.left, y + g.bounds.top + g.bounds.height), cc, tc)); 171 | m_vertices.append(sf::Vertex(sf::Vector2f(x + g.bounds.left + g.bounds.width, y + g.bounds.top + g.bounds.height), cc, tc)); 172 | 173 | m_vertices.append(sf::Vertex(sf::Vector2f(x + g.bounds.left + g.bounds.width, y + g.bounds.top + g.bounds.height), cc, tc)); 174 | m_vertices.append(sf::Vertex(sf::Vector2f(x + g.bounds.left + g.bounds.width, y + g.bounds.top), cc, tc)); 175 | m_vertices.append(sf::Vertex(sf::Vector2f(x + g.bounds.left, y + g.bounds.top), cc, tc)); 176 | } 177 | 178 | // Handle spaces 179 | switch(curChar) 180 | { 181 | case '\t': case '\n': case ' ': 182 | x += hspace; 183 | continue; 184 | } 185 | 186 | // Extract the current glyph's description 187 | const sf::Glyph& glyph = m_font->getGlyph(curChar, kFontSize, false); 188 | 189 | const int left = glyph.bounds.left; 190 | const int top = glyph.bounds.top; 191 | const int right = glyph.bounds.left + glyph.bounds.width; 192 | const int bottom = glyph.bounds.top + glyph.bounds.height; 193 | 194 | const float u1 = glyph.textureRect.left; 195 | const float v1 = glyph.textureRect.top; 196 | const float u2 = glyph.textureRect.left + glyph.textureRect.width; 197 | const float v2 = glyph.textureRect.top + glyph.textureRect.height; 198 | 199 | //add a quad for the current character 200 | const sf::Color col = toColor(screen[i].Color); 201 | m_vertices.append(sf::Vertex(sf::Vector2f(x + left, y + top), col, sf::Vector2f(u1, v1))); 202 | m_vertices.append(sf::Vertex(sf::Vector2f(x + right, y + top), col, sf::Vector2f(u2, v1))); 203 | m_vertices.append(sf::Vertex(sf::Vector2f(x + right, y + bottom), col, sf::Vector2f(u2, v2))); 204 | 205 | m_vertices.append(sf::Vertex(sf::Vector2f(x + right, y + bottom), col, sf::Vector2f(u2, v2))); 206 | m_vertices.append(sf::Vertex(sf::Vector2f(x + left, y + bottom), col, sf::Vector2f(u1, v2))); 207 | m_vertices.append(sf::Vertex(sf::Vector2f(x + left, y + top), col, sf::Vector2f(u1, v1))); 208 | 209 | // Advance to the next character 210 | x += glyph.advance; 211 | 212 | if((i + 1) % model->getConsoleWidth() == 0) 213 | { 214 | y += vspace; 215 | x = 0; 216 | 217 | //we dont use \ns so we fake them, this is in theory for kerning but 218 | //probably doesnt matter since we (supposedly) use a monospaced 219 | //font anyway 220 | prevChar = '\n'; 221 | } 222 | } //for i 223 | 224 | //fill the reserved background vertices 225 | const sf::FloatRect vbounds = m_vertices.getBounds(); 226 | m_vertices[0].position = sf::Vector2f(vbounds.left, vbounds.top); 227 | m_vertices[1].position = sf::Vector2f(vbounds.left, vbounds.top + vbounds.height); 228 | m_vertices[2].position = sf::Vector2f(vbounds.left + vbounds.width, vbounds.top + vbounds.height); 229 | 230 | m_vertices[3].position = sf::Vector2f(vbounds.left + vbounds.width, vbounds.top + vbounds.height); 231 | m_vertices[4].position = sf::Vector2f(vbounds.left + vbounds.width, vbounds.top); 232 | m_vertices[5].position = sf::Vector2f(vbounds.left, vbounds.top); 233 | 234 | const sf::Color bcolor = toColor(model->getColor(ECC_BACKGROUND)); 235 | for(std::size_t j = 0u; j < 6u; ++j) 236 | { 237 | //SFML assumes this is a solid pixel 238 | m_vertices[j].texCoords = sf::Vector2f(1.f, 1.f); 239 | m_vertices[j].color = bcolor; 240 | } 241 | }//geoRebuild 242 | 243 | } //blua 244 | 245 | -------------------------------------------------------------------------------- /sshot/sshot0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRex/LuaConsole/b5b536756ef8e616c2992f827c52271f74ecec00/sshot/sshot0.png --------------------------------------------------------------------------------