├── inih ├── README.txt ├── LICENSE.txt └── INIReader.h ├── libbsd ├── README.txt └── strmode.h ├── tsl ├── README.txt ├── LICENSE ├── robin_growth_policy.h ├── robin_set.h └── robin_map.h ├── screenshot00.png ├── screenshot01.png ├── example-plugin ├── 0sxiv ├── 0renamer ├── _1autojump% ├── 0extra_archive └── _2archive ├── cpp-linenoise ├── README.txt └── LICENSE ├── termbox ├── README.txt ├── COPYING ├── bytebuffer.inl ├── utf8.c ├── input.inl ├── term.inl ├── termbox.h ├── wcwidth-cjk.hpp └── termbox.c ├── LICENSE ├── cmdline ├── LICENSE └── cmdline.h ├── CMakeLists.txt ├── help.hpp ├── TermboxUtil.hpp ├── icons.hpp ├── README_JP.md ├── README.md ├── ImageUtil.hpp └── NanoSyntaxHighlight.hpp /inih/README.txt: -------------------------------------------------------------------------------- 1 | Original: https://github.com/benhoyt/inih 2 | -------------------------------------------------------------------------------- /libbsd/README.txt: -------------------------------------------------------------------------------- 1 | Original: https://github.com/LuaDist/libbsd 2 | -------------------------------------------------------------------------------- /tsl/README.txt: -------------------------------------------------------------------------------- 1 | Original: https://github.com/Tessil/robin-map 2 | -------------------------------------------------------------------------------- /screenshot00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAT1226/Minase/HEAD/screenshot00.png -------------------------------------------------------------------------------- /screenshot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAT1226/Minase/HEAD/screenshot01.png -------------------------------------------------------------------------------- /example-plugin/0sxiv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | # $1 Current file path 3 | # $2 File containing selected files path 4 | 5 | sxiv "$1" -------------------------------------------------------------------------------- /example-plugin/0renamer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | # $1 Current file path 3 | # $2 File containing selected files path 4 | 5 | cat "$2" | vidir - -------------------------------------------------------------------------------- /cpp-linenoise/README.txt: -------------------------------------------------------------------------------- 1 | Original: https://github.com/yhirose/cpp-linenoise 2 | 3 | Modified: 4 | line 2324 comment out 5 | line 2249 Add Arrowkey Code 6 | Add default text 7 | Add clear history 8 | -------------------------------------------------------------------------------- /example-plugin/_1autojump%: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # $1 Current file path 3 | # $2 File containing selected files path 4 | # $3 Input Text 5 | 6 | echo -n > "$2" 7 | 8 | JUMPDIR=`autojump "$3"` 9 | echo -n "$JUMPDIR" > "$2" 10 | -------------------------------------------------------------------------------- /termbox/README.txt: -------------------------------------------------------------------------------- 1 | Original: https://github.com/nsf/termbox 2 | 3 | Add support east asian ambiguous character width. 4 | Add new function. 5 | SO_IMPORT void tb_put_cell_front(int x, int y, const struct tb_cell *cell); 6 | SO_IMPORT void tb_change_cell_front(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg); 7 | SO_IMPORT void tb_use_wcwidth_cjk(int flg); 8 | SO_IMPORT int tb_wcwidth(const wchar_t c); 9 | -------------------------------------------------------------------------------- /example-plugin/0extra_archive: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # $1 Current file path 3 | # $2 File containing selected files path 4 | 5 | if which unar > /dev/null 2>&1 ; then 6 | cmd="unar" 7 | elif which aunpack > /dev/null 2>&1 ; then 8 | cmd="aunpack" 9 | else 10 | exit 11 | fi 12 | 13 | if [ ! -s "$2" ]; then 14 | $cmd "$1" 15 | else 16 | while read line 17 | do 18 | $cmd "$line" 19 | done < $2 20 | fi 21 | 22 | echo "Please any key to exit!" 23 | read -n 1 -r _ 24 | -------------------------------------------------------------------------------- /example-plugin/_2archive: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # $1 Current file path 3 | # $2 File containing selected files path 4 | # $3 Input Text 5 | 6 | CURRENT=`pwd` 7 | TMPFILE=`mktemp` 8 | ARCHIVE=$3 9 | 10 | if [ -z "$ARCHIVE" ]; then 11 | ARCHIVE=$(basename "`head -1 "$2"`") 12 | ARCHIVE=${ARCHIVE%.*}.zip 13 | fi 14 | 15 | while read line 16 | do 17 | realpath --relative-to="${CURRENT}" "${line}" >> "${TMPFILE}" 18 | done < $2 19 | 20 | if [ -s $TMPFILE ]; then 21 | cat $TMPFILE | tr '\n' '\0' | atool -a --null "$ARCHIVE" 22 | echo -n "$ARCHIVE" > "$2" 23 | fi 24 | 25 | rm $TMPFILE 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 SAT 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 | -------------------------------------------------------------------------------- /tsl/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tessil 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 | -------------------------------------------------------------------------------- /termbox/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2010-2013 nsf 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /cpp-linenoise/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 yhirose 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /inih/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The "inih" library is distributed under the New BSD license: 3 | 4 | Copyright (c) 2009, Ben Hoyt 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of Ben Hoyt nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /cmdline/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Hideyuki Tanaka 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Hideyuki Tanaka nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /termbox/bytebuffer.inl: -------------------------------------------------------------------------------- 1 | struct bytebuffer { 2 | char *buf; 3 | int len; 4 | int cap; 5 | }; 6 | 7 | static void bytebuffer_reserve(struct bytebuffer *b, int cap) { 8 | if (b->cap >= cap) { 9 | return; 10 | } 11 | 12 | // prefer doubling capacity 13 | if (b->cap * 2 >= cap) { 14 | cap = b->cap * 2; 15 | } 16 | 17 | char *newbuf = realloc(b->buf, cap); 18 | b->buf = newbuf; 19 | b->cap = cap; 20 | } 21 | 22 | static void bytebuffer_init(struct bytebuffer *b, int cap) { 23 | b->cap = 0; 24 | b->len = 0; 25 | b->buf = 0; 26 | 27 | if (cap > 0) { 28 | b->cap = cap; 29 | b->buf = malloc(cap); // just assume malloc works always 30 | } 31 | } 32 | 33 | static void bytebuffer_free(struct bytebuffer *b) { 34 | if (b->buf) 35 | free(b->buf); 36 | } 37 | 38 | static void bytebuffer_clear(struct bytebuffer *b) { 39 | b->len = 0; 40 | } 41 | 42 | static void bytebuffer_append(struct bytebuffer *b, const char *data, int len) { 43 | bytebuffer_reserve(b, b->len + len); 44 | memcpy(b->buf + b->len, data, len); 45 | b->len += len; 46 | } 47 | 48 | static void bytebuffer_puts(struct bytebuffer *b, const char *str) { 49 | bytebuffer_append(b, str, strlen(str)); 50 | } 51 | 52 | static void bytebuffer_resize(struct bytebuffer *b, int len) { 53 | bytebuffer_reserve(b, len); 54 | b->len = len; 55 | } 56 | 57 | static void bytebuffer_flush(struct bytebuffer *b, int fd) { 58 | write(fd, b->buf, b->len); 59 | bytebuffer_clear(b); 60 | } 61 | 62 | static void bytebuffer_truncate(struct bytebuffer *b, int n) { 63 | if (n <= 0) 64 | return; 65 | if (n > b->len) 66 | n = b->len; 67 | const int nmove = b->len - n; 68 | memmove(b->buf, b->buf+n, nmove); 69 | b->len -= n; 70 | } 71 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.11) 2 | 3 | PROJECT(Minase VERSION 0.1.0 LANGUAGES CXX C) 4 | 5 | IF(NOT CMAKE_BUILD_TYPE) 6 | SET(CMAKE_BUILD_TYPE "Release" CACHE STRING 7 | "Choose the type of build, options are: Debug Release" 8 | FORCE) 9 | ENDIF(NOT CMAKE_BUILD_TYPE) 10 | MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") 11 | 12 | FIND_PACKAGE(Threads REQUIRED) 13 | FIND_PACKAGE(Iconv REQUIRED) 14 | INCLUDE(FindPkgConfig) 15 | 16 | PKG_SEARCH_MODULE(UCHARDET REQUIRED uchardet) 17 | IF(NOT UCHARDET_FOUND) 18 | MESSAGE(FATAL_ERROR "uchardet not found!") 19 | ELSE() 20 | MESSAGE(STATUS "uchardet: ${UCHARDET_VERSION}") 21 | ENDIF() 22 | 23 | PKG_SEARCH_MODULE(TAGLIB REQUIRED taglib) 24 | IF(NOT TAGLIB_FOUND) 25 | MESSAGE(FATAL_ERROR "taglib not found!") 26 | ELSE() 27 | MESSAGE(STATUS "taglib: ${TAGLIB_VERSION}") 28 | ENDIF() 29 | 30 | SET(MIGEMO_LIBRARIES "") 31 | IF(EXISTS "/usr/include/migemo.h") 32 | MESSAGE(STATUS "Use migemo") 33 | ADD_COMPILE_OPTIONS("-DUSE_MIGEMO") 34 | SET(MIGEMO_LIBRARIES "-lmigemo") 35 | ENDIF() 36 | 37 | SET(CMAKE_INCLUDE_CURRENT_DIR ON) 38 | SET(CMAKE_CXX_STANDARD 11) 39 | SET(CMAKE_CXX_FLAGS "-fPIC -Wall") 40 | SET(CMAKE_CXX_FLAGS_DEBUG "-g -pg") 41 | SET(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 -mtune=native -march=native -mfpmath=both") 42 | 43 | ADD_EXECUTABLE(minase main.cpp termbox/termbox.c termbox/utf8.c) 44 | INCLUDE_DIRECTORIES( 45 | ${UCHARDET_INCLUDE_DIRS} 46 | ${ICONV_INCLUDE_DIRS} 47 | ${TAGLIB_INCLUDE_DIRS} 48 | ./ 49 | ) 50 | TARGET_LINK_LIBRARIES( 51 | minase 52 | Threads::Threads 53 | ${UCHARDET_LIBRARIES} 54 | ${ICONV_LIBRARIES} 55 | ${TAGLIB_LIBRARIES} 56 | ${MIGEMO_LIBRARIES} 57 | ) 58 | 59 | INSTALL(TARGETS minase DESTINATION bin) 60 | -------------------------------------------------------------------------------- /termbox/utf8.c: -------------------------------------------------------------------------------- 1 | #include "termbox.h" 2 | 3 | static const unsigned char utf8_length[256] = { 4 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 5 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 6 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 7 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 8 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 10 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 11 | 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 12 | }; 13 | 14 | static const unsigned char utf8_mask[6] = { 15 | 0x7F, 16 | 0x1F, 17 | 0x0F, 18 | 0x07, 19 | 0x03, 20 | 0x01 21 | }; 22 | 23 | int tb_utf8_char_length(char c) 24 | { 25 | return utf8_length[(unsigned char)c]; 26 | } 27 | 28 | int tb_utf8_char_to_unicode(uint32_t *out, const char *c) 29 | { 30 | if (*c == 0) 31 | return TB_EOF; 32 | 33 | int i; 34 | unsigned char len = tb_utf8_char_length(*c); 35 | unsigned char mask = utf8_mask[len-1]; 36 | uint32_t result = c[0] & mask; 37 | for (i = 1; i < len; ++i) { 38 | result <<= 6; 39 | result |= c[i] & 0x3f; 40 | } 41 | 42 | *out = result; 43 | return (int)len; 44 | } 45 | 46 | int tb_utf8_unicode_to_char(char *out, uint32_t c) 47 | { 48 | int len = 0; 49 | int first; 50 | int i; 51 | 52 | if (c < 0x80) { 53 | first = 0; 54 | len = 1; 55 | } else if (c < 0x800) { 56 | first = 0xc0; 57 | len = 2; 58 | } else if (c < 0x10000) { 59 | first = 0xe0; 60 | len = 3; 61 | } else if (c < 0x200000) { 62 | first = 0xf0; 63 | len = 4; 64 | } else if (c < 0x4000000) { 65 | first = 0xf8; 66 | len = 5; 67 | } else { 68 | first = 0xfc; 69 | len = 6; 70 | } 71 | 72 | for (i = len - 1; i > 0; --i) { 73 | out[i] = (c & 0x3f) | 0x80; 74 | c >>= 6; 75 | } 76 | out[0] = c | first; 77 | 78 | return len; 79 | } 80 | -------------------------------------------------------------------------------- /help.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HELP_HPP 2 | #define HELP_HPP 3 | const char* helpstr = { 4 | " Keys : Description\n" 5 | "----------------------------------------------------\n" 6 | " h, Right : Parent directory\n" 7 | " j, Down : Down\n" 8 | " k, Up : Up\n" 9 | " l, Right : Open file/directory\n" 10 | " PgUp, ^U : Scroll up\n" 11 | " PgDn, ^D : Scroll down\n" 12 | " H : Move to top of screen\n" 13 | " M : Move to middle of screen\n" 14 | " L : Move to bottom of screen\n" 15 | " g : Go to first entry\n" 16 | " G : Go to last entry\n" 17 | " ^L : Redraw\n" 18 | " q : Quit\n" 19 | " ^G : Quit and cd\n" 20 | " 0 : View log\n" 21 | " 1 : Switch tab 1\n" 22 | " 2 : Switch tab 2\n" 23 | " 3 : Switch tab 3\n" 24 | " 4 : Switch tab 4\n" 25 | " , : FileView simple/detail\n" 26 | " . : Show/Hide dot files\n" 27 | " i : Enable/Disable image preview\n" 28 | " z : Arcive\n" 29 | " Z : Current line to the middle of the screen\n" 30 | " s : Sort files\n" 31 | " e : Edit File\n" 32 | " Space : Mark file\n" 33 | " u : Clear marks\n" 34 | " a : Invert marks (current directory only)\n" 35 | " c : Mark files for copy\n" 36 | " m : Mark files for move\n" 37 | " d : Delete mark files\n" 38 | " p : Paste\n" 39 | " r : Rename current file\n" 40 | " ^R : Batch rename (vidir)\n" 41 | " ! : Spawn SHELL\n" 42 | " n : Create file/directory\n" 43 | " b : Open Bookmarks\n" 44 | " / : Filter\n" 45 | " ^/ : Change Filter type\n" 46 | " x : Unarchive\n" 47 | " ^x : Run Plugin \n" 48 | "Alt + key : Run Plugin \n" 49 | " * : Toggle execute permission\n" 50 | " ^j : scrolldown in preview\n" 51 | " ^k : scrollup in preview\n" 52 | " U : Unmouont Directory\n" 53 | " ^a : Go to archive mount directory\n" 54 | " ? : Help\n" 55 | }; 56 | #endif 57 | -------------------------------------------------------------------------------- /TermboxUtil.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TERMBOXUTIL_HPP 2 | #define TERMBOXUTIL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include "termbox/termbox.h" 10 | #include "termbox/wcwidth-cjk.hpp" 11 | 12 | std::wstring string2wstring(const std::string &s) { 13 | auto mbstr = s.c_str(); 14 | std::mbstate_t state = std::mbstate_t(); 15 | std::size_t len = 1 + std::mbsrtowcs(NULL, &mbstr, 0, &state); 16 | std::vector wstr(len); 17 | std::mbsrtowcs(&wstr[0], &mbstr, wstr.size(), &state); 18 | 19 | return std::wstring(wstr.begin(), wstr.end()); 20 | } 21 | 22 | std::string wstring2string(const std::wstring& ws) 23 | { 24 | auto wstr = ws.c_str(); 25 | std::mbstate_t state = std::mbstate_t(); 26 | std::size_t len = 1 + std::wcsrtombs(nullptr, &wstr, 0, &state); 27 | std::vector mbstr(len); 28 | std::wcsrtombs(&mbstr[0], &wstr, mbstr.size(), &state); 29 | 30 | return std::string(mbstr.begin(), mbstr.end()); 31 | } 32 | 33 | std::string strimwidth(const std::string& str, int l, int* strlen = 0) 34 | { 35 | auto wstr = string2wstring(str); 36 | 37 | int i, w = 0; 38 | for(i = 0; i < static_cast(wstr.length()); ++i) { 39 | // escape sequence 40 | if(wstr[i] == L'\e') { 41 | if(static_cast(wstr.length()) > i + 1 && wstr[i + 1] == L'[') { 42 | int j = 1; 43 | while(wstr[i + j] != L'm') { 44 | if(j < static_cast(wstr.length())) ++j; 45 | else break; 46 | } 47 | i += j; 48 | continue; 49 | } 50 | } 51 | if(iswcntrl(wstr[i])) wstr[i] = L' '; 52 | 53 | auto wc = tb_wcwidth(wstr[i]); 54 | if(wc < 0) break; 55 | w += wc; 56 | 57 | if(w > l) { 58 | w -= wc; 59 | break; 60 | } 61 | } 62 | 63 | if(strlen != 0) *strlen = w; 64 | return wstring2string(std::wstring(wstr.begin(), wstr.begin() + i)); 65 | } 66 | 67 | int drawText(int x, int y, const std::string &str, uint16_t fg = 0, uint16_t bg = 0, int len = -1) { 68 | auto wstr = string2wstring(str); 69 | int cnt = 0; 70 | uint32_t c; 71 | 72 | for (int i = 0; i < static_cast(wstr.length() - 1); ++i) { 73 | char s[8] = {0}; 74 | wctomb(s, wstr[i]); 75 | 76 | tb_utf8_char_to_unicode(&c, s); 77 | 78 | auto cw = tb_wcwidth(wstr[i]); 79 | if(len != -1 && cnt + cw > len) break; 80 | 81 | tb_change_cell(x + cnt, y, c, fg, bg); 82 | cnt += cw; 83 | } 84 | 85 | return cnt; 86 | } 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /icons.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ICONS_HPP 2 | #define ICONS_HPP 3 | 4 | struct Icon { 5 | const char* match; 6 | const char* icon; 7 | }; 8 | 9 | static const Icon dirIcon = {"", ""}; 10 | static const Icon fileIcon = {"", ""}; 11 | static const Icon exeIcon = {"", ""}; 12 | 13 | static const Icon icons[] = { 14 | // video 15 | {"mp4", "󰈫"}, 16 | {"flv", "󰈫"}, 17 | {"mp4", "󰈫"}, 18 | {"mov", "󰈫"}, 19 | {"wma", "󰈫"}, 20 | {"webm", "󰈫"}, 21 | {"avi", "󰈫"}, 22 | {"vid", "󰈫"}, 23 | {"mpg", "󰈫"}, 24 | {"m4v", "󰈫"}, 25 | {"wmv", "󰈫"}, 26 | {"mkv", "󰈫"}, 27 | {"mpeg", "󰈫"}, 28 | 29 | // image 30 | {"jpg", "󰈟"}, 31 | {"jpeg", "󰈟"}, 32 | {"ico", "󰈟"}, 33 | {"svg", "󰈟"}, 34 | {"gif", "󰈟"}, 35 | {"bmp", "󰈟"}, 36 | {"xcf", "󰈟"}, 37 | {"png", "󰈟"}, 38 | {"psd", "󰈟"}, 39 | {"webp", "󰈟"}, 40 | 41 | // audio 42 | {"wav", "󰎈"}, 43 | {"opus", "󰎈"}, 44 | {"mp3", "󰎈"}, 45 | {"flac", "󰎈"}, 46 | {"m4a", "󰎈"}, 47 | {"ogg", "󰎈"}, 48 | {"oga", "󰎈"}, 49 | {"aup", "󰎈"}, 50 | {"ape", "󰎈"}, 51 | {"tta", "󰎈"}, 52 | 53 | // archive 54 | {"zip", "󰀼"}, 55 | {"xz", "󰀼"}, 56 | {"gzip", "󰀼"}, 57 | {"cab", "󰀼"}, 58 | {"tgz", "󰀼"}, 59 | {"lzh", "󰀼"}, 60 | {"lha", "󰀼"}, 61 | {"tar", "󰀼"}, 62 | {"apk", "󰀼"}, 63 | {"rar", "󰀼"}, 64 | {"7z", "󰀼"}, 65 | {"cpio", "󰀼"}, 66 | {"bz2", "󰀼"}, 67 | {"lzma", "󰀼"}, 68 | {"zst", "󰀼"}, 69 | {"gz", "󰀼"}, 70 | {"cbz", "󰀼"}, 71 | {"cbr", "󰀼"}, 72 | {"iso", ""}, 73 | {"img", ""}, 74 | 75 | // bin 76 | {"o", ""}, 77 | {"class", ""}, 78 | {"so", ""}, 79 | {"a", ""}, 80 | {"bin", ""}, 81 | {"elf", ""}, 82 | {"dll", ""}, 83 | {"exe", ""}, 84 | 85 | // text 86 | {"txt", ""}, 87 | {"md", ""}, 88 | {"markdown", ""}, 89 | 90 | {"html", ""}, 91 | {"htm", ""}, 92 | {"xhtml", ""}, 93 | {"rss", ""}, 94 | {"css", ""}, 95 | 96 | {"pdf", ""}, 97 | {"doc", "󰈬"}, 98 | {"docx", "󰈬"}, 99 | {"rtf", "󰈬"}, 100 | 101 | {"yaml", ""}, 102 | {"toml", ""}, 103 | {"yml", ""}, 104 | {"tml", ""}, 105 | {"ini", ""}, 106 | 107 | {"json", ""}, 108 | 109 | {"1", "󱓷"}, 110 | 111 | // code 112 | {"sh", "󰆍"}, 113 | {"cmake", "󰆍"}, 114 | {"mk", "󰆍"}, 115 | 116 | {"c", ""}, 117 | {"h", ""}, 118 | {"cpp", ""}, 119 | {"c++", ""}, 120 | {"cxx", ""}, 121 | {"cc", ""}, 122 | {"hpp", ""}, 123 | {"hh", ""}, 124 | {"hxx", ""}, 125 | {"cs", ""}, 126 | 127 | {"py", ""}, 128 | {"pyo", ""}, 129 | {"pyd", ""}, 130 | {"pyc", ""}, 131 | 132 | {"rb", ""}, 133 | {"gem", ""}, 134 | {"rake", ""}, 135 | 136 | {"pl", ""}, 137 | {"pm", ""}, 138 | {"t", ""}, 139 | 140 | {"go", ""}, 141 | 142 | {"rs", ""}, 143 | 144 | {"js", ""}, 145 | {"ejs", ""}, 146 | {"ts", ""}, 147 | 148 | {"java", ""}, 149 | {"jar", ""}, 150 | 151 | {"php", ""}, 152 | 153 | {"vim", ""}, 154 | {"vimrc", ""}, 155 | 156 | {"diff", ""}, 157 | {"patch", ""}, 158 | }; 159 | 160 | #endif 161 | -------------------------------------------------------------------------------- /README_JP.md: -------------------------------------------------------------------------------- 1 | # Minase 2 | Minaseはターミナルで動くファイラーです 3 | 4 | ![image](./screenshot00.png) 5 | 6 | ![image](./screenshot01.png) 7 | 8 | ## Features 9 | * 選択しているファイル/ディレクトリのプレビュー表示 10 | * プレビューテキストのシンタックスハイライト表示 (Nano editorのシンタックスハイライト定義ファイルを使います) 11 | * プレビューテキストの文字コードを自動認識 12 | * オーディオファイルのタグをプレビュー表示 13 | * 圧縮ファイルをプレビュー表示 (lsarまたはbsdtarが必要) 14 | * Sixel Graphicsを使ったイメージプレビュー (img2sixelが必要) 15 | * FreeDesktopに準拠したゴミ箱 (trash-cliが必要) 16 | * バッチリネーム (vidirが必要) 17 | * UTF-8 に対応 18 | * "East Asian Ambiguous Width Characters"問題を修正 (wcwidth-cjkを使います) 19 | * Nerd Fontsを使ったアイコン表示 20 | 21 | ## System Requirements 22 | * Linux 23 | 24 | ## Dependencies 25 | * uchardet 26 | * iconv 27 | * TagLib 28 | 29 | optional: 30 | * libsixel 31 | * trash-cli 32 | * vidir 33 | * lsar or bsdtar 34 | * cmigemo 35 | 36 | ## Usage 37 | |Keys|Description| 38 | | ---- | ---- | 39 | |h, Right| 親ディレクトリへ移動| 40 | |j, Down| 下に移動| 41 | |k, Up| 上に移動| 42 | |l, Right| file/directoryを開く| 43 | |PgUp, ^U| 上にスクロール| 44 | |PgDn, ^D| 下にスクロール| 45 | |H| 画面の一番上に移動| 46 | |M| 画面の中央に移動| 47 | |L| 画面の一番下に移動| 48 | |g| 最初の項目に移動| 49 | |G| 最後の項目に移動| 50 | |^L| 再描画| 51 | |q| 終了| 52 | |^G| 終了した後にcd| 53 | |0| ログを見る| 54 | |1| タブ1 に切り替え| 55 | |2| タブ2 に切り替え| 56 | |3| タブ3 に切り替え| 57 | |4| タブ4 に切り替え| 58 | |,| ファイルリストを simple/detail 表示に切り替え| 59 | |.| ドットファイルの表示/非表示| 60 | |i| イメージプレビューの有効/無効| 61 | |z| アーカイブを作成| 62 | |Z| 現在の行を画面の中心に| 63 | |s| ソート項目の変更| 64 | |e| ファイルを編集| 65 | |Space| ファイルをマーク| 66 | |u| マークを消去| 67 | |a| 現在のディレクトリのマークを反転| 68 | |c| マークしたファイルにコピーマークを付ける| 69 | |m| マークしたファイルにムーブマークを付ける| 70 | |d| マークしたファイルを削除| 71 | |p| コピー/ムーブマークをつけたファイルを貼り付け| 72 | |r| 現在のファイルをリネーム| 73 | |^R| バッチリネーム (vidir)| 74 | |!| シェルを起動| 75 | |n| file/directoryの作成| 76 | |b| ブックマークを開く| 77 | |/| フィルター| 78 | |^/| フィルターの種類を変更| 79 | |*| 実行属性をトグルする| 80 | |x| アーカイブを解凍| 81 | |^x| pluginを実行| 82 | |Alt + key| pluginを実行| 83 | |^j| プレビュー画面を下にスクロール| 84 | |^k| プレビュー画面を上にスクロール| 85 | |^a| アーカイブマウントディレクトリへ移動| 86 | |?| ヘルプを表示| 87 | 88 | 終了時にcdするには: 89 | ``` 90 | $ minase; if [ -f ~/.config/Minase/lastdir ]; then cd "`cat ~/.config/Minase/lastdir`"; rm ~/.config/Minase/lastdir; fi; 91 | ``` 92 | ## Installation 93 | ``` 94 | $ cmake . 95 | $ make 96 | $ sudo make install 97 | ``` 98 | 99 | ## Customization 100 | ~/.config/Minase/config.ini 101 | ``` 102 | [Options] 103 | ; File Opener 104 | Opener = xdg-open 105 | 106 | ; Log view Max lines 107 | LogMaxLines = 100 108 | 109 | ; Preview Max lines (-1: 無制限) 110 | PreViewMaxLines = 50 111 | 112 | ; Use trash-cli 113 | UseTrash = true 114 | 115 | ; Nano Editor Syntax Highlighting Files 116 | NanorcPath = /usr/share/nano/ 117 | 118 | ; East Asian Ambiguous Width 119 | wcwidth-cjk = false 120 | 121 | ; 0: simple / 1: detail 122 | FileViewType = 0 123 | 124 | ; 0: name / 1: size / 2: date 125 | SortType = 2 126 | 127 | ; 0: Ascending / 1: Descending 128 | SortOrder = 1 129 | 130 | ; Migemo Dictionary File 131 | ;MigemoDict = /usr/share/migemo/utf-8/migemo-dict 132 | 133 | ; 0: Normal / 1: Regexp / 2: Migemo 134 | FilterType = 0 135 | 136 | ; ArchiveMntDir 137 | ;ArchiveMntDir = ~/.config/Minase/mnt 138 | 139 | ; CustomCopy 140 | ;CustomCopy = advcp -vgrp 141 | 142 | ; CustomMove 143 | ;CustomMove = advmv -vg 144 | 145 | ; CustomRenamer 146 | ;CustomRenamer = massren 147 | ;CustomRenamer = mmv * 148 | 149 | ; Icon 150 | ;UseIcon = true 151 | ``` 152 | 153 | ~/.config/Minase/bookmarks 154 | ``` 155 | ~/Download 156 | ~/Documents 157 | /usr/local 158 | ``` 159 | 160 | ~/.config/Minase/plugin.ini 161 | ``` 162 | [sxiv] 163 | ; Plugin Path 164 | filepath = ~/.config/Minase/plugins/0sxiv 165 | ; GUI: true / false 166 | gui = true 167 | ; Shortcut Key 168 | key = s 169 | 170 | [autojump] 171 | filepath = ~/.config/Minase/plugins/_1autojump% 172 | key = j 173 | ``` 174 | 175 | ## License 176 | * MIT 177 | -------------------------------------------------------------------------------- /libbsd/strmode.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 1990, 1993 3 | * The Regents of the University of California. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 4. Neither the name of the University nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software 15 | * without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | void 36 | strmode(mode_t mode, char *p) 37 | { 38 | /* print type */ 39 | switch (mode & S_IFMT) { 40 | case S_IFDIR: /* directory */ 41 | *p++ = 'd'; 42 | break; 43 | case S_IFCHR: /* character special */ 44 | *p++ = 'c'; 45 | break; 46 | case S_IFBLK: /* block special */ 47 | *p++ = 'b'; 48 | break; 49 | case S_IFREG: /* regular */ 50 | *p++ = '-'; 51 | break; 52 | case S_IFLNK: /* symbolic link */ 53 | *p++ = 'l'; 54 | break; 55 | case S_IFSOCK: /* socket */ 56 | *p++ = 's'; 57 | break; 58 | #ifdef S_IFIFO 59 | case S_IFIFO: /* fifo */ 60 | *p++ = 'p'; 61 | break; 62 | #endif 63 | #ifdef S_IFWHT 64 | case S_IFWHT: /* whiteout */ 65 | *p++ = 'w'; 66 | break; 67 | #endif 68 | default: /* unknown */ 69 | *p++ = '?'; 70 | break; 71 | } 72 | /* usr */ 73 | if (mode & S_IRUSR) 74 | *p++ = 'r'; 75 | else 76 | *p++ = '-'; 77 | if (mode & S_IWUSR) 78 | *p++ = 'w'; 79 | else 80 | *p++ = '-'; 81 | switch (mode & (S_IXUSR | S_ISUID)) { 82 | case 0: 83 | *p++ = '-'; 84 | break; 85 | case S_IXUSR: 86 | *p++ = 'x'; 87 | break; 88 | case S_ISUID: 89 | *p++ = 'S'; 90 | break; 91 | case S_IXUSR | S_ISUID: 92 | *p++ = 's'; 93 | break; 94 | } 95 | /* group */ 96 | if (mode & S_IRGRP) 97 | *p++ = 'r'; 98 | else 99 | *p++ = '-'; 100 | if (mode & S_IWGRP) 101 | *p++ = 'w'; 102 | else 103 | *p++ = '-'; 104 | switch (mode & (S_IXGRP | S_ISGID)) { 105 | case 0: 106 | *p++ = '-'; 107 | break; 108 | case S_IXGRP: 109 | *p++ = 'x'; 110 | break; 111 | case S_ISGID: 112 | *p++ = 'S'; 113 | break; 114 | case S_IXGRP | S_ISGID: 115 | *p++ = 's'; 116 | break; 117 | } 118 | /* other */ 119 | if (mode & S_IROTH) 120 | *p++ = 'r'; 121 | else 122 | *p++ = '-'; 123 | if (mode & S_IWOTH) 124 | *p++ = 'w'; 125 | else 126 | *p++ = '-'; 127 | switch (mode & (S_IXOTH | S_ISVTX)) { 128 | case 0: 129 | *p++ = '-'; 130 | break; 131 | case S_IXOTH: 132 | *p++ = 'x'; 133 | break; 134 | case S_ISVTX: 135 | *p++ = 'T'; 136 | break; 137 | case S_IXOTH | S_ISVTX: 138 | *p++ = 't'; 139 | break; 140 | } 141 | *p++ = ' '; /* will be a '+' if ACL's implemented */ 142 | *p = '\0'; 143 | } 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minase 2 | (Here is [Japanese version](./README_JP.md)) 3 | Minase is terminal file manager. 4 | 5 | ![image](./screenshot00.png) 6 | 7 | ![image](./screenshot01.png) 8 | 9 | ## Features 10 | * Preview of the selected file/directory 11 | * Preview text syntax highlight (use Nano editor syntax highlight files) 12 | * Preview text auto encodeing 13 | * Preview audio tags 14 | * Preview archive files (needs lsar or bsdtar) 15 | * Preview image using Sixel Graphics (needs img2sixel) 16 | * FreeDesktop compliant trash (needs trash-cli) 17 | * Batch rename (needs vidir) 18 | * UTF-8 support 19 | * Fix "East Asian Ambiguous Width Characters" problem (use wcwidth-cjk) 20 | * Icon support using a patched nerd font 21 | 22 | ## System Requirements 23 | * Linux 24 | 25 | ## Dependencies 26 | * uchardet 27 | * iconv 28 | * TagLib 29 | 30 | optional: 31 | * libsixel 32 | * trash-cli 33 | * vidir 34 | * unar or bsdtar 35 | * cmigemo 36 | * atool 37 | * archivemount 38 | * fusermount3 or fusermount 39 | 40 | ## Usage 41 | |Keys|Description| 42 | | ---- | ---- | 43 | |h, Right| Parent directory| 44 | |j, Down| Down| 45 | |k, Up| Up| 46 | |l, Right| Open file/directory| 47 | |PgUp, ^U| Scroll up| 48 | |PgDn, ^D| Scroll down| 49 | |H| Move to top of screen| 50 | |M| Move to middle of screen| 51 | |L| Move to bottom of screen| 52 | |g| Go to first entry| 53 | |G| Go to last entry| 54 | |^L| Redraw| 55 | |q| Quit| 56 | |^G| Quit and cd| 57 | |0| View log| 58 | |1| Switch tab 1| 59 | |2| Switch tab 2| 60 | |3| Switch tab 3| 61 | |4| Switch tab 4| 62 | |,| FileView simple/detail| 63 | |.| Show/Hide dot files| 64 | |i| Enable/Disable image preview| 65 | |z| Arcive| 66 | |Z| Current line to the middle of the screen| 67 | |s| Sort files| 68 | |e| Edit File| 69 | |Space| Mark file| 70 | |u| Clear marks| 71 | |a| Invert marks (current directory only)| 72 | |c| Mark files for copy| 73 | |m| Mark files for move| 74 | |d| Delete mark files| 75 | |p| Paste| 76 | |r| Rename current file| 77 | |^R| Batch rename (vidir)| 78 | |!| Spawn SHELL| 79 | |n| Create file/directory| 80 | |b| Open Bookmarks| 81 | |/| Filter| 82 | |^/| Change Filter type| 83 | |x| Unarchive| 84 | |^x| Run Plugin | 85 | |Alt + key| Run Plugin | 86 | |*| Toggle execute permission| 87 | |^j| scrolldown in preview| 88 | |^k| scrollup in preview| 89 | |U| Unmouont Directory| 90 | |^a| Go to archive mount directory| 91 | |?| Help| 92 | 93 | Quit and cd: 94 | ``` 95 | $ minase; if [ -f ~/.config/Minase/lastdir ]; then cd "`cat ~/.config/Minase/lastdir`"; rm ~/.config/Minase/lastdir; fi; 96 | ``` 97 | 98 | ## Installation 99 | ``` 100 | $ cmake . 101 | $ make 102 | $ sudo make install 103 | ``` 104 | 105 | ## Customization 106 | ~/.config/Minase/config.ini 107 | ``` 108 | [Options] 109 | ; File Opener 110 | Opener = xdg-open 111 | 112 | ; Log view Max lines 113 | LogMaxLines = 100 114 | 115 | ; Preview Max lines (-1: unlimited) 116 | PreViewMaxLines = 50 117 | 118 | ; Use trash-cli 119 | UseTrash = true 120 | 121 | ; Nano Editor Syntax Highlighting Files 122 | NanorcPath = /usr/share/nano/ 123 | 124 | ; East Asian Ambiguous Width 125 | wcwidth-cjk = false 126 | 127 | ; 0: simple / 1: detail 128 | FileViewType = 0 129 | 130 | ; 0: name / 1: size / 2: date 131 | SortType = 2 132 | 133 | ; 0: Ascending / 1: Descending 134 | SortOrder = 1 135 | 136 | ; Migemo Dictionary File 137 | ;MigemoDict = /usr/share/migemo/utf-8/migemo-dict 138 | 139 | ; 0: Normal / 1: Regexp / 2: Migemo 140 | FilterType = 0 141 | 142 | ; ArchiveMntDir 143 | ;ArchiveMntDir = ~/.config/Minase/mnt 144 | 145 | ; CustomCopy 146 | ;CustomCopy = advcp -vgrp 147 | 148 | ; CustomMove 149 | ;CustomMove = advmv -vg 150 | 151 | ; CustomRenamer 152 | ;CustomRenamer = massren 153 | ;CustomRenamer = mmv * 154 | 155 | ; Icon 156 | ;UseIcon = true 157 | ``` 158 | 159 | ~/.config/Minase/bookmarks 160 | ``` 161 | ~/Download 162 | ~/Documents 163 | /usr/local 164 | ``` 165 | 166 | ~/.config/Minase/plugin.ini 167 | ``` 168 | [sxiv] 169 | ; Plugin Path 170 | filepath = ~/.config/Minase/plugins/0sxiv 171 | ; GUI: true / false 172 | gui = true 173 | ; Shortcut Key 174 | key = s 175 | 176 | [autojump] 177 | filepath = ~/.config/Minase/plugins/_1autojump% 178 | key = j 179 | ``` 180 | 181 | ## License 182 | * MIT 183 | -------------------------------------------------------------------------------- /termbox/input.inl: -------------------------------------------------------------------------------- 1 | // if s1 starts with s2 returns true, else false 2 | // len is the length of s1 3 | // s2 should be null-terminated 4 | static bool starts_with(const char *s1, int len, const char *s2) 5 | { 6 | int n = 0; 7 | while (*s2 && n < len) { 8 | if (*s1++ != *s2++) 9 | return false; 10 | n++; 11 | } 12 | return *s2 == 0; 13 | } 14 | 15 | static int parse_mouse_event(struct tb_event *event, const char *buf, int len) { 16 | if (len >= 6 && starts_with(buf, len, "\033[M")) { 17 | // X10 mouse encoding, the simplest one 18 | // \033 [ M Cb Cx Cy 19 | int b = buf[3] - 32; 20 | switch (b & 3) { 21 | case 0: 22 | if ((b & 64) != 0) 23 | event->key = TB_KEY_MOUSE_WHEEL_UP; 24 | else 25 | event->key = TB_KEY_MOUSE_LEFT; 26 | break; 27 | case 1: 28 | if ((b & 64) != 0) 29 | event->key = TB_KEY_MOUSE_WHEEL_DOWN; 30 | else 31 | event->key = TB_KEY_MOUSE_MIDDLE; 32 | break; 33 | case 2: 34 | event->key = TB_KEY_MOUSE_RIGHT; 35 | break; 36 | case 3: 37 | event->key = TB_KEY_MOUSE_RELEASE; 38 | break; 39 | default: 40 | return -6; 41 | } 42 | event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default 43 | if ((b & 32) != 0) 44 | event->mod |= TB_MOD_MOTION; 45 | 46 | // the coord is 1,1 for upper left 47 | event->x = (uint8_t)buf[4] - 1 - 32; 48 | event->y = (uint8_t)buf[5] - 1 - 32; 49 | 50 | return 6; 51 | } else if (starts_with(buf, len, "\033[<") || starts_with(buf, len, "\033[")) { 52 | // xterm 1006 extended mode or urxvt 1015 extended mode 53 | // xterm: \033 [ < Cb ; Cx ; Cy (M or m) 54 | // urxvt: \033 [ Cb ; Cx ; Cy M 55 | int i, mi = -1, starti = -1; 56 | int isM, isU, s1 = -1, s2 = -1; 57 | int n1 = 0, n2 = 0, n3 = 0; 58 | 59 | for (i = 0; i < len; i++) { 60 | // We search the first (s1) and the last (s2) ';' 61 | if (buf[i] == ';') { 62 | if (s1 == -1) 63 | s1 = i; 64 | s2 = i; 65 | } 66 | 67 | // We search for the first 'm' or 'M' 68 | if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1) { 69 | mi = i; 70 | break; 71 | } 72 | } 73 | if (mi == -1) 74 | return 0; 75 | 76 | // whether it's a capital M or not 77 | isM = (buf[mi] == 'M'); 78 | 79 | if (buf[2] == '<') { 80 | isU = 0; 81 | starti = 3; 82 | } else { 83 | isU = 1; 84 | starti = 2; 85 | } 86 | 87 | if (s1 == -1 || s2 == -1 || s1 == s2) 88 | return 0; 89 | 90 | n1 = strtoul(&buf[starti], NULL, 10); 91 | n2 = strtoul(&buf[s1 + 1], NULL, 10); 92 | n3 = strtoul(&buf[s2 + 1], NULL, 10); 93 | 94 | if (isU) 95 | n1 -= 32; 96 | 97 | switch (n1 & 3) { 98 | case 0: 99 | if ((n1&64) != 0) { 100 | event->key = TB_KEY_MOUSE_WHEEL_UP; 101 | } else { 102 | event->key = TB_KEY_MOUSE_LEFT; 103 | } 104 | break; 105 | case 1: 106 | if ((n1&64) != 0) { 107 | event->key = TB_KEY_MOUSE_WHEEL_DOWN; 108 | } else { 109 | event->key = TB_KEY_MOUSE_MIDDLE; 110 | } 111 | break; 112 | case 2: 113 | event->key = TB_KEY_MOUSE_RIGHT; 114 | break; 115 | case 3: 116 | event->key = TB_KEY_MOUSE_RELEASE; 117 | break; 118 | default: 119 | return mi + 1; 120 | } 121 | 122 | if (!isM) { 123 | // on xterm mouse release is signaled by lowercase m 124 | event->key = TB_KEY_MOUSE_RELEASE; 125 | } 126 | 127 | event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default 128 | if ((n1&32) != 0) 129 | event->mod |= TB_MOD_MOTION; 130 | 131 | event->x = (uint8_t)n2 - 1; 132 | event->y = (uint8_t)n3 - 1; 133 | 134 | return mi + 1; 135 | } 136 | 137 | return 0; 138 | } 139 | 140 | // convert escape sequence to event, and return consumed bytes on success (failure == 0) 141 | static int parse_escape_seq(struct tb_event *event, const char *buf, int len) 142 | { 143 | int mouse_parsed = parse_mouse_event(event, buf, len); 144 | 145 | if (mouse_parsed != 0) 146 | return mouse_parsed; 147 | 148 | // it's pretty simple here, find 'starts_with' match and return 149 | // success, else return failure 150 | int i; 151 | for (i = 0; keys[i]; i++) { 152 | if (starts_with(buf, len, keys[i])) { 153 | event->ch = 0; 154 | event->key = 0xFFFF-i; 155 | return strlen(keys[i]); 156 | } 157 | } 158 | return 0; 159 | } 160 | 161 | static bool extract_event(struct tb_event *event, struct bytebuffer *inbuf, int inputmode) 162 | { 163 | const char *buf = inbuf->buf; 164 | const int len = inbuf->len; 165 | if (len == 0) 166 | return false; 167 | 168 | if (buf[0] == '\033') { 169 | int n = parse_escape_seq(event, buf, len); 170 | if (n != 0) { 171 | bool success = true; 172 | if (n < 0) { 173 | success = false; 174 | n = -n; 175 | } 176 | bytebuffer_truncate(inbuf, n); 177 | return success; 178 | } else { 179 | // it's not escape sequence, then it's ALT or ESC, 180 | // check inputmode 181 | if (inputmode&TB_INPUT_ESC) { 182 | // if we're in escape mode, fill ESC event, pop 183 | // buffer, return success 184 | event->ch = 0; 185 | event->key = TB_KEY_ESC; 186 | event->mod = 0; 187 | bytebuffer_truncate(inbuf, 1); 188 | return true; 189 | } else if (inputmode&TB_INPUT_ALT) { 190 | // if we're in alt mode, set ALT modifier to 191 | // event and redo parsing 192 | event->mod = TB_MOD_ALT; 193 | bytebuffer_truncate(inbuf, 1); 194 | return extract_event(event, inbuf, inputmode); 195 | } 196 | assert(!"never got here"); 197 | } 198 | } 199 | 200 | // if we're here, this is not an escape sequence and not an alt sequence 201 | // so, it's a FUNCTIONAL KEY or a UNICODE character 202 | 203 | // first of all check if it's a functional key 204 | if ((unsigned char)buf[0] <= TB_KEY_SPACE || 205 | (unsigned char)buf[0] == TB_KEY_BACKSPACE2) 206 | { 207 | // fill event, pop buffer, return success */ 208 | event->ch = 0; 209 | event->key = (uint16_t)buf[0]; 210 | bytebuffer_truncate(inbuf, 1); 211 | return true; 212 | } 213 | 214 | // feh... we got utf8 here 215 | 216 | // check if there is all bytes 217 | if (len >= tb_utf8_char_length(buf[0])) { 218 | /* everything ok, fill event, pop buffer, return success */ 219 | tb_utf8_char_to_unicode(&event->ch, buf); 220 | event->key = 0; 221 | bytebuffer_truncate(inbuf, tb_utf8_char_length(buf[0])); 222 | return true; 223 | } 224 | 225 | // event isn't recognized, perhaps there is not enough bytes in utf8 226 | // sequence 227 | return false; 228 | } 229 | -------------------------------------------------------------------------------- /ImageUtil.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ImageUtil_HPP 2 | #define ImageUtil_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace ImageUtil { 8 | enum IMG_TYPE { 9 | IMG_PNG, 10 | IMG_JPG, 11 | IMG_BMP, 12 | IMG_GIF, 13 | IMG_TGA, 14 | IMG_PSD, 15 | IMG_UNKNOWN, 16 | }; 17 | 18 | IMG_TYPE checkHeader(FILE* fp) 19 | { 20 | unsigned char header[17] = {0}; 21 | 22 | fread(header, 1, sizeof(header), fp); 23 | fseek(fp, 0L, SEEK_SET); 24 | 25 | if(header[0] == 0x89 && header[1] == 0x50 && 26 | header[2] == 0x4E && header[3] == 0x47 && 27 | header[4] == 0x0D && header[5] == 0x0A && 28 | header[6] == 0x1A && header[7] == 0x0A) 29 | return IMG_TYPE::IMG_PNG; 30 | 31 | if(header[0] == 0xFF && header[1] == 0xD8) 32 | return IMG_TYPE::IMG_JPG; 33 | 34 | if(header[0] == 0x42 && header[1] == 0x4D && header[6] == 0x00 && 35 | header[7] == 0x00 && header[8] == 0x00 && header[9] == 0x00 && 36 | (header[14] == 0x28 || header[14] == 0x0C || header[14] == 40 || 37 | header[14] == 108 || header[14] == 124)) 38 | return IMG_TYPE::IMG_BMP; 39 | 40 | if(header[0] == 0x47 && header[1] == 0x49 && header[2] == 0x46) { 41 | for(int i = 3; i < 15; ++i) 42 | if(header[i] < 0x08 && header[i] != 0x00) return IMG_TYPE::IMG_GIF; 43 | } 44 | 45 | if(header[0] == 0x38 && header[1] == 0x42 && header[2] == 0x50 && 46 | header[3] == 0x53 && header[4] == 0x00 && header[5] == 0x01) 47 | return IMG_TYPE::IMG_PSD; 48 | 49 | // IMG_TGA 50 | union { 51 | unsigned char c[4]; 52 | int i; 53 | } ci; 54 | ci.c[0] = ci.c[1] = ci.c[2] = ci.c[3] = 0; 55 | if(header[1] == 0x01) { 56 | if(header[2] == 0x01 || header[2] == 0x09) { 57 | ci.c[0] = header[12], ci.c[1] = header[13]; 58 | if(ci.i >= 1) { 59 | ci.c[0] = header[14], ci.c[1] = header[15]; 60 | if(ci.i >= 1) { 61 | if(header[16] == 8 || header[16] == 16) 62 | return IMG_TYPE::IMG_TGA; 63 | } 64 | } 65 | } 66 | } 67 | else if(header[1] == 0x00) { 68 | if(header[2] == 0x02 || header[2] == 0x03 || header[2] == 0x0A || header[2] == 0x0B) { 69 | ci.c[0] = header[12], ci.c[1] = header[13]; 70 | if(ci.i >= 1) { 71 | ci.c[0] = header[14], ci.c[1] = header[15]; 72 | if(ci.i >= 1) { 73 | if(header[16] == 8 || header[16] == 15 || header[16] == 16 || 74 | header[16] == 24 || header[16] == 32) { 75 | return IMG_TYPE::IMG_TGA; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | return IMG_TYPE::IMG_UNKNOWN; 83 | } 84 | 85 | bool getSize(FILE* fp, IMG_TYPE type, int& width, int& height) 86 | { 87 | unsigned char buf[64] = {0}; 88 | union { 89 | unsigned char c[4]; 90 | int i; 91 | } ci; 92 | 93 | ci.c[0] = ci.c[1] = ci.c[2] = ci.c[3] = 0; 94 | width = -1, height = -1; 95 | 96 | switch(type) { 97 | case IMG_TYPE::IMG_PNG: 98 | fread(buf, 1, 33, fp); 99 | 100 | ci.c[3] = buf[0x08 + 0x08], ci.c[2] = buf[0x08 + 0x09]; 101 | ci.c[1] = buf[0x08 + 0x0A], ci.c[0] = buf[0x08 + 0x0B]; 102 | width = ci.i; 103 | 104 | ci.c[3] = buf[0x08 + 0x0C], ci.c[2] = buf[0x08 + 0x0D]; 105 | ci.c[1] = buf[0x08 + 0x0E], ci.c[0] = buf[0x08 + 0x0F]; 106 | height = ci.i; 107 | break; 108 | 109 | case IMG_TYPE::IMG_JPG: 110 | fseek(fp, 4, SEEK_SET); 111 | while(!(buf[0] == 0xFF && (buf[1] == 0xC0 || 112 | buf[1] == 0xC1 || 113 | buf[1] == 0xC2 || 114 | buf[1] == 0xC3))) { 115 | fseek(fp, ci.i - 2, SEEK_CUR); 116 | fread(buf, 1, 4, fp); 117 | ci.c[1] = buf[2], ci.c[0] = buf[3]; 118 | 119 | if(feof(fp)) break; 120 | } 121 | if(!feof(fp)) { 122 | fseek(fp, 1, SEEK_CUR); 123 | fread(buf, 1, 4, fp); 124 | 125 | ci.c[1] = buf[0], ci.c[0] = buf[1]; 126 | height = ci.i; 127 | 128 | ci.c[1] = buf[2], ci.c[0] = buf[3]; 129 | width = ci.i; 130 | } 131 | break; 132 | 133 | case IMG_TYPE::IMG_BMP: 134 | fseek(fp, 14, SEEK_SET); 135 | fread(buf, 1, 4, fp); 136 | ci.c[3] = buf[3], ci.c[2] = buf[2]; 137 | ci.c[1] = buf[1], ci.c[0] = buf[0]; 138 | 139 | // Windows 140 | if(ci.i == 40 || ci.i == 108 || ci.i == 124) { 141 | fread(buf, 1, 8, fp); 142 | ci.c[3] = buf[3], ci.c[2] = buf[2]; 143 | ci.c[1] = buf[1], ci.c[0] = buf[0]; 144 | width = ci.i; 145 | 146 | ci.c[3] = buf[7], ci.c[2] = buf[6]; 147 | ci.c[1] = buf[5], ci.c[0] = buf[4]; 148 | height = ci.i; 149 | } 150 | // OS/2 151 | else if(ci.i == 12) { 152 | fread(buf, 1, 4, fp); 153 | ci.c[1] = buf[1], ci.c[0] = buf[0]; 154 | width = ci.i; 155 | 156 | ci.c[1] = buf[3], ci.c[0] = buf[2]; 157 | height = ci.i; 158 | } 159 | break; 160 | 161 | case IMG_TYPE::IMG_GIF: 162 | fseek(fp, 6, SEEK_SET); 163 | fread(buf, 1, 4, fp); 164 | 165 | ci.c[1] = buf[1], ci.c[0] = buf[0]; 166 | width = ci.i; 167 | 168 | ci.c[1] = buf[3], ci.c[0] = buf[2]; 169 | height = ci.i; 170 | break; 171 | 172 | case IMG_TYPE::IMG_TGA: 173 | fseek(fp, 12, SEEK_SET); 174 | fread(buf, 1, 4, fp); 175 | ci.c[0] = buf[0], ci.c[1] = buf[1]; 176 | width = ci.i; 177 | ci.c[0] = buf[2], ci.c[1] = buf[3]; 178 | height = ci.i; 179 | break; 180 | 181 | case IMG_TYPE::IMG_PSD: 182 | fseek(fp, 14, SEEK_SET); 183 | fread(buf, 1, 8, fp); 184 | 185 | ci.c[3] = buf[0], ci.c[2] = buf[1]; 186 | ci.c[1] = buf[2], ci.c[0] = buf[3]; 187 | height = ci.i; 188 | 189 | ci.c[3] = buf[4], ci.c[2] = buf[5]; 190 | ci.c[1] = buf[6], ci.c[0] = buf[7]; 191 | width = ci.i; 192 | break; 193 | 194 | case IMG_TYPE::IMG_UNKNOWN: 195 | default: 196 | return false; 197 | }; 198 | 199 | return true; 200 | } 201 | 202 | void CalcScaleSize_KeepAspectRatio(int srcW, int srcH, 203 | int scaleW, int scaleH, 204 | int& dstW, int& dstH) { 205 | double w_ratio = static_cast(scaleW) / static_cast(srcW); 206 | double h_ratio = static_cast(scaleH) / static_cast(srcH); 207 | double ratio = 0.0; 208 | 209 | if(w_ratio > h_ratio) ratio = h_ratio; 210 | else ratio = w_ratio; 211 | 212 | dstW = static_cast(floor(srcW * ratio)); 213 | dstH = static_cast(floor(srcH * ratio)); 214 | } 215 | }; 216 | 217 | #endif 218 | -------------------------------------------------------------------------------- /termbox/term.inl: -------------------------------------------------------------------------------- 1 | enum { 2 | T_ENTER_CA, 3 | T_EXIT_CA, 4 | T_SHOW_CURSOR, 5 | T_HIDE_CURSOR, 6 | T_CLEAR_SCREEN, 7 | T_SGR0, 8 | T_UNDERLINE, 9 | T_BOLD, 10 | T_BLINK, 11 | T_REVERSE, 12 | T_ENTER_KEYPAD, 13 | T_EXIT_KEYPAD, 14 | T_ENTER_MOUSE, 15 | T_EXIT_MOUSE, 16 | T_FUNCS_NUM, 17 | }; 18 | 19 | #define ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" 20 | #define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" 21 | 22 | #define EUNSUPPORTED_TERM -1 23 | 24 | // rxvt-256color 25 | static const char *rxvt_256color_keys[] = { 26 | "\033[11~","\033[12~","\033[13~","\033[14~","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[7~","\033[8~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0 27 | }; 28 | static const char *rxvt_256color_funcs[] = { 29 | "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, 30 | }; 31 | 32 | // Eterm 33 | static const char *eterm_keys[] = { 34 | "\033[11~","\033[12~","\033[13~","\033[14~","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[7~","\033[8~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0 35 | }; 36 | static const char *eterm_funcs[] = { 37 | "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "", 38 | }; 39 | 40 | // screen 41 | static const char *screen_keys[] = { 42 | "\033OP","\033OQ","\033OR","\033OS","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[1~","\033[4~","\033[5~","\033[6~","\033OA","\033OB","\033OD","\033OC", 0 43 | }; 44 | static const char *screen_funcs[] = { 45 | "\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l", "\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, 46 | }; 47 | 48 | // rxvt-unicode 49 | static const char *rxvt_unicode_keys[] = { 50 | "\033[11~","\033[12~","\033[13~","\033[14~","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[7~","\033[8~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0 51 | }; 52 | static const char *rxvt_unicode_funcs[] = { 53 | "\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, 54 | }; 55 | 56 | // linux 57 | static const char *linux_keys[] = { 58 | "\033[[A","\033[[B","\033[[C","\033[[D","\033[[E","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[1~","\033[4~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0 59 | }; 60 | static const char *linux_funcs[] = { 61 | "", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J", "\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "", 62 | }; 63 | 64 | // xterm 65 | static const char *xterm_keys[] = { 66 | "\033OP","\033OQ","\033OR","\033OS","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033OH","\033OF","\033[5~","\033[6~","\033OA","\033OB","\033OD","\033OC", 0 67 | }; 68 | static const char *xterm_funcs[] = { 69 | "\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l", "\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, 70 | }; 71 | 72 | static struct term { 73 | const char *name; 74 | const char **keys; 75 | const char **funcs; 76 | } terms[] = { 77 | {"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs}, 78 | {"Eterm", eterm_keys, eterm_funcs}, 79 | {"screen", screen_keys, screen_funcs}, 80 | {"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs}, 81 | {"linux", linux_keys, linux_funcs}, 82 | {"xterm", xterm_keys, xterm_funcs}, 83 | {0, 0, 0}, 84 | }; 85 | 86 | static bool init_from_terminfo = false; 87 | static const char **keys; 88 | static const char **funcs; 89 | 90 | static int try_compatible(const char *term, const char *name, 91 | const char **tkeys, const char **tfuncs) 92 | { 93 | if (strstr(term, name)) { 94 | keys = tkeys; 95 | funcs = tfuncs; 96 | return 0; 97 | } 98 | 99 | return EUNSUPPORTED_TERM; 100 | } 101 | 102 | static int init_term_builtin(void) 103 | { 104 | int i; 105 | const char *term = getenv("TERM"); 106 | 107 | if (term) { 108 | for (i = 0; terms[i].name; i++) { 109 | if (!strcmp(terms[i].name, term)) { 110 | keys = terms[i].keys; 111 | funcs = terms[i].funcs; 112 | return 0; 113 | } 114 | } 115 | 116 | /* let's do some heuristic, maybe it's a compatible terminal */ 117 | if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0) 118 | return 0; 119 | if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0) 120 | return 0; 121 | if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0) 122 | return 0; 123 | if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0) 124 | return 0; 125 | if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0) 126 | return 0; 127 | /* let's assume that 'cygwin' is xterm compatible */ 128 | if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0) 129 | return 0; 130 | } 131 | 132 | return EUNSUPPORTED_TERM; 133 | } 134 | 135 | //---------------------------------------------------------------------- 136 | // terminfo 137 | //---------------------------------------------------------------------- 138 | 139 | static char *read_file(const char *file) { 140 | FILE *f = fopen(file, "rb"); 141 | if (!f) 142 | return 0; 143 | 144 | struct stat st; 145 | if (fstat(fileno(f), &st) != 0) { 146 | fclose(f); 147 | return 0; 148 | } 149 | 150 | char *data = malloc(st.st_size); 151 | if (!data) { 152 | fclose(f); 153 | return 0; 154 | } 155 | 156 | if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) { 157 | fclose(f); 158 | free(data); 159 | return 0; 160 | } 161 | 162 | fclose(f); 163 | return data; 164 | } 165 | 166 | static char *terminfo_try_path(const char *path, const char *term) { 167 | char tmp[4096]; 168 | snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term); 169 | tmp[sizeof(tmp)-1] = '\0'; 170 | char *data = read_file(tmp); 171 | if (data) { 172 | return data; 173 | } 174 | 175 | // fallback to darwin specific dirs structure 176 | snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term); 177 | tmp[sizeof(tmp)-1] = '\0'; 178 | return read_file(tmp); 179 | } 180 | 181 | static char *load_terminfo(void) { 182 | char tmp[4096]; 183 | const char *term = getenv("TERM"); 184 | if (!term) { 185 | return 0; 186 | } 187 | 188 | // if TERMINFO is set, no other directory should be searched 189 | const char *terminfo = getenv("TERMINFO"); 190 | if (terminfo) { 191 | return terminfo_try_path(terminfo, term); 192 | } 193 | 194 | // next, consider ~/.terminfo 195 | const char *home = getenv("HOME"); 196 | if (home) { 197 | snprintf(tmp, sizeof(tmp), "%s/.terminfo", home); 198 | tmp[sizeof(tmp)-1] = '\0'; 199 | char *data = terminfo_try_path(tmp, term); 200 | if (data) 201 | return data; 202 | } 203 | 204 | // next, TERMINFO_DIRS 205 | const char *dirs = getenv("TERMINFO_DIRS"); 206 | if (dirs) { 207 | snprintf(tmp, sizeof(tmp), "%s", dirs); 208 | tmp[sizeof(tmp)-1] = '\0'; 209 | char *dir = strtok(tmp, ":"); 210 | while (dir) { 211 | const char *cdir = dir; 212 | if (strcmp(cdir, "") == 0) { 213 | cdir = "/usr/share/terminfo"; 214 | } 215 | char *data = terminfo_try_path(cdir, term); 216 | if (data) 217 | return data; 218 | dir = strtok(0, ":"); 219 | } 220 | } 221 | 222 | // fallback to /usr/share/terminfo 223 | return terminfo_try_path("/usr/share/terminfo", term); 224 | } 225 | 226 | #define TI_MAGIC 0432 227 | #define TI_ALT_MAGIC 542 228 | #define TI_HEADER_LENGTH 12 229 | #define TB_KEYS_NUM 22 230 | 231 | static const char *terminfo_copy_string(char *data, int str, int table) { 232 | const int16_t off = *(int16_t*)(data + str); 233 | const char *src = data + table + off; 234 | int len = strlen(src); 235 | char *dst = malloc(len+1); 236 | strcpy(dst, src); 237 | return dst; 238 | } 239 | 240 | static const int16_t ti_funcs[] = { 241 | 28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88, 242 | }; 243 | 244 | static const int16_t ti_keys[] = { 245 | 66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69, 246 | 70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61, 247 | 79, 83, 248 | }; 249 | 250 | static int init_term(void) { 251 | int i; 252 | char *data = load_terminfo(); 253 | if (!data) { 254 | init_from_terminfo = false; 255 | return init_term_builtin(); 256 | } 257 | 258 | int16_t *header = (int16_t*)data; 259 | 260 | const int number_sec_len = header[0] == TI_ALT_MAGIC ? 4 : 2; 261 | 262 | if ((header[1] + header[2]) % 2) { 263 | // old quirk to align everything on word boundaries 264 | header[2] += 1; 265 | } 266 | 267 | const int str_offset = TI_HEADER_LENGTH + 268 | header[1] + header[2] + number_sec_len * header[3]; 269 | const int table_offset = str_offset + 2 * header[4]; 270 | 271 | keys = malloc(sizeof(const char*) * (TB_KEYS_NUM+1)); 272 | for (i = 0; i < TB_KEYS_NUM; i++) { 273 | keys[i] = terminfo_copy_string(data, 274 | str_offset + 2 * ti_keys[i], table_offset); 275 | } 276 | keys[TB_KEYS_NUM] = 0; 277 | 278 | funcs = malloc(sizeof(const char*) * T_FUNCS_NUM); 279 | // the last two entries are reserved for mouse. because the table offset is 280 | // not there, the two entries have to fill in manually 281 | for (i = 0; i < T_FUNCS_NUM-2; i++) { 282 | funcs[i] = terminfo_copy_string(data, 283 | str_offset + 2 * ti_funcs[i], table_offset); 284 | } 285 | 286 | funcs[T_FUNCS_NUM-2] = ENTER_MOUSE_SEQ; 287 | funcs[T_FUNCS_NUM-1] = EXIT_MOUSE_SEQ; 288 | 289 | init_from_terminfo = true; 290 | free(data); 291 | return 0; 292 | } 293 | 294 | static void shutdown_term(void) { 295 | if (init_from_terminfo) { 296 | int i; 297 | for (i = 0; i < TB_KEYS_NUM; i++) { 298 | free((void*)keys[i]); 299 | } 300 | // the last two entries are reserved for mouse. because the table offset 301 | // is not there, the two entries have to fill in manually and do not 302 | // need to be freed. 303 | for (i = 0; i < T_FUNCS_NUM-2; i++) { 304 | free((void*)funcs[i]); 305 | } 306 | free(keys); 307 | free(funcs); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /tsl/robin_growth_policy.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Tessil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #ifndef TSL_ROBIN_GROWTH_POLICY_H 25 | #define TSL_ROBIN_GROWTH_POLICY_H 26 | 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | 39 | #ifdef TSL_DEBUG 40 | # define tsl_rh_assert(expr) assert(expr) 41 | #else 42 | # define tsl_rh_assert(expr) (static_cast(0)) 43 | #endif 44 | 45 | 46 | /** 47 | * If exceptions are enabled, throw the exception passed in parameter, otherwise call std::terminate. 48 | */ 49 | #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (defined (_MSC_VER) && defined (_CPPUNWIND))) && !defined(TSL_NO_EXCEPTIONS) 50 | # define TSL_RH_THROW_OR_TERMINATE(ex, msg) throw ex(msg) 51 | #else 52 | # ifdef NDEBUG 53 | # define TSL_RH_THROW_OR_TERMINATE(ex, msg) std::terminate() 54 | # else 55 | # include 56 | # define TSL_RH_THROW_OR_TERMINATE(ex, msg) do { std::fprintf(stderr, msg); std::terminate(); } while(0) 57 | # endif 58 | #endif 59 | 60 | 61 | #if defined(__GNUC__) || defined(__clang__) 62 | # define TSL_RH_LIKELY(exp) (__builtin_expect(!!(exp), true)) 63 | #else 64 | # define TSL_RH_LIKELY(exp) (exp) 65 | #endif 66 | 67 | 68 | namespace tsl { 69 | namespace rh { 70 | 71 | /** 72 | * Grow the hash table by a factor of GrowthFactor keeping the bucket count to a power of two. It allows 73 | * the table to use a mask operation instead of a modulo operation to map a hash to a bucket. 74 | * 75 | * GrowthFactor must be a power of two >= 2. 76 | */ 77 | template 78 | class power_of_two_growth_policy { 79 | public: 80 | /** 81 | * Called on the hash table creation and on rehash. The number of buckets for the table is passed in parameter. 82 | * This number is a minimum, the policy may update this value with a higher value if needed (but not lower). 83 | * 84 | * If 0 is given, min_bucket_count_in_out must still be 0 after the policy creation and 85 | * bucket_for_hash must always return 0 in this case. 86 | */ 87 | explicit power_of_two_growth_policy(std::size_t& min_bucket_count_in_out) { 88 | if(min_bucket_count_in_out > max_bucket_count()) { 89 | TSL_RH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maxmimum size."); 90 | } 91 | 92 | if(min_bucket_count_in_out > 0) { 93 | min_bucket_count_in_out = round_up_to_power_of_two(min_bucket_count_in_out); 94 | m_mask = min_bucket_count_in_out - 1; 95 | } 96 | else { 97 | m_mask = 0; 98 | } 99 | } 100 | 101 | /** 102 | * Return the bucket [0, bucket_count()) to which the hash belongs. 103 | * If bucket_count() is 0, it must always return 0. 104 | */ 105 | std::size_t bucket_for_hash(std::size_t hash) const noexcept { 106 | return hash & m_mask; 107 | } 108 | 109 | /** 110 | * Return the number of buckets that should be used on next growth. 111 | */ 112 | std::size_t next_bucket_count() const { 113 | if((m_mask + 1) > max_bucket_count() / GrowthFactor) { 114 | TSL_RH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maxmimum size."); 115 | } 116 | 117 | return (m_mask + 1) * GrowthFactor; 118 | } 119 | 120 | /** 121 | * Return the maximum number of buckets supported by the policy. 122 | */ 123 | std::size_t max_bucket_count() const { 124 | // Largest power of two. 125 | return (std::numeric_limits::max() / 2) + 1; 126 | } 127 | 128 | /** 129 | * Reset the growth policy as if it was created with a bucket count of 0. 130 | * After a clear, the policy must always return 0 when bucket_for_hash is called. 131 | */ 132 | void clear() noexcept { 133 | m_mask = 0; 134 | } 135 | 136 | private: 137 | static std::size_t round_up_to_power_of_two(std::size_t value) { 138 | if(is_power_of_two(value)) { 139 | return value; 140 | } 141 | 142 | if(value == 0) { 143 | return 1; 144 | } 145 | 146 | --value; 147 | for(std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { 148 | value |= value >> i; 149 | } 150 | 151 | return value + 1; 152 | } 153 | 154 | static constexpr bool is_power_of_two(std::size_t value) { 155 | return value != 0 && (value & (value - 1)) == 0; 156 | } 157 | 158 | protected: 159 | static_assert(is_power_of_two(GrowthFactor) && GrowthFactor >= 2, "GrowthFactor must be a power of two >= 2."); 160 | 161 | std::size_t m_mask; 162 | }; 163 | 164 | 165 | /** 166 | * Grow the hash table by GrowthFactor::num / GrowthFactor::den and use a modulo to map a hash 167 | * to a bucket. Slower but it can be useful if you want a slower growth. 168 | */ 169 | template> 170 | class mod_growth_policy { 171 | public: 172 | explicit mod_growth_policy(std::size_t& min_bucket_count_in_out) { 173 | if(min_bucket_count_in_out > max_bucket_count()) { 174 | TSL_RH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maxmimum size."); 175 | } 176 | 177 | if(min_bucket_count_in_out > 0) { 178 | m_mod = min_bucket_count_in_out; 179 | } 180 | else { 181 | m_mod = 1; 182 | } 183 | } 184 | 185 | std::size_t bucket_for_hash(std::size_t hash) const noexcept { 186 | return hash % m_mod; 187 | } 188 | 189 | std::size_t next_bucket_count() const { 190 | if(m_mod == max_bucket_count()) { 191 | TSL_RH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maxmimum size."); 192 | } 193 | 194 | const double next_bucket_count = std::ceil(double(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR); 195 | if(!std::isnormal(next_bucket_count)) { 196 | TSL_RH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maxmimum size."); 197 | } 198 | 199 | if(next_bucket_count > double(max_bucket_count())) { 200 | return max_bucket_count(); 201 | } 202 | else { 203 | return std::size_t(next_bucket_count); 204 | } 205 | } 206 | 207 | std::size_t max_bucket_count() const { 208 | return MAX_BUCKET_COUNT; 209 | } 210 | 211 | void clear() noexcept { 212 | m_mod = 1; 213 | } 214 | 215 | private: 216 | static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = 1.0 * GrowthFactor::num / GrowthFactor::den; 217 | static const std::size_t MAX_BUCKET_COUNT = 218 | std::size_t(double( 219 | std::numeric_limits::max() / REHASH_SIZE_MULTIPLICATION_FACTOR 220 | )); 221 | 222 | static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, "Growth factor should be >= 1.1."); 223 | 224 | std::size_t m_mod; 225 | }; 226 | 227 | 228 | 229 | namespace detail { 230 | 231 | static constexpr const std::array PRIMES = {{ 232 | 1ul, 5ul, 17ul, 29ul, 37ul, 53ul, 67ul, 79ul, 97ul, 131ul, 193ul, 257ul, 389ul, 521ul, 769ul, 1031ul, 233 | 1543ul, 2053ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 234 | 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 235 | 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul 236 | }}; 237 | 238 | template 239 | static constexpr std::size_t mod(std::size_t hash) { return hash % PRIMES[IPrime]; } 240 | 241 | // MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows for faster modulo as the 242 | // compiler can optimize the modulo code better with a constant known at the compilation. 243 | static constexpr const std::array MOD_PRIME = {{ 244 | &mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, &mod<7>, &mod<8>, &mod<9>, &mod<10>, 245 | &mod<11>, &mod<12>, &mod<13>, &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>, 246 | &mod<21>, &mod<22>, &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, &mod<28>, &mod<29>, &mod<30>, 247 | &mod<31>, &mod<32>, &mod<33>, &mod<34>, &mod<35>, &mod<36>, &mod<37> , &mod<38>, &mod<39> 248 | }}; 249 | 250 | } 251 | 252 | /** 253 | * Grow the hash table by using prime numbers as bucket count. Slower than tsl::rh::power_of_two_growth_policy in 254 | * general but will probably distribute the values around better in the buckets with a poor hash function. 255 | * 256 | * To allow the compiler to optimize the modulo operation, a lookup table is used with constant primes numbers. 257 | * 258 | * With a switch the code would look like: 259 | * \code 260 | * switch(iprime) { // iprime is the current prime of the hash table 261 | * case 0: hash % 5ul; 262 | * break; 263 | * case 1: hash % 17ul; 264 | * break; 265 | * case 2: hash % 29ul; 266 | * break; 267 | * ... 268 | * } 269 | * \endcode 270 | * 271 | * Due to the constant variable in the modulo the compiler is able to optimize the operation 272 | * by a series of multiplications, substractions and shifts. 273 | * 274 | * The 'hash % 5' could become something like 'hash - (hash * 0xCCCCCCCD) >> 34) * 5' in a 64 bits environement. 275 | */ 276 | class prime_growth_policy { 277 | public: 278 | explicit prime_growth_policy(std::size_t& min_bucket_count_in_out) { 279 | auto it_prime = std::lower_bound(detail::PRIMES.begin(), 280 | detail::PRIMES.end(), min_bucket_count_in_out); 281 | if(it_prime == detail::PRIMES.end()) { 282 | TSL_RH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maxmimum size."); 283 | } 284 | 285 | m_iprime = static_cast(std::distance(detail::PRIMES.begin(), it_prime)); 286 | if(min_bucket_count_in_out > 0) { 287 | min_bucket_count_in_out = *it_prime; 288 | } 289 | else { 290 | min_bucket_count_in_out = 0; 291 | } 292 | } 293 | 294 | std::size_t bucket_for_hash(std::size_t hash) const noexcept { 295 | return detail::MOD_PRIME[m_iprime](hash); 296 | } 297 | 298 | std::size_t next_bucket_count() const { 299 | if(m_iprime + 1 >= detail::PRIMES.size()) { 300 | TSL_RH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maxmimum size."); 301 | } 302 | 303 | return detail::PRIMES[m_iprime + 1]; 304 | } 305 | 306 | std::size_t max_bucket_count() const { 307 | return detail::PRIMES.back(); 308 | } 309 | 310 | void clear() noexcept { 311 | m_iprime = 0; 312 | } 313 | 314 | private: 315 | unsigned int m_iprime; 316 | 317 | static_assert(std::numeric_limits::max() >= detail::PRIMES.size(), 318 | "The type of m_iprime is not big enough."); 319 | }; 320 | 321 | } 322 | } 323 | 324 | #endif 325 | -------------------------------------------------------------------------------- /termbox/termbox.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /* for shared objects */ 7 | #if __GNUC__ >= 4 8 | #define SO_IMPORT __attribute__((visibility("default"))) 9 | #else 10 | #define SO_IMPORT 11 | #endif 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | /* Key constants. See also struct tb_event's key field. 18 | * 19 | * These are a safe subset of terminfo keys, which exist on all popular 20 | * terminals. Termbox uses only them to stay truly portable. 21 | */ 22 | #define TB_KEY_F1 (0xFFFF-0) 23 | #define TB_KEY_F2 (0xFFFF-1) 24 | #define TB_KEY_F3 (0xFFFF-2) 25 | #define TB_KEY_F4 (0xFFFF-3) 26 | #define TB_KEY_F5 (0xFFFF-4) 27 | #define TB_KEY_F6 (0xFFFF-5) 28 | #define TB_KEY_F7 (0xFFFF-6) 29 | #define TB_KEY_F8 (0xFFFF-7) 30 | #define TB_KEY_F9 (0xFFFF-8) 31 | #define TB_KEY_F10 (0xFFFF-9) 32 | #define TB_KEY_F11 (0xFFFF-10) 33 | #define TB_KEY_F12 (0xFFFF-11) 34 | #define TB_KEY_INSERT (0xFFFF-12) 35 | #define TB_KEY_DELETE (0xFFFF-13) 36 | #define TB_KEY_HOME (0xFFFF-14) 37 | #define TB_KEY_END (0xFFFF-15) 38 | #define TB_KEY_PGUP (0xFFFF-16) 39 | #define TB_KEY_PGDN (0xFFFF-17) 40 | #define TB_KEY_ARROW_UP (0xFFFF-18) 41 | #define TB_KEY_ARROW_DOWN (0xFFFF-19) 42 | #define TB_KEY_ARROW_LEFT (0xFFFF-20) 43 | #define TB_KEY_ARROW_RIGHT (0xFFFF-21) 44 | #define TB_KEY_MOUSE_LEFT (0xFFFF-22) 45 | #define TB_KEY_MOUSE_RIGHT (0xFFFF-23) 46 | #define TB_KEY_MOUSE_MIDDLE (0xFFFF-24) 47 | #define TB_KEY_MOUSE_RELEASE (0xFFFF-25) 48 | #define TB_KEY_MOUSE_WHEEL_UP (0xFFFF-26) 49 | #define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27) 50 | 51 | /* These are all ASCII code points below SPACE character and a BACKSPACE key. */ 52 | #define TB_KEY_CTRL_TILDE 0x00 53 | #define TB_KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */ 54 | #define TB_KEY_CTRL_A 0x01 55 | #define TB_KEY_CTRL_B 0x02 56 | #define TB_KEY_CTRL_C 0x03 57 | #define TB_KEY_CTRL_D 0x04 58 | #define TB_KEY_CTRL_E 0x05 59 | #define TB_KEY_CTRL_F 0x06 60 | #define TB_KEY_CTRL_G 0x07 61 | #define TB_KEY_BACKSPACE 0x08 62 | #define TB_KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */ 63 | #define TB_KEY_TAB 0x09 64 | #define TB_KEY_CTRL_I 0x09 /* clash with 'TAB' */ 65 | #define TB_KEY_CTRL_J 0x0A 66 | #define TB_KEY_CTRL_K 0x0B 67 | #define TB_KEY_CTRL_L 0x0C 68 | #define TB_KEY_ENTER 0x0D 69 | #define TB_KEY_CTRL_M 0x0D /* clash with 'ENTER' */ 70 | #define TB_KEY_CTRL_N 0x0E 71 | #define TB_KEY_CTRL_O 0x0F 72 | #define TB_KEY_CTRL_P 0x10 73 | #define TB_KEY_CTRL_Q 0x11 74 | #define TB_KEY_CTRL_R 0x12 75 | #define TB_KEY_CTRL_S 0x13 76 | #define TB_KEY_CTRL_T 0x14 77 | #define TB_KEY_CTRL_U 0x15 78 | #define TB_KEY_CTRL_V 0x16 79 | #define TB_KEY_CTRL_W 0x17 80 | #define TB_KEY_CTRL_X 0x18 81 | #define TB_KEY_CTRL_Y 0x19 82 | #define TB_KEY_CTRL_Z 0x1A 83 | #define TB_KEY_ESC 0x1B 84 | #define TB_KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */ 85 | #define TB_KEY_CTRL_3 0x1B /* clash with 'ESC' */ 86 | #define TB_KEY_CTRL_4 0x1C 87 | #define TB_KEY_CTRL_BACKSLASH 0x1C /* clash with 'CTRL_4' */ 88 | #define TB_KEY_CTRL_5 0x1D 89 | #define TB_KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */ 90 | #define TB_KEY_CTRL_6 0x1E 91 | #define TB_KEY_CTRL_7 0x1F 92 | #define TB_KEY_CTRL_SLASH 0x1F /* clash with 'CTRL_7' */ 93 | #define TB_KEY_CTRL_UNDERSCORE 0x1F /* clash with 'CTRL_7' */ 94 | #define TB_KEY_SPACE 0x20 95 | #define TB_KEY_BACKSPACE2 0x7F 96 | #define TB_KEY_CTRL_8 0x7F /* clash with 'BACKSPACE2' */ 97 | 98 | /* These are non-existing ones. 99 | * 100 | * #define TB_KEY_CTRL_1 clash with '1' 101 | * #define TB_KEY_CTRL_9 clash with '9' 102 | * #define TB_KEY_CTRL_0 clash with '0' 103 | */ 104 | 105 | /* 106 | * Alt modifier constant, see tb_event.mod field and tb_select_input_mode function. 107 | * Mouse-motion modifier 108 | */ 109 | #define TB_MOD_ALT 0x01 110 | #define TB_MOD_MOTION 0x02 111 | 112 | /* Colors (see struct tb_cell's fg and bg fields). */ 113 | #define TB_DEFAULT 0x00 114 | #define TB_BLACK 0x01 115 | #define TB_RED 0x02 116 | #define TB_GREEN 0x03 117 | #define TB_YELLOW 0x04 118 | #define TB_BLUE 0x05 119 | #define TB_MAGENTA 0x06 120 | #define TB_CYAN 0x07 121 | #define TB_WHITE 0x08 122 | 123 | /* Attributes, it is possible to use multiple attributes by combining them 124 | * using bitwise OR ('|'). Although, colors cannot be combined. But you can 125 | * combine attributes and a single color. See also struct tb_cell's fg and bg 126 | * fields. 127 | */ 128 | #define TB_BOLD 0x0100 129 | #define TB_UNDERLINE 0x0200 130 | #define TB_REVERSE 0x0400 131 | 132 | /* A cell, single conceptual entity on the terminal screen. The terminal screen 133 | * is basically a 2d array of cells. It has the following fields: 134 | * - 'ch' is a unicode character 135 | * - 'fg' foreground color and attributes 136 | * - 'bg' background color and attributes 137 | */ 138 | struct tb_cell { 139 | uint32_t ch; 140 | uint16_t fg; 141 | uint16_t bg; 142 | }; 143 | 144 | #define TB_EVENT_KEY 1 145 | #define TB_EVENT_RESIZE 2 146 | #define TB_EVENT_MOUSE 3 147 | 148 | /* An event, single interaction from the user. The 'mod' and 'ch' fields are 149 | * valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type' 150 | * is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is 151 | * TB_EVENT_MOUSE. The 'key' field is valid if 'type' is either TB_EVENT_KEY 152 | * or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only 153 | * one of them can be non-zero at a time. 154 | */ 155 | struct tb_event { 156 | uint8_t type; 157 | uint8_t mod; /* modifiers to either 'key' or 'ch' below */ 158 | uint16_t key; /* one of the TB_KEY_* constants */ 159 | uint32_t ch; /* unicode character */ 160 | int32_t w; 161 | int32_t h; 162 | int32_t x; 163 | int32_t y; 164 | }; 165 | 166 | /* Error codes returned by tb_init(). All of them are self-explanatory, except 167 | * the pipe trap error. Termbox uses unix pipes in order to deliver a message 168 | * from a signal handler (SIGWINCH) to the main event reading loop. Honestly in 169 | * most cases you should just check the returned code as < 0. 170 | */ 171 | #define TB_EUNSUPPORTED_TERMINAL -1 172 | #define TB_EFAILED_TO_OPEN_TTY -2 173 | #define TB_EPIPE_TRAP_ERROR -3 174 | 175 | /* Initializes the termbox library. This function should be called before any 176 | * other functions. Function tb_init is same as tb_init_file("/dev/tty"). 177 | * After successful initialization, the library must be 178 | * finalized using the tb_shutdown() function. 179 | */ 180 | SO_IMPORT int tb_init(void); 181 | SO_IMPORT int tb_init_file(const char* name); 182 | SO_IMPORT int tb_init_fd(int inout); 183 | SO_IMPORT void tb_shutdown(void); 184 | 185 | /* Returns the size of the internal back buffer (which is the same as 186 | * terminal's window size in characters). The internal buffer can be resized 187 | * after tb_clear() or tb_present() function calls. Both dimensions have an 188 | * unspecified negative value when called before tb_init() or after 189 | * tb_shutdown(). 190 | */ 191 | SO_IMPORT int tb_width(void); 192 | SO_IMPORT int tb_height(void); 193 | 194 | /* Clears the internal back buffer using TB_DEFAULT color or the 195 | * color/attributes set by tb_set_clear_attributes() function. 196 | */ 197 | SO_IMPORT void tb_clear(void); 198 | SO_IMPORT void tb_set_clear_attributes(uint16_t fg, uint16_t bg); 199 | 200 | /* Synchronizes the internal back buffer with the terminal. */ 201 | SO_IMPORT void tb_present(void); 202 | 203 | #define TB_HIDE_CURSOR -1 204 | 205 | /* Sets the position of the cursor. Upper-left character is (0, 0). If you pass 206 | * TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor 207 | * is hidden by default. 208 | */ 209 | SO_IMPORT void tb_set_cursor(int cx, int cy); 210 | 211 | /* Changes cell's parameters in the internal back buffer at the specified 212 | * position. 213 | */ 214 | SO_IMPORT void tb_put_cell(int x, int y, const struct tb_cell *cell); 215 | SO_IMPORT void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg); 216 | 217 | SO_IMPORT void tb_put_cell_front(int x, int y, const struct tb_cell *cell); 218 | SO_IMPORT void tb_change_cell_front(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg); 219 | 220 | /* Copies the buffer from 'cells' at the specified position, assuming the 221 | * buffer is a two-dimensional array of size ('w' x 'h'), represented as a 222 | * one-dimensional buffer containing lines of cells starting from the top. 223 | * 224 | * (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own) 225 | */ 226 | SO_IMPORT void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells); 227 | 228 | /* Returns a pointer to internal cell back buffer. You can get its dimensions 229 | * using tb_width() and tb_height() functions. The pointer stays valid as long 230 | * as no tb_clear() and tb_present() calls are made. The buffer is 231 | * one-dimensional buffer containing lines of cells starting from the top. 232 | */ 233 | SO_IMPORT struct tb_cell *tb_cell_buffer(void); 234 | 235 | #define TB_INPUT_CURRENT 0 /* 000 */ 236 | #define TB_INPUT_ESC 1 /* 001 */ 237 | #define TB_INPUT_ALT 2 /* 010 */ 238 | #define TB_INPUT_MOUSE 4 /* 100 */ 239 | 240 | /* Sets the termbox input mode. Termbox has two input modes: 241 | * 1. Esc input mode. 242 | * When ESC sequence is in the buffer and it doesn't match any known 243 | * ESC sequence => ESC means TB_KEY_ESC. 244 | * 2. Alt input mode. 245 | * When ESC sequence is in the buffer and it doesn't match any known 246 | * sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event. 247 | * 248 | * You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the 249 | * modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes 250 | * were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some 251 | * reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it 252 | * will behave as if only TB_INPUT_ESC was selected. 253 | * 254 | * If 'mode' is TB_INPUT_CURRENT, it returns the current input mode. 255 | * 256 | * Default termbox input mode is TB_INPUT_ESC. 257 | */ 258 | SO_IMPORT int tb_select_input_mode(int mode); 259 | 260 | #define TB_OUTPUT_CURRENT 0 261 | #define TB_OUTPUT_NORMAL 1 262 | #define TB_OUTPUT_256 2 263 | #define TB_OUTPUT_216 3 264 | #define TB_OUTPUT_GRAYSCALE 4 265 | 266 | /* Sets the termbox output mode. Termbox has three output options: 267 | * 1. TB_OUTPUT_NORMAL => [1..8] 268 | * This mode provides 8 different colors: 269 | * black, red, green, yellow, blue, magenta, cyan, white 270 | * Shortcut: TB_BLACK, TB_RED, ... 271 | * Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE 272 | * 273 | * Example usage: 274 | * tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED); 275 | * 276 | * 2. TB_OUTPUT_256 => [0..256] 277 | * In this mode you can leverage the 256 terminal mode: 278 | * 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL 279 | * 0x08 - 0x0f: TB_* | TB_BOLD 280 | * 0x10 - 0xe7: 216 different colors 281 | * 0xe8 - 0xff: 24 different shades of grey 282 | * 283 | * Example usage: 284 | * tb_change_cell(x, y, '@', 184, 240); 285 | * tb_change_cell(x, y, '@', 0xb8, 0xf0); 286 | * 287 | * 3. TB_OUTPUT_216 => [0..216] 288 | * This mode supports the 3rd range of the 256 mode only. 289 | * But you don't need to provide an offset. 290 | * 291 | * 4. TB_OUTPUT_GRAYSCALE => [0..23] 292 | * This mode supports the 4th range of the 256 mode only. 293 | * But you dont need to provide an offset. 294 | * 295 | * Execute build/src/demo/output to see its impact on your terminal. 296 | * 297 | * If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode. 298 | * 299 | * Default termbox output mode is TB_OUTPUT_NORMAL. 300 | */ 301 | SO_IMPORT int tb_select_output_mode(int mode); 302 | 303 | /* Wait for an event up to 'timeout' milliseconds and fill the 'event' 304 | * structure with it, when the event is available. Returns the type of the 305 | * event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case 306 | * there were no event during 'timeout' period. 307 | */ 308 | SO_IMPORT int tb_peek_event(struct tb_event *event, int timeout); 309 | 310 | /* Wait for an event forever and fill the 'event' structure with it, when the 311 | * event is available. Returns the type of the event (one of TB_EVENT_* 312 | * constants) or -1 if there was an error. 313 | */ 314 | SO_IMPORT int tb_poll_event(struct tb_event *event); 315 | 316 | /* Utility utf8 functions. */ 317 | #define TB_EOF -1 318 | SO_IMPORT int tb_utf8_char_length(char c); 319 | SO_IMPORT int tb_utf8_char_to_unicode(uint32_t *out, const char *c); 320 | SO_IMPORT int tb_utf8_unicode_to_char(char *out, uint32_t c); 321 | 322 | /* wcwidth */ 323 | SO_IMPORT void tb_use_wcwidth_cjk(int flg); 324 | SO_IMPORT int tb_wcwidth(const wchar_t c); 325 | 326 | #ifdef __cplusplus 327 | } 328 | #endif 329 | -------------------------------------------------------------------------------- /termbox/wcwidth-cjk.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan 3 | * License: BSD-like (2-clause, see wcwidth.c) 4 | * URL: 5 | * Twitter: 6 | */ 7 | 8 | /* 9 | * This is an implementation of wcwidth() and wcswidth() (defined in 10 | * IEEE Std 1002.1-2001) for Unicode. 11 | * 12 | * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html 13 | * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html 14 | * 15 | * Markus Kuhn -- 2007-05-26 (Unicode 5.0) 16 | * 17 | * Permission to use, copy, modify, and distribute this software 18 | * for any purpose and without fee is hereby granted. The author 19 | * disclaims all warranties with regard to this software. 20 | * 21 | * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c 22 | */ 23 | 24 | /* Changes made for mutt: 25 | * - Adapted for Mutt by Edmund Grimley Evans. 26 | * - Changed 'first'/'last' members of combined[] to wchar_t from 27 | * unsigned short to fix compiler warnings, 2007-11-13, Rocco Rutte 28 | */ 29 | #ifndef WCWIDTH_CJK_HPP 30 | #define WCWIDTH_CJK_HPP 31 | 32 | #include 33 | 34 | struct interval { 35 | wchar_t first; 36 | wchar_t last; 37 | }; 38 | 39 | /* auxiliary function for binary search in interval table */ 40 | static int bisearch(wchar_t ucs, const struct interval *table, int max) { 41 | int min = 0; 42 | int mid; 43 | 44 | if (ucs < table[0].first || ucs > table[max].last) 45 | return 0; 46 | while (max >= min) { 47 | mid = (min + max) / 2; 48 | if (ucs > table[mid].last) 49 | min = mid + 1; 50 | else if (ucs < table[mid].first) 51 | max = mid - 1; 52 | else 53 | return 1; 54 | } 55 | 56 | return 0; 57 | } 58 | 59 | 60 | /* The following two functions define the column width of an ISO 10646 61 | * character as follows: 62 | * 63 | * - The null character (U+0000) has a column width of 0. 64 | * 65 | * - Other C0/C1 control characters and DEL will lead to a return 66 | * value of -1. 67 | * 68 | * - Non-spacing and enclosing combining characters (general 69 | * category code Mn or Me in the Unicode database) have a 70 | * column width of 0. 71 | * 72 | * - SOFT HYPHEN (U+00AD) has a column width of 1. 73 | * 74 | * - Other format characters (general category code Cf in the Unicode 75 | * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. 76 | * 77 | * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) 78 | * have a column width of 0. 79 | * 80 | * - Spacing characters in the East Asian Wide (W) or East Asian 81 | * Full-width (F) category as defined in Unicode Technical 82 | * Report #11 have a column width of 2. 83 | * 84 | * - All remaining characters (including all printable 85 | * ISO 8859-1 and WGL4 characters, Unicode control characters, 86 | * etc.) have a column width of 1. 87 | * 88 | * This implementation assumes that wchar_t characters are encoded 89 | * in ISO 10646. 90 | */ 91 | 92 | int wcwidth_ucs(wchar_t ucs) 93 | { 94 | /* sorted list of non-overlapping intervals of non-spacing characters */ 95 | /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ 96 | static const struct interval combining[] = { 97 | { 0x0300, 0x036f }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, 98 | { 0x0591, 0x05bd }, { 0x05bf, 0x05bf }, { 0x05c1, 0x05c2 }, 99 | { 0x05c4, 0x05c5 }, { 0x05c7, 0x05c7 }, { 0x0600, 0x0603 }, 100 | { 0x0610, 0x0615 }, { 0x064b, 0x065e }, { 0x0670, 0x0670 }, 101 | { 0x06d6, 0x06e4 }, { 0x06e7, 0x06e8 }, { 0x06ea, 0x06ed }, 102 | { 0x070f, 0x070f }, { 0x0711, 0x0711 }, { 0x0730, 0x074a }, 103 | { 0x07a6, 0x07b0 }, { 0x07eb, 0x07f3 }, { 0x0901, 0x0902 }, 104 | { 0x093c, 0x093c }, { 0x0941, 0x0948 }, { 0x094d, 0x094d }, 105 | { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, 106 | { 0x09bc, 0x09bc }, { 0x09c1, 0x09c4 }, { 0x09cd, 0x09cd }, 107 | { 0x09e2, 0x09e3 }, { 0x0a01, 0x0a02 }, { 0x0a3c, 0x0a3c }, 108 | { 0x0a41, 0x0a42 }, { 0x0a47, 0x0a48 }, { 0x0a4b, 0x0a4d }, 109 | { 0x0a70, 0x0a71 }, { 0x0a81, 0x0a82 }, { 0x0abc, 0x0abc }, 110 | { 0x0ac1, 0x0ac5 }, { 0x0ac7, 0x0ac8 }, { 0x0acd, 0x0acd }, 111 | { 0x0ae2, 0x0ae3 }, { 0x0b01, 0x0b01 }, { 0x0b3c, 0x0b3c }, 112 | { 0x0b3f, 0x0b3f }, { 0x0b41, 0x0b43 }, { 0x0b4d, 0x0b4d }, 113 | { 0x0b56, 0x0b56 }, { 0x0b82, 0x0b82 }, { 0x0bc0, 0x0bc0 }, 114 | { 0x0bcd, 0x0bcd }, { 0x0c3e, 0x0c40 }, { 0x0c46, 0x0c48 }, 115 | { 0x0c4a, 0x0c4d }, { 0x0c55, 0x0c56 }, { 0x0cbc, 0x0cbc }, 116 | { 0x0cbf, 0x0cbf }, { 0x0cc6, 0x0cc6 }, { 0x0ccc, 0x0ccd }, 117 | { 0x0ce2, 0x0ce3 }, { 0x0d41, 0x0d43 }, { 0x0d4d, 0x0d4d }, 118 | { 0x0dca, 0x0dca }, { 0x0dd2, 0x0dd4 }, { 0x0dd6, 0x0dd6 }, 119 | { 0x0e31, 0x0e31 }, { 0x0e34, 0x0e3a }, { 0x0e47, 0x0e4e }, 120 | { 0x0eb1, 0x0eb1 }, { 0x0eb4, 0x0eb9 }, { 0x0ebb, 0x0ebc }, 121 | { 0x0ec8, 0x0ecd }, { 0x0f18, 0x0f19 }, { 0x0f35, 0x0f35 }, 122 | { 0x0f37, 0x0f37 }, { 0x0f39, 0x0f39 }, { 0x0f71, 0x0f7e }, 123 | { 0x0f80, 0x0f84 }, { 0x0f86, 0x0f87 }, { 0x0f90, 0x0f97 }, 124 | { 0x0f99, 0x0fbc }, { 0x0fc6, 0x0fc6 }, { 0x102d, 0x1030 }, 125 | { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, 126 | { 0x1058, 0x1059 }, { 0x1160, 0x11ff }, { 0x135f, 0x135f }, 127 | { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, 128 | { 0x1772, 0x1773 }, { 0x17b4, 0x17b5 }, { 0x17b7, 0x17bd }, 129 | { 0x17c6, 0x17c6 }, { 0x17c9, 0x17d3 }, { 0x17dd, 0x17dd }, 130 | { 0x180b, 0x180d }, { 0x18a9, 0x18a9 }, { 0x1920, 0x1922 }, 131 | { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193b }, 132 | { 0x1a17, 0x1a18 }, { 0x1b00, 0x1b03 }, { 0x1b34, 0x1b34 }, 133 | { 0x1b36, 0x1b3a }, { 0x1b3c, 0x1b3c }, { 0x1b42, 0x1b42 }, 134 | { 0x1b6b, 0x1b73 }, { 0x1dc0, 0x1dca }, { 0x1dfe, 0x1dff }, 135 | { 0x200b, 0x200f }, { 0x202a, 0x202e }, { 0x2060, 0x2063 }, 136 | { 0x206a, 0x206f }, { 0x20d0, 0x20ef }, { 0x302a, 0x302f }, 137 | #ifndef __APPLE__ 138 | { 0x3099, 0x309a }, 139 | #endif 140 | { 0xa806, 0xa806 }, { 0xa80b, 0xa80b }, 141 | { 0xa825, 0xa826 }, { 0xfb1e, 0xfb1e }, { 0xfe00, 0xfe0f }, 142 | { 0xfe20, 0xfe23 }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb }, 143 | { 0x10a01, 0x10a03 }, { 0x10a05, 0x10a06 }, { 0x10a0c, 0x10a0f }, 144 | { 0x10a38, 0x10a3a }, { 0x10a3f, 0x10a3f }, { 0x1d167, 0x1d169 }, 145 | { 0x1d173, 0x1d182 }, { 0x1d185, 0x1d18b }, { 0x1d1aa, 0x1d1ad }, 146 | { 0x1d242, 0x1d244 }, { 0xe0001, 0xe0001 }, { 0xe0020, 0xe007f }, 147 | { 0xe0100, 0xe01ef } 148 | }; 149 | 150 | /* test for 8-bit control characters */ 151 | if (ucs == 0) 152 | return 0; 153 | if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) 154 | return -1; 155 | 156 | /* binary search in table of non-spacing characters */ 157 | if (bisearch(ucs, combining, 158 | sizeof(combining) / sizeof(struct interval) - 1)) 159 | return 0; 160 | 161 | /* if we arrive here, ucs is not a combining or C0/C1 control character */ 162 | 163 | /* fast test for majority of non-wide scripts */ 164 | if (ucs < 0x1100) 165 | return 1; 166 | 167 | return 1 + 168 | (ucs >= 0x1100 && 169 | (ucs <= 0x115f || /* Hangul Jamo init. consonants */ 170 | ucs == 0x2329 || ucs == 0x232a || 171 | (ucs >= 0x2e80 && ucs <= 0xa4cf && 172 | ucs != 0x303f) || /* CJK ... Yi */ 173 | (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ 174 | (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ 175 | (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ 176 | (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ 177 | (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ 178 | (ucs >= 0xffe0 && ucs <= 0xffe6) || 179 | (ucs >= 0x20000 && ucs <= 0x2fffd) || 180 | (ucs >= 0x30000 && ucs <= 0x3fffd))); 181 | } 182 | 183 | #if 0 /* original */ 184 | int wcswidth_ucs(const wchar_t *pwcs, size_t n) 185 | { 186 | int w, width = 0; 187 | 188 | for (;*pwcs && n-- > 0; pwcs++) 189 | if ((w = wcwidth_ucs(*pwcs)) < 0) 190 | return -1; 191 | else 192 | width += w; 193 | 194 | return width; 195 | } 196 | #endif 197 | 198 | /* 199 | * The following functions are the same as wcwidth_ucs() and 200 | * wcwidth_cjk(), except that spacing characters in the East Asian 201 | * Ambiguous (A) category as defined in Unicode Technical Report #11 202 | * have a column width of 2. This variant might be useful for users of 203 | * CJK legacy encodings who want to migrate to UCS without changing 204 | * the traditional terminal character-width behaviour. It is not 205 | * otherwise recommended for general use. 206 | */ 207 | /* 208 | * In addition to the explanation mentioned above, 209 | * several characters in the East Asian Narrow (Na) and Not East Asian 210 | * (Neutral) category as defined in Unicode Technical Report #11 211 | * actually have a column width of 2 in CJK legacy encodings. 212 | */ 213 | 214 | //#define wcwidth_cjk wcwidth 215 | //#define wcswidth_cjk wcswidth 216 | 217 | /* For FreeBSD: wcwidth() is implemented as an inline function */ 218 | #ifdef wcwidth 219 | # undef wcwidth 220 | #endif 221 | 222 | int wcwidth_cjk(wchar_t ucs) 223 | { 224 | /* sorted list of non-overlapping intervals of East Asian Ambiguous 225 | * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ 226 | static const struct interval ambiguous[] = { 227 | { 0x00a1, 0x00a1 }, { 0x00a4, 0x00a4 }, { 0x00a7, 0x00a8 }, 228 | { 0x00aa, 0x00aa }, { 0x00ae, 0x00ae }, { 0x00b0, 0x00b4 }, 229 | { 0x00b6, 0x00ba }, { 0x00bc, 0x00bf }, { 0x00c6, 0x00c6 }, 230 | { 0x00d0, 0x00d0 }, { 0x00d7, 0x00d8 }, { 0x00de, 0x00e1 }, 231 | { 0x00e6, 0x00e6 }, { 0x00e8, 0x00ea }, { 0x00ec, 0x00ed }, 232 | { 0x00f0, 0x00f0 }, { 0x00f2, 0x00f3 }, { 0x00f7, 0x00fa }, 233 | { 0x00fc, 0x00fc }, { 0x00fe, 0x00fe }, { 0x0101, 0x0101 }, 234 | { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011b, 0x011b }, 235 | { 0x0126, 0x0127 }, { 0x012b, 0x012b }, { 0x0131, 0x0133 }, 236 | { 0x0138, 0x0138 }, { 0x013f, 0x0142 }, { 0x0144, 0x0144 }, 237 | { 0x0148, 0x014b }, { 0x014d, 0x014d }, { 0x0152, 0x0153 }, 238 | { 0x0166, 0x0167 }, { 0x016b, 0x016b }, { 0x01ce, 0x01ce }, 239 | { 0x01d0, 0x01d0 }, { 0x01d2, 0x01d2 }, { 0x01d4, 0x01d4 }, 240 | { 0x01d6, 0x01d6 }, { 0x01d8, 0x01d8 }, { 0x01da, 0x01da }, 241 | { 0x01dc, 0x01dc }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, 242 | { 0x02c4, 0x02c4 }, { 0x02c7, 0x02c7 }, { 0x02c9, 0x02cb }, 243 | { 0x02cd, 0x02cd }, { 0x02d0, 0x02d0 }, { 0x02d8, 0x02db }, 244 | { 0x02dd, 0x02dd }, { 0x02df, 0x02df }, { 0x0391, 0x03a1 }, 245 | { 0x03a3, 0x03a9 }, { 0x03b1, 0x03c1 }, { 0x03c3, 0x03c9 }, 246 | { 0x0401, 0x0401 }, { 0x0410, 0x044f }, { 0x0451, 0x0451 }, 247 | { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, 248 | { 0x201c, 0x201d }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, 249 | { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, 250 | { 0x203b, 0x203b }, { 0x203e, 0x203e }, { 0x2074, 0x2074 }, 251 | { 0x207f, 0x207f }, { 0x2081, 0x2084 }, { 0x20ac, 0x20ac }, 252 | { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, 253 | { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, 254 | { 0x2126, 0x2126 }, { 0x212b, 0x212b }, { 0x2153, 0x2154 }, 255 | { 0x215b, 0x215e }, { 0x2160, 0x216b }, { 0x2170, 0x2179 }, 256 | { 0x2190, 0x2199 }, { 0x21b8, 0x21b9 }, { 0x21d2, 0x21d2 }, 257 | { 0x21d4, 0x21d4 }, { 0x21e7, 0x21e7 }, { 0x2200, 0x2200 }, 258 | { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220b, 0x220b }, 259 | { 0x220f, 0x220f }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, 260 | { 0x221a, 0x221a }, { 0x221d, 0x2220 }, { 0x2223, 0x2223 }, 261 | { 0x2225, 0x2225 }, { 0x2227, 0x222c }, { 0x222e, 0x222e }, 262 | { 0x2234, 0x2237 }, { 0x223c, 0x223d }, { 0x2248, 0x2248 }, 263 | { 0x224c, 0x224c }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, 264 | { 0x2264, 0x2267 }, { 0x226a, 0x226b }, { 0x226e, 0x226f }, 265 | { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, 266 | { 0x2299, 0x2299 }, { 0x22a5, 0x22a5 }, { 0x22bf, 0x22bf }, 267 | { 0x2312, 0x2312 }, { 0x2460, 0x24e9 }, { 0x24eb, 0x254b }, 268 | { 0x2550, 0x2573 }, { 0x2580, 0x258f }, { 0x2592, 0x2595 }, 269 | { 0x25a0, 0x25a1 }, { 0x25a3, 0x25a9 }, { 0x25b2, 0x25b3 }, 270 | { 0x25b6, 0x25b7 }, { 0x25bc, 0x25bd }, { 0x25c0, 0x25c1 }, 271 | { 0x25c6, 0x25c8 }, { 0x25cb, 0x25cb }, { 0x25ce, 0x25d1 }, 272 | { 0x25e2, 0x25e5 }, { 0x25ef, 0x25ef }, { 0x2605, 0x2606 }, 273 | { 0x2609, 0x2609 }, { 0x260e, 0x260f }, { 0x2614, 0x2615 }, 274 | { 0x261c, 0x261c }, { 0x261e, 0x261e }, { 0x2640, 0x2640 }, 275 | { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, 276 | { 0x2667, 0x266a }, { 0x266c, 0x266d }, { 0x266f, 0x266f }, 277 | { 0x273d, 0x273d }, { 0x2776, 0x277f }, { 0xe000, 0xf8ff }, 278 | { 0xfffd, 0xfffd }, { 0xf0000, 0xffffd }, { 0x100000, 0x10fffd } 279 | }; 280 | 281 | #ifdef JA_LEGACY 282 | /* For Japanese legacy encodings, the following characters are added. */ 283 | static const struct interval legacy_ja[] = { 284 | { 0x00A2, 0x00A3 }, { 0x00A5, 0x00A6 }, { 0x00AC, 0x00AC }, 285 | { 0x00AF, 0x00AF }, { 0x2212, 0x2212 } 286 | }; 287 | #endif /* JA_LEGACY */ 288 | 289 | /* binary search in table of non-spacing characters */ 290 | if (bisearch(ucs, ambiguous, 291 | sizeof(ambiguous) / sizeof(struct interval) - 1)) 292 | return 2; 293 | #ifdef JA_LEGACY 294 | if (bisearch(ucs, legacy_ja, 295 | sizeof(legacy_ja) / sizeof(struct interval) - 1)) 296 | return 2; 297 | #endif /* JA_LEGACY */ 298 | 299 | //#ifdef EMOJI 300 | static const struct interval emoji[] = { 301 | { 0x2600, 0x27BF }, 302 | { 0x1F300, 0x1F64F }, { 0x1F680, 0x1F6FF }, { 0x1F900, 0x1F9FF }, 303 | }; 304 | 305 | if (bisearch(ucs, emoji, 306 | sizeof(emoji) / sizeof(struct interval) - 1)) 307 | return 2; 308 | //#endif /* EMOJI */ 309 | 310 | return wcwidth_ucs(ucs); 311 | } 312 | 313 | int wcswidth_cjk(const wchar_t *pwcs, size_t n) 314 | { 315 | int w, width = 0; 316 | 317 | for (;*pwcs && n-- > 0; pwcs++) 318 | if ((w = wcwidth_cjk(*pwcs)) < 0) 319 | return -1; 320 | else 321 | width += w; 322 | 323 | return width; 324 | } 325 | 326 | #endif 327 | -------------------------------------------------------------------------------- /inih/INIReader.h: -------------------------------------------------------------------------------- 1 | // Read an INI file into easy-to-access name/value pairs. 2 | 3 | // inih and INIReader are released under the New BSD license (see LICENSE.txt). 4 | // Go to the project home page for more info: 5 | // 6 | // https://github.com/benhoyt/inih 7 | /* inih -- simple .INI file parser 8 | 9 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 10 | home page for more info: 11 | 12 | https://github.com/benhoyt/inih 13 | 14 | */ 15 | 16 | #ifndef __INI_H__ 17 | #define __INI_H__ 18 | 19 | /* Make this header file easier to include in C++ code */ 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | #include 25 | 26 | /* Typedef for prototype of handler function. */ 27 | typedef int (*ini_handler)(void* user, const char* section, 28 | const char* name, const char* value); 29 | 30 | /* Typedef for prototype of fgets-style reader function. */ 31 | typedef char* (*ini_reader)(char* str, int num, void* stream); 32 | 33 | /* Parse given INI-style file. May have [section]s, name=value pairs 34 | (whitespace stripped), and comments starting with ';' (semicolon). Section 35 | is "" if name=value pair parsed before any section heading. name:value 36 | pairs are also supported as a concession to Python's configparser. 37 | 38 | For each name=value pair parsed, call handler function with given user 39 | pointer as well as section, name, and value (data only valid for duration 40 | of handler call). Handler should return nonzero on success, zero on error. 41 | 42 | Returns 0 on success, line number of first error on parse error (doesn't 43 | stop on first error), -1 on file open error, or -2 on memory allocation 44 | error (only when INI_USE_STACK is zero). 45 | */ 46 | int ini_parse(const char* filename, ini_handler handler, void* user); 47 | 48 | /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't 49 | close the file when it's finished -- the caller must do that. */ 50 | int ini_parse_file(FILE* file, ini_handler handler, void* user); 51 | 52 | /* Same as ini_parse(), but takes an ini_reader function pointer instead of 53 | filename. Used for implementing custom or string-based I/O. */ 54 | int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 55 | void* user); 56 | 57 | /* Nonzero to allow multi-line value parsing, in the style of Python's 58 | configparser. If allowed, ini_parse() will call the handler with the same 59 | name for each subsequent line parsed. */ 60 | #ifndef INI_ALLOW_MULTILINE 61 | #define INI_ALLOW_MULTILINE 1 62 | #endif 63 | 64 | /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of 65 | the file. See http://code.google.com/p/inih/issues/detail?id=21 */ 66 | #ifndef INI_ALLOW_BOM 67 | #define INI_ALLOW_BOM 1 68 | #endif 69 | 70 | /* Nonzero to allow inline comments (with valid inline comment characters 71 | specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match 72 | Python 3.2+ configparser behaviour. */ 73 | #ifndef INI_ALLOW_INLINE_COMMENTS 74 | #define INI_ALLOW_INLINE_COMMENTS 1 75 | #endif 76 | #ifndef INI_INLINE_COMMENT_PREFIXES 77 | #define INI_INLINE_COMMENT_PREFIXES ";" 78 | #endif 79 | 80 | /* Nonzero to use stack, zero to use heap (malloc/free). */ 81 | #ifndef INI_USE_STACK 82 | #define INI_USE_STACK 1 83 | #endif 84 | 85 | /* Stop parsing on first error (default is to keep parsing). */ 86 | #ifndef INI_STOP_ON_FIRST_ERROR 87 | #define INI_STOP_ON_FIRST_ERROR 0 88 | #endif 89 | 90 | /* Maximum line length for any line in INI file. */ 91 | #ifndef INI_MAX_LINE 92 | #define INI_MAX_LINE 200 93 | #endif 94 | 95 | #ifdef __cplusplus 96 | } 97 | #endif 98 | 99 | /* inih -- simple .INI file parser 100 | 101 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 102 | home page for more info: 103 | 104 | https://github.com/benhoyt/inih 105 | 106 | */ 107 | 108 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 109 | #define _CRT_SECURE_NO_WARNINGS 110 | #endif 111 | 112 | #include 113 | #include 114 | #include 115 | 116 | #if !INI_USE_STACK 117 | #include 118 | #endif 119 | 120 | #define MAX_SECTION 50 121 | #define MAX_NAME 50 122 | 123 | /* Strip whitespace chars off end of given string, in place. Return s. */ 124 | inline static char* rstrip(char* s) 125 | { 126 | char* p = s + strlen(s); 127 | while (p > s && isspace((unsigned char)(*--p))) 128 | *p = '\0'; 129 | return s; 130 | } 131 | 132 | /* Return pointer to first non-whitespace char in given string. */ 133 | inline static char* lskip(const char* s) 134 | { 135 | while (*s && isspace((unsigned char)(*s))) 136 | s++; 137 | return (char*)s; 138 | } 139 | 140 | /* Return pointer to first char (of chars) or inline comment in given string, 141 | or pointer to null at end of string if neither found. Inline comment must 142 | be prefixed by a whitespace character to register as a comment. */ 143 | inline static char* find_chars_or_comment(const char* s, const char* chars) 144 | { 145 | #if INI_ALLOW_INLINE_COMMENTS 146 | int was_space = 0; 147 | while (*s && (!chars || !strchr(chars, *s)) && 148 | !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { 149 | was_space = isspace((unsigned char)(*s)); 150 | s++; 151 | } 152 | #else 153 | while (*s && (!chars || !strchr(chars, *s))) { 154 | s++; 155 | } 156 | #endif 157 | return (char*)s; 158 | } 159 | 160 | /* Version of strncpy that ensures dest (size bytes) is null-terminated. */ 161 | inline static char* strncpy0(char* dest, const char* src, size_t size) 162 | { 163 | strncpy(dest, src, size); 164 | dest[size - 1] = '\0'; 165 | return dest; 166 | } 167 | 168 | /* See documentation in header file. */ 169 | inline int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 170 | void* user) 171 | { 172 | /* Uses a fair bit of stack (use heap instead if you need to) */ 173 | #if INI_USE_STACK 174 | char line[INI_MAX_LINE]; 175 | #else 176 | char* line; 177 | #endif 178 | char section[MAX_SECTION] = ""; 179 | char prev_name[MAX_NAME] = ""; 180 | 181 | char* start; 182 | char* end; 183 | char* name; 184 | char* value; 185 | int lineno = 0; 186 | int error = 0; 187 | 188 | #if !INI_USE_STACK 189 | line = (char*)malloc(INI_MAX_LINE); 190 | if (!line) { 191 | return -2; 192 | } 193 | #endif 194 | 195 | /* Scan through stream line by line */ 196 | while (reader(line, INI_MAX_LINE, stream) != NULL) { 197 | lineno++; 198 | 199 | start = line; 200 | #if INI_ALLOW_BOM 201 | if (lineno == 1 && (unsigned char)start[0] == 0xEF && 202 | (unsigned char)start[1] == 0xBB && 203 | (unsigned char)start[2] == 0xBF) { 204 | start += 3; 205 | } 206 | #endif 207 | start = lskip(rstrip(start)); 208 | 209 | if (*start == ';' || *start == '#') { 210 | /* Per Python configparser, allow both ; and # comments at the 211 | start of a line */ 212 | } 213 | #if INI_ALLOW_MULTILINE 214 | else if (*prev_name && *start && start > line) { 215 | 216 | #if INI_ALLOW_INLINE_COMMENTS 217 | end = find_chars_or_comment(start, NULL); 218 | if (*end) 219 | *end = '\0'; 220 | rstrip(start); 221 | #endif 222 | 223 | /* Non-blank line with leading whitespace, treat as continuation 224 | of previous name's value (as per Python configparser). */ 225 | if (!handler(user, section, prev_name, start) && !error) 226 | error = lineno; 227 | } 228 | #endif 229 | else if (*start == '[') { 230 | /* A "[section]" line */ 231 | end = find_chars_or_comment(start + 1, "]"); 232 | if (*end == ']') { 233 | *end = '\0'; 234 | strncpy0(section, start + 1, sizeof(section)); 235 | *prev_name = '\0'; 236 | } 237 | else if (!error) { 238 | /* No ']' found on section line */ 239 | error = lineno; 240 | } 241 | } 242 | else if (*start) { 243 | /* Not a comment, must be a name[=:]value pair */ 244 | end = find_chars_or_comment(start, "=:"); 245 | if (*end == '=' || *end == ':') { 246 | *end = '\0'; 247 | name = rstrip(start); 248 | value = lskip(end + 1); 249 | #if INI_ALLOW_INLINE_COMMENTS 250 | end = find_chars_or_comment(value, NULL); 251 | if (*end) 252 | *end = '\0'; 253 | #endif 254 | rstrip(value); 255 | 256 | /* Valid name[=:]value pair found, call handler */ 257 | strncpy0(prev_name, name, sizeof(prev_name)); 258 | if (!handler(user, section, name, value) && !error) 259 | error = lineno; 260 | } 261 | else if (!error) { 262 | /* No '=' or ':' found on name[=:]value line */ 263 | error = lineno; 264 | } 265 | } 266 | 267 | #if INI_STOP_ON_FIRST_ERROR 268 | if (error) 269 | break; 270 | #endif 271 | } 272 | 273 | #if !INI_USE_STACK 274 | free(line); 275 | #endif 276 | 277 | return error; 278 | } 279 | 280 | /* See documentation in header file. */ 281 | inline int ini_parse_file(FILE* file, ini_handler handler, void* user) 282 | { 283 | return ini_parse_stream((ini_reader)fgets, file, handler, user); 284 | } 285 | 286 | /* See documentation in header file. */ 287 | inline int ini_parse(const char* filename, ini_handler handler, void* user) 288 | { 289 | FILE* file; 290 | int error; 291 | 292 | file = fopen(filename, "r"); 293 | if (!file) 294 | return -1; 295 | error = ini_parse_file(file, handler, user); 296 | fclose(file); 297 | return error; 298 | } 299 | 300 | #endif /* __INI_H__ */ 301 | 302 | 303 | #ifndef __INIREADER_H__ 304 | #define __INIREADER_H__ 305 | 306 | #include 307 | #include 308 | #include 309 | 310 | // Read an INI file into easy-to-access name/value pairs. (Note that I've gone 311 | // for simplicity here rather than speed, but it should be pretty decent.) 312 | class INIReader 313 | { 314 | public: 315 | // Empty Constructor 316 | INIReader() {}; 317 | 318 | // Construct INIReader and parse given filename. See ini.h for more info 319 | // about the parsing. 320 | INIReader(std::string filename); 321 | 322 | // Construct INIReader and parse given file. See ini.h for more info 323 | // about the parsing. 324 | INIReader(FILE *file); 325 | 326 | // Return the result of ini_parse(), i.e., 0 on success, line number of 327 | // first error on parse error, or -1 on file open error. 328 | int ParseError() const; 329 | 330 | // Return the list of sections found in ini file 331 | const std::set& Sections() const; 332 | 333 | // Get a string value from INI file, returning default_value if not found. 334 | std::string Get(std::string section, std::string name, 335 | std::string default_value) const; 336 | 337 | // Get an integer (long) value from INI file, returning default_value if 338 | // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). 339 | long GetInteger(std::string section, std::string name, long default_value) const; 340 | 341 | // Get a real (floating point double) value from INI file, returning 342 | // default_value if not found or not a valid floating point value 343 | // according to strtod(). 344 | double GetReal(std::string section, std::string name, double default_value) const; 345 | 346 | // Get a boolean value from INI file, returning default_value if not found or if 347 | // not a valid true/false value. Valid true values are "true", "yes", "on", "1", 348 | // and valid false values are "false", "no", "off", "0" (not case sensitive). 349 | bool GetBoolean(std::string section, std::string name, bool default_value) const; 350 | 351 | protected: 352 | int _error; 353 | std::map _values; 354 | std::set _sections; 355 | static std::string MakeKey(std::string section, std::string name); 356 | static int ValueHandler(void* user, const char* section, const char* name, 357 | const char* value); 358 | }; 359 | 360 | #endif // __INIREADER_H__ 361 | 362 | 363 | #ifndef __INIREADER__ 364 | #define __INIREADER__ 365 | 366 | #include 367 | #include 368 | #include 369 | 370 | inline INIReader::INIReader(std::string filename) 371 | { 372 | _error = ini_parse(filename.c_str(), ValueHandler, this); 373 | } 374 | 375 | inline INIReader::INIReader(FILE *file) 376 | { 377 | _error = ini_parse_file(file, ValueHandler, this); 378 | } 379 | 380 | inline int INIReader::ParseError() const 381 | { 382 | return _error; 383 | } 384 | 385 | inline const std::set& INIReader::Sections() const 386 | { 387 | return _sections; 388 | } 389 | 390 | inline std::string INIReader::Get(std::string section, std::string name, std::string default_value) const 391 | { 392 | std::string key = MakeKey(section, name); 393 | return _values.count(key) ? _values.at(key) : default_value; 394 | } 395 | 396 | inline long INIReader::GetInteger(std::string section, std::string name, long default_value) const 397 | { 398 | std::string valstr = Get(section, name, ""); 399 | const char* value = valstr.c_str(); 400 | char* end; 401 | // This parses "1234" (decimal) and also "0x4D2" (hex) 402 | long n = strtol(value, &end, 0); 403 | return end > value ? n : default_value; 404 | } 405 | 406 | inline double INIReader::GetReal(std::string section, std::string name, double default_value) const 407 | { 408 | std::string valstr = Get(section, name, ""); 409 | const char* value = valstr.c_str(); 410 | char* end; 411 | double n = strtod(value, &end); 412 | return end > value ? n : default_value; 413 | } 414 | 415 | inline bool INIReader::GetBoolean(std::string section, std::string name, bool default_value) const 416 | { 417 | std::string valstr = Get(section, name, ""); 418 | // Convert to lower case to make string comparisons case-insensitive 419 | std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); 420 | if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") 421 | return true; 422 | else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") 423 | return false; 424 | else 425 | return default_value; 426 | } 427 | 428 | inline std::string INIReader::MakeKey(std::string section, std::string name) 429 | { 430 | std::string key = section + "=" + name; 431 | // Convert to lower case to make section/name lookups case-insensitive 432 | std::transform(key.begin(), key.end(), key.begin(), ::tolower); 433 | return key; 434 | } 435 | 436 | inline int INIReader::ValueHandler(void* user, const char* section, const char* name, 437 | const char* value) 438 | { 439 | INIReader* reader = (INIReader*)user; 440 | std::string key = MakeKey(section, name); 441 | if (reader->_values[key].size() > 0) 442 | reader->_values[key] += "\n"; 443 | reader->_values[key] += value; 444 | reader->_sections.insert(section); 445 | return 1; 446 | } 447 | 448 | #endif // __INIREADER__ 449 | -------------------------------------------------------------------------------- /NanoSyntaxHighlight.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NANOSYNTAXHIGHLIGHT_HPP 2 | #define NANOSYNTAXHIGHLIGHT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | class NanoSyntaxHighlight { 17 | friend class NanoSyntaxHighlightTest; 18 | 19 | public: 20 | NanoSyntaxHighlight() :tabsize_(-1), lineNumbers_(false) {} 21 | 22 | std::string highlight(const std::string& fileName, 23 | const std::string& txt, std::atomic& kill) { 24 | 25 | fcolorBuf_.resize(txt.length()); 26 | bcolorBuf_.resize(txt.length()); 27 | std::fill(fcolorBuf_.begin(), fcolorBuf_.end(), 0); 28 | std::fill(bcolorBuf_.begin(), bcolorBuf_.end(), 0); 29 | if(kill) return txt; 30 | 31 | int n; 32 | auto end = txt.find_first_of('\n'); 33 | if(end == std::string::npos) 34 | n = getHighlightType(fileName, txt); 35 | else 36 | n = getHighlightType(fileName, txt.substr(0, end)); 37 | 38 | if(n == -1) return txt; 39 | 40 | const std::vector& hlRuleList = hightlightList_[n].highlightRuleList; 41 | for(size_t i = 0; i < hlRuleList.size(); ++i) { 42 | if(hlRuleList[i].regex.empty()) continue; 43 | if(hlRuleList[i].regex.front() == '"' && hlRuleList[i].regex.back() == '"') { 44 | auto rStr = hlRuleList[i].regex.substr(1, hlRuleList[i].regex.length() - 2); 45 | 46 | int fcolor = getColorCode(hlRuleList[i].fcolor); 47 | int bcolor = getColorCode(hlRuleList[i].bcolor) + 10; 48 | 49 | regexNormal(txt, rStr, fcolor, bcolor, hlRuleList[i].icase); 50 | } 51 | else if(stringStartWith(hlRuleList[i].regex, "start") && i + 1 < hlRuleList.size() && 52 | stringStartWith(hlRuleList[i + 1].regex, "end")) { 53 | auto rStr1 = getSurroundDoubleQuotation(hlRuleList[i].regex); 54 | auto rStr2 = getSurroundDoubleQuotation(hlRuleList[i + 1].regex); 55 | 56 | int fcolor = getColorCode(hlRuleList[i].fcolor); 57 | int bcolor = getColorCode(hlRuleList[i].bcolor) + 10; 58 | 59 | regexSurround(txt, rStr1, rStr2, fcolor, bcolor, 60 | hlRuleList[i].icase, hlRuleList[i + 1].icase); 61 | } 62 | if(kill) return txt; 63 | } 64 | 65 | std::string result; 66 | int currentFColor = 0, currentBColor = 0, lineCnt = 0; 67 | for(size_t i = 0; i < txt.length(); ++i) { 68 | std::string colorStr; 69 | 70 | if(currentFColor != fcolorBuf_[i]) { 71 | currentFColor = fcolorBuf_[i]; 72 | colorStr.append(getAnsiFColor(currentFColor)); 73 | } 74 | if(currentBColor != bcolorBuf_[i]) { 75 | currentBColor = bcolorBuf_[i]; 76 | colorStr.append(getAnsiBColor(currentBColor)); 77 | } 78 | 79 | if(i != 0 && txt[i - 1] == '\n' && colorStr.empty()) { 80 | if(currentFColor != 0 || currentBColor != 0) { 81 | colorStr.append(getAnsiFColor(currentFColor)); 82 | colorStr.append(getAnsiBColor(currentBColor)); 83 | } 84 | } 85 | 86 | if(lineNumbers_ && (i == 0 || txt[i - 1] == '\n')) { 87 | char buf[80]; 88 | snprintf(buf, sizeof(buf), "\e[39;49m%6d: ", ++lineCnt); 89 | 90 | colorStr = buf + colorStr; 91 | } 92 | 93 | if(tabsize_ >= 0) { 94 | if(txt[i] == '\t') result.append(colorStr + tab2space()); 95 | else result.append(colorStr + txt[i]); 96 | } 97 | else result.append(colorStr + txt[i]); 98 | if(kill) break; 99 | } 100 | 101 | return result; 102 | } 103 | 104 | std::string highlight(const std::string& fileName, 105 | FILE* fp, std::atomic& kill) { 106 | std::string txt; 107 | char rbuf[1024]; 108 | while(!feof(fp)) { 109 | if(fgets(rbuf, sizeof(rbuf), fp) != 0) { 110 | auto str = std::string(rbuf); 111 | 112 | // remove crlf 113 | if(!str.empty() && str.back() == '\n') str.pop_back(); 114 | if(!str.empty() && str.back() == '\r') str.pop_back(); 115 | str.push_back('\n'); 116 | 117 | txt += str; 118 | } 119 | } 120 | 121 | return highlight(fileName, txt, std::ref(kill)); 122 | } 123 | 124 | void setTabSpace(int tabsize) { tabsize_ = tabsize; } 125 | int getTabSpace() const { return tabsize_; } 126 | void setLineNumbers(bool t) { lineNumbers_ = t; } 127 | bool getLineNumbers() const { return lineNumbers_; } 128 | std::string getCurrentSyntaxName() const { return currentSyntaxName_; } 129 | 130 | bool loadPathNanoRC(const std::string& path) { 131 | auto dir = opendir(path.c_str()); 132 | if(dir == NULL) return false; 133 | 134 | struct dirent* dp; 135 | while((dp = readdir(dir)) != NULL) { 136 | std::string name(dp -> d_name); 137 | 138 | auto i = name.find_last_of("."); 139 | if(i == 0) continue; 140 | if(i == std::string::npos) continue; 141 | 142 | auto suffix = name.substr(i + 1, name.size() - i); 143 | if(suffix == "nanorc") { 144 | if(path.back() != '/') name = '/' + name; 145 | 146 | loadNanoRC(path + name); 147 | } 148 | } 149 | closedir(dir); 150 | 151 | return true; 152 | } 153 | 154 | bool loadNanoRC(const std::string& fileName) { 155 | FILE* fp; 156 | if((fp = fopen(fileName.c_str(), "r")) == NULL) { 157 | return false; 158 | } 159 | 160 | char rbuf[5120]; 161 | Highlight hightlight; 162 | 163 | while(!feof(fp)) { 164 | if(fgets(rbuf, sizeof(rbuf), fp) != 0) { 165 | if(rbuf[0] == '#') continue; 166 | if(rbuf[0] == '\n') continue; 167 | 168 | auto splitTxt = splitText(rbuf); 169 | if(splitTxt[0] == "syntax") { 170 | hightlight.name = getSurroundDoubleQuotation(splitTxt[1]); 171 | 172 | for(size_t i = 2; i < splitTxt.size(); ++i) 173 | hightlight.fileRegexList.emplace_back(splitTxt[i]); 174 | } 175 | else if(splitTxt[0] == "magic") { 176 | hightlight.magicRegex = splitTxt[1]; 177 | } 178 | else if(splitTxt[0] == "header") { 179 | hightlight.headerRegex = splitTxt[1]; 180 | } 181 | else if(splitTxt[0] == "comment") { 182 | hightlight.commentRegex = splitTxt[1]; 183 | } 184 | else if(splitTxt[0] == "color" || splitTxt[0] == "icolor") { 185 | bool icase = (splitTxt[0] == "color") ? false: true; 186 | std::string fcolor, bcolor; 187 | 188 | auto i = splitTxt[1].find_first_of(','); 189 | if(i == std::string::npos) 190 | fcolor = splitTxt[1]; 191 | else { 192 | fcolor = std::string(splitTxt[1].begin(), splitTxt[1].begin() + i); 193 | bcolor = std::string(splitTxt[1].begin() + i + 1, splitTxt[1].end());; 194 | } 195 | 196 | for(size_t i = 2; i < splitTxt.size(); ++i) { 197 | HighlightRule hlRule; 198 | 199 | hlRule.fcolor = fcolor; 200 | hlRule.bcolor = bcolor; 201 | hlRule.regex = splitTxt[i]; 202 | hlRule.icase = icase; 203 | hightlight.highlightRuleList.emplace_back(hlRule); 204 | } 205 | } 206 | } 207 | } 208 | 209 | fclose(fp); 210 | hightlightList_.emplace_back(hightlight); 211 | 212 | return true; 213 | } 214 | 215 | private: 216 | bool stringStartWith(const std::string& str, const std::string& start) { 217 | return strncmp(str.c_str(), start.c_str(), start.length()) == 0; 218 | } 219 | 220 | int getHighlightType(const std::string& fileName, const std::string& header) { 221 | 222 | int n = -1; 223 | currentSyntaxName_ = ""; 224 | for(size_t i = 0; i < hightlightList_.size(); ++i) { 225 | for(auto regex: hightlightList_[i].fileRegexList) { 226 | regex_t re; 227 | regmatch_t m[1]; 228 | 229 | auto rstr = getSurroundDoubleQuotation(regex); 230 | regcomp(&re, rstr.c_str(), 231 | REG_EXTENDED|REG_NOSUB|REG_ICASE); 232 | 233 | if(regexec(&re, fileName.c_str(), 0, m, 0) != REG_NOMATCH) { 234 | n = i; 235 | regfree(&re); 236 | break; 237 | } 238 | regfree(&re); 239 | } 240 | } 241 | 242 | if(n == -1) { 243 | for(size_t i = 0; i < hightlightList_.size(); ++i) { 244 | if(!hightlightList_[i].headerRegex.empty()) { 245 | regex_t re; 246 | regmatch_t m[1]; 247 | 248 | auto rstr = getSurroundDoubleQuotation(hightlightList_[i].headerRegex); 249 | regcomp(&re, rstr.c_str(), 250 | REG_EXTENDED|REG_NOSUB|REG_ICASE); 251 | 252 | if(regexec(&re, header.c_str(), 0, m, 0) != REG_NOMATCH) { 253 | n = i; 254 | regfree(&re); 255 | break; 256 | } 257 | regfree(&re); 258 | } 259 | } 260 | } 261 | 262 | if(n != -1) 263 | currentSyntaxName_ = hightlightList_[n].name; 264 | 265 | return n; 266 | } 267 | 268 | int getColorCode(const std::string& colorName) const { 269 | if(colorName == "black") return 30; 270 | if(colorName == "red") return 31; 271 | if(colorName == "green") return 32; 272 | if(colorName == "yellow") return 33; 273 | if(colorName == "blue") return 34; 274 | if(colorName == "magenta") return 35; 275 | if(colorName == "cyan") return 36; 276 | if(colorName == "white") return 37; 277 | 278 | if(colorName == "brightblack") return 90; 279 | if(colorName == "brightred") return 91; 280 | if(colorName == "brightgreen") return 92; 281 | if(colorName == "brightyellow") return 93; 282 | if(colorName == "brightblue") return 94; 283 | if(colorName == "brightmagenta") return 95; 284 | if(colorName == "brightcyan") return 96; 285 | if(colorName == "brightwhite") return 97; 286 | 287 | return 39; 288 | } 289 | 290 | bool isBlankLine(const std::string& str) const { 291 | for(size_t i = 0; i < str.length(); ++i) 292 | if(str[i] != '\n') return false; 293 | 294 | return true; 295 | } 296 | 297 | std::string getAnsiFColor(int color) const { 298 | std::string result; 299 | 300 | if(color == 0) result ="\e[39m"; 301 | else result = "\e[" + std::to_string(color) + "m"; 302 | 303 | return result; 304 | } 305 | 306 | std::string getAnsiBColor(int color) const { 307 | std::string result; 308 | 309 | if(color == 0) result = "\e[49m"; 310 | else result = "\e[" + std::to_string(color) + "m"; 311 | 312 | return result; 313 | } 314 | 315 | void regexNormal(const std::string& txt, const std::string& reg, 316 | int fcolor, int bcolor, bool icase) { 317 | int cflags = icase ? REG_EXTENDED|REG_NEWLINE|REG_ICASE : REG_EXTENDED|REG_NEWLINE; 318 | regex_t re; 319 | regmatch_t m[1]; 320 | regcomp(&re, reg.c_str(), cflags); 321 | 322 | auto ptxt = txt.c_str(); 323 | while(1) { 324 | int eflags = (ptxt == txt.c_str()) ? REG_NOTEOL : REG_NOTBOL|REG_NOTEOL; 325 | 326 | if(regexec(&re, ptxt, 1, m, eflags) != REG_NOMATCH) { 327 | if(m[0].rm_so != -1) { 328 | auto mStr = std::string(ptxt + m[0].rm_so, ptxt + m[0].rm_eo); 329 | 330 | if(!isBlankLine(mStr)) { 331 | int begin = ptxt + m[0].rm_so - txt.c_str(); 332 | int end = ptxt + m[0].rm_eo - txt.c_str(); 333 | 334 | for(int j = begin; j < end; ++j) { 335 | fcolorBuf_[j] = fcolor; 336 | bcolorBuf_[j] = bcolor; 337 | } 338 | } 339 | 340 | ptxt = ptxt + m[0].rm_eo; 341 | if(m[0].rm_eo == 0) ++ptxt; 342 | if(ptxt >= txt.c_str() + txt.length()) break; 343 | } 344 | } 345 | else break; 346 | } 347 | regfree(&re); 348 | } 349 | 350 | void regexSurround(const std::string& txt, const std::string& sreg, const std::string& ereg, 351 | int fcolor, int bcolor, bool icase1, bool icase2) { 352 | regmatch_t m[1]; 353 | regex_t re1, re2; 354 | 355 | int cflag1 = icase1 ? REG_EXTENDED|REG_ICASE : REG_EXTENDED; 356 | int cflag2 = icase2 ? REG_EXTENDED|REG_ICASE : REG_EXTENDED; 357 | 358 | if(sreg[0] != '^' && sreg.find_first_of('$') == std::string::npos) 359 | regcomp(&re1, sreg.c_str(), cflag1); 360 | else 361 | regcomp(&re1, sreg.c_str(), cflag1|REG_NEWLINE); 362 | 363 | if(ereg[0] != '^' && ereg.find_first_of('$') == std::string::npos) 364 | regcomp(&re2, ereg.c_str(), cflag2); 365 | else 366 | regcomp(&re2, ereg.c_str(), cflag2|REG_NEWLINE); 367 | 368 | int begin = 0, end = 0; 369 | const char* ptxt = txt.c_str(); 370 | while(1) { 371 | if(regexec(&re1, ptxt, 1, m, 0) != REG_NOMATCH) { 372 | begin = ptxt + m[0].rm_so - txt.c_str(); 373 | 374 | ptxt = ptxt + m[0].rm_eo; 375 | if(ptxt > txt.c_str() + txt.length()) break; 376 | 377 | if(regexec(&re2, ptxt, 1, m, 0) != REG_NOMATCH) { 378 | end = ptxt + m[0].rm_eo - txt.c_str(); 379 | 380 | for(int j = begin; j < end; ++j) { 381 | fcolorBuf_[j] = fcolor; 382 | bcolorBuf_[j] = bcolor; 383 | } 384 | 385 | ptxt = ptxt + m[0].rm_eo; 386 | if(m[0].rm_eo == 0) ++ptxt; 387 | if(ptxt >= txt.c_str() + txt.length()) break; 388 | } 389 | else { 390 | end = txt.length(); 391 | 392 | for(int j = begin; j < end; ++j) { 393 | fcolorBuf_[j] = fcolor; 394 | bcolorBuf_[j] = bcolor; 395 | } 396 | break; 397 | } 398 | } 399 | else break; 400 | } 401 | 402 | regfree(&re1); 403 | regfree(&re2); 404 | } 405 | 406 | std::string tab2space() const { 407 | std::string result; 408 | 409 | for(int i = 0; i < tabsize_; ++i) 410 | result.push_back(' '); 411 | 412 | return result; 413 | } 414 | 415 | std::string getSurroundDoubleQuotation(const std::string& str) const { 416 | auto begin = str.find_first_of('"'); 417 | auto end = str.find_last_of('"'); 418 | 419 | if(begin != std::string::npos && end != std::string::npos && begin != end) 420 | return str.substr(begin + 1, end - begin - 1); 421 | 422 | return str; 423 | } 424 | 425 | std::vector splitText(const std::string& txt) const { 426 | std::vector result; 427 | 428 | bool qmark = false; 429 | for(size_t i = 0; i < txt.length(); ++i) { 430 | if(txt[i] == ' ') continue; 431 | 432 | qmark = false; 433 | for(size_t j = i; j < txt.length(); ++j) { 434 | if(txt[j] == '"') { 435 | if((!qmark && (txt[j - 1] == ' ' || txt[j - 1] == '=')) || 436 | (qmark && (txt[j + 1] == ' ' || txt[j + 1] == '\n'))) { 437 | 438 | if(qmark) qmark = false; 439 | else qmark = true; 440 | } 441 | } 442 | 443 | if(!qmark && (txt[j] == ' ' || txt[j] == '\n')) { 444 | result.emplace_back(std::string(&txt[i], &txt[j])); 445 | i = j; 446 | break; 447 | } 448 | } 449 | } 450 | 451 | return result; 452 | } 453 | 454 | struct HighlightRule { 455 | std::string fcolor; 456 | std::string bcolor; 457 | 458 | std::string regex; 459 | bool icase; 460 | }; 461 | 462 | struct Highlight { 463 | std::string name; 464 | std::vector fileRegexList; 465 | 466 | std::string magicRegex; 467 | std::string commentRegex; 468 | std::string headerRegex; 469 | 470 | std::vector highlightRuleList; 471 | }; 472 | std::vector hightlightList_; 473 | std::vector fcolorBuf_, bcolorBuf_; 474 | std::string currentSyntaxName_; 475 | int tabsize_; 476 | bool lineNumbers_; 477 | }; 478 | 479 | #endif 480 | -------------------------------------------------------------------------------- /termbox/termbox.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define __USE_XOPEN // Use wcwidth 17 | #include 18 | 19 | #include "termbox.h" 20 | 21 | #include "bytebuffer.inl" 22 | #include "term.inl" 23 | #include "input.inl" 24 | #include "wcwidth-cjk.hpp" 25 | 26 | struct cellbuf { 27 | int width; 28 | int height; 29 | struct tb_cell *cells; 30 | }; 31 | 32 | #define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)] 33 | #define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1) 34 | #define LAST_COORD_INIT -1 35 | 36 | static struct termios orig_tios; 37 | 38 | static struct cellbuf back_buffer; 39 | static struct cellbuf front_buffer; 40 | static struct bytebuffer output_buffer; 41 | static struct bytebuffer input_buffer; 42 | 43 | static int termw = -1; 44 | static int termh = -1; 45 | 46 | static int inputmode = TB_INPUT_ESC; 47 | static int outputmode = TB_OUTPUT_NORMAL; 48 | 49 | static int inout; 50 | static int winch_fds[2]; 51 | 52 | static int lastx = LAST_COORD_INIT; 53 | static int lasty = LAST_COORD_INIT; 54 | static int cursor_x = -1; 55 | static int cursor_y = -1; 56 | 57 | static uint16_t background = TB_DEFAULT; 58 | static uint16_t foreground = TB_DEFAULT; 59 | 60 | static struct sigaction orig_sig; 61 | 62 | static void write_cursor(int x, int y); 63 | static void write_sgr(uint16_t fg, uint16_t bg); 64 | 65 | static void cellbuf_init(struct cellbuf *buf, int width, int height); 66 | static void cellbuf_resize(struct cellbuf *buf, int width, int height); 67 | static void cellbuf_clear(struct cellbuf *buf); 68 | static void cellbuf_free(struct cellbuf *buf); 69 | 70 | static void update_size(void); 71 | static void update_term_size(void); 72 | static void send_attr(uint16_t fg, uint16_t bg); 73 | static void send_char(int x, int y, uint32_t c); 74 | static void send_clear(void); 75 | static void sigwinch_handler(int xxx); 76 | static int wait_fill_event(struct tb_event *event, struct timeval *timeout); 77 | 78 | /* may happen in a different thread */ 79 | static volatile int buffer_size_change_request; 80 | 81 | int (*func_wcwidth)(const wchar_t c) = wcwidth; 82 | /* -------------------------------------------------------- */ 83 | 84 | int tb_wcwidth(const wchar_t c) { 85 | return func_wcwidth(c); 86 | } 87 | 88 | int tb_init_fd(int inout_) 89 | { 90 | inout = inout_; 91 | if (inout == -1) { 92 | return TB_EFAILED_TO_OPEN_TTY; 93 | } 94 | 95 | if (init_term() < 0) { 96 | close(inout); 97 | return TB_EUNSUPPORTED_TERMINAL; 98 | } 99 | 100 | if (pipe(winch_fds) < 0) { 101 | close(inout); 102 | return TB_EPIPE_TRAP_ERROR; 103 | } 104 | 105 | struct sigaction sa; 106 | memset(&sa, 0, sizeof(sa)); 107 | sa.sa_handler = sigwinch_handler; 108 | sa.sa_flags = 0; 109 | sigaction(SIGWINCH, &sa, &orig_sig); 110 | 111 | tcgetattr(inout, &orig_tios); 112 | 113 | struct termios tios; 114 | memcpy(&tios, &orig_tios, sizeof(tios)); 115 | 116 | tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP 117 | | INLCR | IGNCR | ICRNL | IXON); 118 | tios.c_oflag &= ~OPOST; 119 | tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 120 | tios.c_cflag &= ~(CSIZE | PARENB); 121 | tios.c_cflag |= CS8; 122 | tios.c_cc[VMIN] = 0; 123 | tios.c_cc[VTIME] = 0; 124 | tcsetattr(inout, TCSAFLUSH, &tios); 125 | 126 | bytebuffer_init(&input_buffer, 128); 127 | bytebuffer_init(&output_buffer, 32 * 1024); 128 | 129 | bytebuffer_puts(&output_buffer, funcs[T_ENTER_CA]); 130 | bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]); 131 | bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]); 132 | send_clear(); 133 | 134 | update_term_size(); 135 | cellbuf_init(&back_buffer, termw, termh); 136 | cellbuf_init(&front_buffer, termw, termh); 137 | cellbuf_clear(&back_buffer); 138 | cellbuf_clear(&front_buffer); 139 | 140 | return 0; 141 | } 142 | 143 | void tb_use_wcwidth_cjk(int flg){ 144 | if(flg == 0) func_wcwidth = wcwidth; 145 | else func_wcwidth = wcwidth_cjk; 146 | } 147 | 148 | int tb_init_file(const char* name){ 149 | return tb_init_fd(open(name, O_RDWR)); 150 | } 151 | 152 | int tb_init(void) 153 | { 154 | return tb_init_file("/dev/tty"); 155 | } 156 | 157 | void tb_shutdown(void) 158 | { 159 | if (termw == -1) { 160 | fputs("tb_shutdown() should not be called twice.", stderr); 161 | abort(); 162 | } 163 | 164 | bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]); 165 | bytebuffer_puts(&output_buffer, funcs[T_SGR0]); 166 | bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]); 167 | bytebuffer_puts(&output_buffer, funcs[T_EXIT_CA]); 168 | bytebuffer_puts(&output_buffer, funcs[T_EXIT_KEYPAD]); 169 | bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]); 170 | bytebuffer_flush(&output_buffer, inout); 171 | tcsetattr(inout, TCSAFLUSH, &orig_tios); 172 | 173 | orig_sig.sa_flags = SA_RESETHAND; 174 | sigaction(SIGWINCH, &orig_sig, 0); 175 | 176 | shutdown_term(); 177 | close(inout); 178 | close(winch_fds[0]); 179 | close(winch_fds[1]); 180 | 181 | cellbuf_free(&back_buffer); 182 | cellbuf_free(&front_buffer); 183 | bytebuffer_free(&output_buffer); 184 | bytebuffer_free(&input_buffer); 185 | termw = termh = -1; 186 | } 187 | 188 | void tb_present(void) 189 | { 190 | int x,y,w,i; 191 | struct tb_cell *back, *front; 192 | 193 | /* invalidate cursor position */ 194 | lastx = LAST_COORD_INIT; 195 | lasty = LAST_COORD_INIT; 196 | 197 | if (buffer_size_change_request) { 198 | update_size(); 199 | buffer_size_change_request = 0; 200 | } 201 | 202 | for (y = 0; y < front_buffer.height; ++y) { 203 | for (x = 0; x < front_buffer.width; ) { 204 | back = &CELL(&back_buffer, x, y); 205 | front = &CELL(&front_buffer, x, y); 206 | w = func_wcwidth(back->ch); 207 | if (w < 1) w = 1; 208 | if (memcmp(back, front, sizeof(struct tb_cell)) == 0) { 209 | x += w; 210 | continue; 211 | } 212 | memcpy(front, back, sizeof(struct tb_cell)); 213 | send_attr(back->fg, back->bg); 214 | if (w > 1 && x >= front_buffer.width - (w - 1)) { 215 | // Not enough room for wide ch, so send spaces 216 | for (i = x; i < front_buffer.width; ++i) { 217 | send_char(i, y, ' '); 218 | } 219 | } else { 220 | send_char(x, y, back->ch); 221 | for (i = 1; i < w; ++i) { 222 | front = &CELL(&front_buffer, x + i, y); 223 | front->ch = 0; 224 | front->fg = back->fg; 225 | front->bg = back->bg; 226 | } 227 | } 228 | x += w; 229 | } 230 | } 231 | if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) 232 | write_cursor(cursor_x, cursor_y); 233 | bytebuffer_flush(&output_buffer, inout); 234 | } 235 | 236 | void tb_set_cursor(int cx, int cy) 237 | { 238 | if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy)) 239 | bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]); 240 | 241 | if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy)) 242 | bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]); 243 | 244 | cursor_x = cx; 245 | cursor_y = cy; 246 | if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) 247 | write_cursor(cursor_x, cursor_y); 248 | } 249 | 250 | void tb_put_cell(int x, int y, const struct tb_cell *cell) 251 | { 252 | if ((unsigned)x >= (unsigned)back_buffer.width) 253 | return; 254 | if ((unsigned)y >= (unsigned)back_buffer.height) 255 | return; 256 | CELL(&back_buffer, x, y) = *cell; 257 | } 258 | 259 | void tb_put_cell_front(int x, int y, const struct tb_cell *cell) 260 | { 261 | if ((unsigned)x >= (unsigned)back_buffer.width) 262 | return; 263 | if ((unsigned)y >= (unsigned)back_buffer.height) 264 | return; 265 | CELL(&front_buffer, x, y) = *cell; 266 | } 267 | 268 | void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg) 269 | { 270 | struct tb_cell c = {ch, fg, bg}; 271 | tb_put_cell(x, y, &c); 272 | } 273 | 274 | void tb_change_cell_front(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg) 275 | { 276 | struct tb_cell c = {ch, fg, bg}; 277 | tb_put_cell_front(x, y, &c); 278 | } 279 | 280 | void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells) 281 | { 282 | if (x + w < 0 || x >= back_buffer.width) 283 | return; 284 | if (y + h < 0 || y >= back_buffer.height) 285 | return; 286 | int xo = 0, yo = 0, ww = w, hh = h; 287 | if (x < 0) { 288 | xo = -x; 289 | ww -= xo; 290 | x = 0; 291 | } 292 | if (y < 0) { 293 | yo = -y; 294 | hh -= yo; 295 | y = 0; 296 | } 297 | if (ww > back_buffer.width - x) 298 | ww = back_buffer.width - x; 299 | if (hh > back_buffer.height - y) 300 | hh = back_buffer.height - y; 301 | 302 | int sy; 303 | struct tb_cell *dst = &CELL(&back_buffer, x, y); 304 | const struct tb_cell *src = cells + yo * w + xo; 305 | size_t size = sizeof(struct tb_cell) * ww; 306 | 307 | for (sy = 0; sy < hh; ++sy) { 308 | memcpy(dst, src, size); 309 | dst += back_buffer.width; 310 | src += w; 311 | } 312 | } 313 | 314 | struct tb_cell *tb_cell_buffer(void) 315 | { 316 | return back_buffer.cells; 317 | } 318 | 319 | int tb_poll_event(struct tb_event *event) 320 | { 321 | return wait_fill_event(event, 0); 322 | } 323 | 324 | int tb_peek_event(struct tb_event *event, int timeout) 325 | { 326 | struct timeval tv; 327 | tv.tv_sec = timeout / 1000; 328 | tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; 329 | return wait_fill_event(event, &tv); 330 | } 331 | 332 | int tb_width(void) 333 | { 334 | return termw; 335 | } 336 | 337 | int tb_height(void) 338 | { 339 | return termh; 340 | } 341 | 342 | void tb_clear(void) 343 | { 344 | if (buffer_size_change_request) { 345 | update_size(); 346 | buffer_size_change_request = 0; 347 | } 348 | cellbuf_clear(&back_buffer); 349 | } 350 | 351 | int tb_select_input_mode(int mode) 352 | { 353 | if (mode) { 354 | if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) 355 | mode |= TB_INPUT_ESC; 356 | 357 | /* technically termbox can handle that, but let's be nice and show here 358 | what mode is actually used */ 359 | if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT)) 360 | mode &= ~TB_INPUT_ALT; 361 | 362 | inputmode = mode; 363 | if (mode&TB_INPUT_MOUSE) { 364 | bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]); 365 | bytebuffer_flush(&output_buffer, inout); 366 | } else { 367 | bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]); 368 | bytebuffer_flush(&output_buffer, inout); 369 | } 370 | } 371 | return inputmode; 372 | } 373 | 374 | int tb_select_output_mode(int mode) 375 | { 376 | if (mode) 377 | outputmode = mode; 378 | return outputmode; 379 | } 380 | 381 | void tb_set_clear_attributes(uint16_t fg, uint16_t bg) 382 | { 383 | foreground = fg; 384 | background = bg; 385 | } 386 | 387 | /* -------------------------------------------------------- */ 388 | 389 | static int convertnum(uint32_t num, char* buf) { 390 | int i, l = 0; 391 | int ch; 392 | do { 393 | buf[l++] = '0' + (num % 10); 394 | num /= 10; 395 | } while (num); 396 | for(i = 0; i < l / 2; i++) { 397 | ch = buf[i]; 398 | buf[i] = buf[l - 1 - i]; 399 | buf[l - 1 - i] = ch; 400 | } 401 | return l; 402 | } 403 | 404 | #define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X)-1) 405 | #define WRITE_INT(X) bytebuffer_append(&output_buffer, buf, convertnum((X), buf)) 406 | 407 | static void write_cursor(int x, int y) { 408 | char buf[32]; 409 | WRITE_LITERAL("\033["); 410 | WRITE_INT(y+1); 411 | WRITE_LITERAL(";"); 412 | WRITE_INT(x+1); 413 | WRITE_LITERAL("H"); 414 | } 415 | 416 | static void write_sgr(uint16_t fg, uint16_t bg) { 417 | char buf[32]; 418 | 419 | if (fg == TB_DEFAULT && bg == TB_DEFAULT) 420 | return; 421 | 422 | switch (outputmode) { 423 | case TB_OUTPUT_256: 424 | case TB_OUTPUT_216: 425 | case TB_OUTPUT_GRAYSCALE: 426 | WRITE_LITERAL("\033["); 427 | if (fg != TB_DEFAULT) { 428 | WRITE_LITERAL("38;5;"); 429 | WRITE_INT(fg); 430 | if (bg != TB_DEFAULT) { 431 | WRITE_LITERAL(";"); 432 | } 433 | } 434 | if (bg != TB_DEFAULT) { 435 | WRITE_LITERAL("48;5;"); 436 | WRITE_INT(bg); 437 | } 438 | WRITE_LITERAL("m"); 439 | break; 440 | case TB_OUTPUT_NORMAL: 441 | default: 442 | WRITE_LITERAL("\033["); 443 | if (fg != TB_DEFAULT) { 444 | WRITE_LITERAL("3"); 445 | WRITE_INT(fg - 1); 446 | if (bg != TB_DEFAULT) { 447 | WRITE_LITERAL(";"); 448 | } 449 | } 450 | if (bg != TB_DEFAULT) { 451 | WRITE_LITERAL("4"); 452 | WRITE_INT(bg - 1); 453 | } 454 | WRITE_LITERAL("m"); 455 | break; 456 | } 457 | } 458 | 459 | static void cellbuf_init(struct cellbuf *buf, int width, int height) 460 | { 461 | buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height); 462 | assert(buf->cells); 463 | buf->width = width; 464 | buf->height = height; 465 | } 466 | 467 | static void cellbuf_resize(struct cellbuf *buf, int width, int height) 468 | { 469 | if (buf->width == width && buf->height == height) 470 | return; 471 | 472 | int oldw = buf->width; 473 | int oldh = buf->height; 474 | struct tb_cell *oldcells = buf->cells; 475 | 476 | cellbuf_init(buf, width, height); 477 | cellbuf_clear(buf); 478 | 479 | int minw = (width < oldw) ? width : oldw; 480 | int minh = (height < oldh) ? height : oldh; 481 | int i; 482 | 483 | for (i = 0; i < minh; ++i) { 484 | struct tb_cell *csrc = oldcells + (i * oldw); 485 | struct tb_cell *cdst = buf->cells + (i * width); 486 | memcpy(cdst, csrc, sizeof(struct tb_cell) * minw); 487 | } 488 | 489 | free(oldcells); 490 | } 491 | 492 | static void cellbuf_clear(struct cellbuf *buf) 493 | { 494 | int i; 495 | int ncells = buf->width * buf->height; 496 | 497 | for (i = 0; i < ncells; ++i) { 498 | buf->cells[i].ch = ' '; 499 | buf->cells[i].fg = foreground; 500 | buf->cells[i].bg = background; 501 | } 502 | } 503 | 504 | static void cellbuf_free(struct cellbuf *buf) 505 | { 506 | free(buf->cells); 507 | } 508 | 509 | static void get_term_size(int *w, int *h) 510 | { 511 | struct winsize sz; 512 | memset(&sz, 0, sizeof(sz)); 513 | 514 | ioctl(inout, TIOCGWINSZ, &sz); 515 | 516 | if (w) *w = sz.ws_col; 517 | if (h) *h = sz.ws_row; 518 | } 519 | 520 | static void update_term_size(void) 521 | { 522 | struct winsize sz; 523 | memset(&sz, 0, sizeof(sz)); 524 | 525 | ioctl(inout, TIOCGWINSZ, &sz); 526 | 527 | termw = sz.ws_col; 528 | termh = sz.ws_row; 529 | } 530 | 531 | static void send_attr(uint16_t fg, uint16_t bg) 532 | { 533 | #define LAST_ATTR_INIT 0xFFFF 534 | static uint16_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT; 535 | if (fg != lastfg || bg != lastbg) { 536 | bytebuffer_puts(&output_buffer, funcs[T_SGR0]); 537 | 538 | uint16_t fgcol; 539 | uint16_t bgcol; 540 | 541 | switch (outputmode) { 542 | case TB_OUTPUT_256: 543 | fgcol = fg & 0xFF; 544 | bgcol = bg & 0xFF; 545 | break; 546 | 547 | case TB_OUTPUT_216: 548 | fgcol = fg & 0xFF; if (fgcol > 215) fgcol = 7; 549 | bgcol = bg & 0xFF; if (bgcol > 215) bgcol = 0; 550 | fgcol += 0x10; 551 | bgcol += 0x10; 552 | break; 553 | 554 | case TB_OUTPUT_GRAYSCALE: 555 | fgcol = fg & 0xFF; if (fgcol > 23) fgcol = 23; 556 | bgcol = bg & 0xFF; if (bgcol > 23) bgcol = 0; 557 | fgcol += 0xe8; 558 | bgcol += 0xe8; 559 | break; 560 | 561 | case TB_OUTPUT_NORMAL: 562 | default: 563 | fgcol = fg & 0x0F; 564 | bgcol = bg & 0x0F; 565 | } 566 | 567 | if (fg & TB_BOLD) 568 | bytebuffer_puts(&output_buffer, funcs[T_BOLD]); 569 | if (bg & TB_BOLD) 570 | bytebuffer_puts(&output_buffer, funcs[T_BLINK]); 571 | if (fg & TB_UNDERLINE) 572 | bytebuffer_puts(&output_buffer, funcs[T_UNDERLINE]); 573 | if ((fg & TB_REVERSE) || (bg & TB_REVERSE)) 574 | bytebuffer_puts(&output_buffer, funcs[T_REVERSE]); 575 | 576 | write_sgr(fgcol, bgcol); 577 | 578 | lastfg = fg; 579 | lastbg = bg; 580 | } 581 | } 582 | 583 | static void send_char(int x, int y, uint32_t c) 584 | { 585 | char buf[7]; 586 | int bw = tb_utf8_unicode_to_char(buf, c); 587 | if (x-1 != lastx || y != lasty) 588 | write_cursor(x, y); 589 | lastx = x; lasty = y; 590 | if(!c) buf[0] = ' '; // replace 0 with whitespace 591 | bytebuffer_append(&output_buffer, buf, bw); 592 | } 593 | 594 | static void send_clear(void) 595 | { 596 | send_attr(foreground, background); 597 | bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]); 598 | if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) 599 | write_cursor(cursor_x, cursor_y); 600 | bytebuffer_flush(&output_buffer, inout); 601 | 602 | /* we need to invalidate cursor position too and these two vars are 603 | * used only for simple cursor positioning optimization, cursor 604 | * actually may be in the correct place, but we simply discard 605 | * optimization once and it gives us simple solution for the case when 606 | * cursor moved */ 607 | lastx = LAST_COORD_INIT; 608 | lasty = LAST_COORD_INIT; 609 | } 610 | 611 | static void sigwinch_handler(int xxx) 612 | { 613 | (void) xxx; 614 | const int zzz = 1; 615 | write(winch_fds[1], &zzz, sizeof(int)); 616 | } 617 | 618 | static void update_size(void) 619 | { 620 | update_term_size(); 621 | cellbuf_resize(&back_buffer, termw, termh); 622 | cellbuf_resize(&front_buffer, termw, termh); 623 | cellbuf_clear(&front_buffer); 624 | send_clear(); 625 | } 626 | 627 | static int read_up_to(int n) { 628 | assert(n > 0); 629 | const int prevlen = input_buffer.len; 630 | bytebuffer_resize(&input_buffer, prevlen + n); 631 | 632 | int read_n = 0; 633 | while (read_n <= n) { 634 | ssize_t r = 0; 635 | if (read_n < n) { 636 | r = read(inout, input_buffer.buf + prevlen + read_n, n - read_n); 637 | } 638 | #ifdef __CYGWIN__ 639 | // While linux man for tty says when VMIN == 0 && VTIME == 0, read 640 | // should return 0 when there is nothing to read, cygwin's read returns 641 | // -1. Not sure why and if it's correct to ignore it, but let's pretend 642 | // it's zero. 643 | if (r < 0) r = 0; 644 | #endif 645 | if (r < 0) { 646 | // EAGAIN / EWOULDBLOCK shouldn't occur here 647 | assert(errno != EAGAIN && errno != EWOULDBLOCK); 648 | return -1; 649 | } else if (r > 0) { 650 | read_n += r; 651 | } else { 652 | bytebuffer_resize(&input_buffer, prevlen + read_n); 653 | return read_n; 654 | } 655 | } 656 | assert(!"unreachable"); 657 | return 0; 658 | } 659 | 660 | static int wait_fill_event(struct tb_event *event, struct timeval *timeout) 661 | { 662 | // ;-) 663 | #define ENOUGH_DATA_FOR_PARSING 64 664 | fd_set events; 665 | memset(event, 0, sizeof(struct tb_event)); 666 | 667 | // try to extract event from input buffer, return on success 668 | event->type = TB_EVENT_KEY; 669 | if (extract_event(event, &input_buffer, inputmode)) 670 | return event->type; 671 | 672 | // it looks like input buffer is incomplete, let's try the short path, 673 | // but first make sure there is enough space 674 | int n = read_up_to(ENOUGH_DATA_FOR_PARSING); 675 | if (n < 0) 676 | return -1; 677 | if (n > 0 && extract_event(event, &input_buffer, inputmode)) 678 | return event->type; 679 | 680 | // n == 0, or not enough data, let's go to select 681 | while (1) { 682 | FD_ZERO(&events); 683 | FD_SET(inout, &events); 684 | FD_SET(winch_fds[0], &events); 685 | int maxfd = (winch_fds[0] > inout) ? winch_fds[0] : inout; 686 | int result = select(maxfd+1, &events, 0, 0, timeout); 687 | if (!result) 688 | return 0; 689 | 690 | if (FD_ISSET(inout, &events)) { 691 | event->type = TB_EVENT_KEY; 692 | n = read_up_to(ENOUGH_DATA_FOR_PARSING); 693 | if (n < 0) 694 | return -1; 695 | 696 | if (n == 0) 697 | continue; 698 | 699 | if (extract_event(event, &input_buffer, inputmode)) 700 | return event->type; 701 | } 702 | if (FD_ISSET(winch_fds[0], &events)) { 703 | event->type = TB_EVENT_RESIZE; 704 | int zzz = 0; 705 | read(winch_fds[0], &zzz, sizeof(int)); 706 | buffer_size_change_request = 1; 707 | get_term_size(&event->w, &event->h); 708 | return TB_EVENT_RESIZE; 709 | } 710 | } 711 | } 712 | -------------------------------------------------------------------------------- /cmdline/cmdline.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009, Hideyuki Tanaka 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | namespace cmdline{ 43 | 44 | namespace detail{ 45 | 46 | template 47 | class lexical_cast_t{ 48 | public: 49 | static Target cast(const Source &arg){ 50 | Target ret; 51 | std::stringstream ss; 52 | if (!(ss<>ret && ss.eof())) 53 | throw std::bad_cast(); 54 | 55 | return ret; 56 | } 57 | }; 58 | 59 | template 60 | class lexical_cast_t{ 61 | public: 62 | static Target cast(const Source &arg){ 63 | return arg; 64 | } 65 | }; 66 | 67 | template 68 | class lexical_cast_t{ 69 | public: 70 | static std::string cast(const Source &arg){ 71 | std::ostringstream ss; 72 | ss< 78 | class lexical_cast_t{ 79 | public: 80 | static Target cast(const std::string &arg){ 81 | Target ret; 82 | std::istringstream ss(arg); 83 | if (!(ss>>ret && ss.eof())) 84 | throw std::bad_cast(); 85 | return ret; 86 | } 87 | }; 88 | 89 | template 90 | struct is_same { 91 | static const bool value = false; 92 | }; 93 | 94 | template 95 | struct is_same{ 96 | static const bool value = true; 97 | }; 98 | 99 | template 100 | Target lexical_cast(const Source &arg) 101 | { 102 | return lexical_cast_t::value>::cast(arg); 103 | } 104 | 105 | static inline std::string demangle(const std::string &name) 106 | { 107 | int status=0; 108 | char *p=abi::__cxa_demangle(name.c_str(), 0, 0, &status); 109 | std::string ret(p); 110 | free(p); 111 | return ret; 112 | } 113 | 114 | template 115 | std::string readable_typename() 116 | { 117 | return demangle(typeid(T).name()); 118 | } 119 | 120 | template 121 | std::string default_value(T def) 122 | { 123 | return detail::lexical_cast(def); 124 | } 125 | 126 | template <> 127 | inline std::string readable_typename() 128 | { 129 | return "string"; 130 | } 131 | 132 | } // detail 133 | 134 | //----- 135 | 136 | class cmdline_error : public std::exception { 137 | public: 138 | cmdline_error(const std::string &msg): msg(msg){} 139 | ~cmdline_error() throw() {} 140 | const char *what() const throw() { return msg.c_str(); } 141 | private: 142 | std::string msg; 143 | }; 144 | 145 | template 146 | struct default_reader{ 147 | T operator()(const std::string &str){ 148 | return detail::lexical_cast(str); 149 | } 150 | }; 151 | 152 | template 153 | struct range_reader{ 154 | range_reader(const T &low, const T &high): low(low), high(high) {} 155 | T operator()(const std::string &s) const { 156 | T ret=default_reader()(s); 157 | if (!(ret>=low && ret<=high)) throw cmdline::cmdline_error("range_error"); 158 | return ret; 159 | } 160 | private: 161 | T low, high; 162 | }; 163 | 164 | template 165 | range_reader range(const T &low, const T &high) 166 | { 167 | return range_reader(low, high); 168 | } 169 | 170 | template 171 | struct oneof_reader{ 172 | T operator()(const std::string &s){ 173 | T ret=default_reader()(s); 174 | if (std::find(alt.begin(), alt.end(), ret)==alt.end()) 175 | throw cmdline_error(""); 176 | return ret; 177 | } 178 | void add(const T &v){ alt.push_back(v); } 179 | private: 180 | std::vector alt; 181 | }; 182 | 183 | template 184 | oneof_reader oneof(T a1) 185 | { 186 | oneof_reader ret; 187 | ret.add(a1); 188 | return ret; 189 | } 190 | 191 | template 192 | oneof_reader oneof(T a1, T a2) 193 | { 194 | oneof_reader ret; 195 | ret.add(a1); 196 | ret.add(a2); 197 | return ret; 198 | } 199 | 200 | template 201 | oneof_reader oneof(T a1, T a2, T a3) 202 | { 203 | oneof_reader ret; 204 | ret.add(a1); 205 | ret.add(a2); 206 | ret.add(a3); 207 | return ret; 208 | } 209 | 210 | template 211 | oneof_reader oneof(T a1, T a2, T a3, T a4) 212 | { 213 | oneof_reader ret; 214 | ret.add(a1); 215 | ret.add(a2); 216 | ret.add(a3); 217 | ret.add(a4); 218 | return ret; 219 | } 220 | 221 | template 222 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5) 223 | { 224 | oneof_reader ret; 225 | ret.add(a1); 226 | ret.add(a2); 227 | ret.add(a3); 228 | ret.add(a4); 229 | ret.add(a5); 230 | return ret; 231 | } 232 | 233 | template 234 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6) 235 | { 236 | oneof_reader ret; 237 | ret.add(a1); 238 | ret.add(a2); 239 | ret.add(a3); 240 | ret.add(a4); 241 | ret.add(a5); 242 | ret.add(a6); 243 | return ret; 244 | } 245 | 246 | template 247 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7) 248 | { 249 | oneof_reader ret; 250 | ret.add(a1); 251 | ret.add(a2); 252 | ret.add(a3); 253 | ret.add(a4); 254 | ret.add(a5); 255 | ret.add(a6); 256 | ret.add(a7); 257 | return ret; 258 | } 259 | 260 | template 261 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8) 262 | { 263 | oneof_reader ret; 264 | ret.add(a1); 265 | ret.add(a2); 266 | ret.add(a3); 267 | ret.add(a4); 268 | ret.add(a5); 269 | ret.add(a6); 270 | ret.add(a7); 271 | ret.add(a8); 272 | return ret; 273 | } 274 | 275 | template 276 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8, T a9) 277 | { 278 | oneof_reader ret; 279 | ret.add(a1); 280 | ret.add(a2); 281 | ret.add(a3); 282 | ret.add(a4); 283 | ret.add(a5); 284 | ret.add(a6); 285 | ret.add(a7); 286 | ret.add(a8); 287 | ret.add(a9); 288 | return ret; 289 | } 290 | 291 | template 292 | oneof_reader oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8, T a9, T a10) 293 | { 294 | oneof_reader ret; 295 | ret.add(a1); 296 | ret.add(a2); 297 | ret.add(a3); 298 | ret.add(a4); 299 | ret.add(a5); 300 | ret.add(a6); 301 | ret.add(a7); 302 | ret.add(a8); 303 | ret.add(a9); 304 | ret.add(a10); 305 | return ret; 306 | } 307 | 308 | //----- 309 | 310 | class parser{ 311 | public: 312 | parser(){ 313 | } 314 | ~parser(){ 315 | for (std::map::iterator p=options.begin(); 316 | p!=options.end(); p++) 317 | delete p->second; 318 | } 319 | 320 | void add(const std::string &name, 321 | char short_name=0, 322 | const std::string &desc=""){ 323 | if (options.count(name)) throw cmdline_error("multiple definition: "+name); 324 | options[name]=new option_without_value(name, short_name, desc); 325 | ordered.push_back(options[name]); 326 | } 327 | 328 | template 329 | void add(const std::string &name, 330 | char short_name=0, 331 | const std::string &desc="", 332 | bool need=true, 333 | const T def=T()){ 334 | add(name, short_name, desc, need, def, default_reader()); 335 | } 336 | 337 | template 338 | void add(const std::string &name, 339 | char short_name=0, 340 | const std::string &desc="", 341 | bool need=true, 342 | const T def=T(), 343 | F reader=F()){ 344 | if (options.count(name)) throw cmdline_error("multiple definition: "+name); 345 | options[name]=new option_with_value_with_reader(name, short_name, need, def, desc, reader); 346 | ordered.push_back(options[name]); 347 | } 348 | 349 | void footer(const std::string &f){ 350 | ftr=f; 351 | } 352 | 353 | void set_program_name(const std::string &name){ 354 | prog_name=name; 355 | } 356 | 357 | bool exist(const std::string &name) const { 358 | if (options.count(name)==0) throw cmdline_error("there is no flag: --"+name); 359 | return options.find(name)->second->has_set(); 360 | } 361 | 362 | template 363 | const T &get(const std::string &name) const { 364 | if (options.count(name)==0) throw cmdline_error("there is no flag: --"+name); 365 | const option_with_value *p=dynamic_cast*>(options.find(name)->second); 366 | if (p==NULL) throw cmdline_error("type mismatch flag '"+name+"'"); 367 | return p->get(); 368 | } 369 | 370 | const std::vector &rest() const { 371 | return others; 372 | } 373 | 374 | bool parse(const std::string &arg){ 375 | std::vector args; 376 | 377 | std::string buf; 378 | bool in_quote=false; 379 | for (std::string::size_type i=0; i=arg.length()){ 394 | errors.push_back("unexpected occurrence of '\\' at end of string"); 395 | return false; 396 | } 397 | } 398 | 399 | buf+=arg[i]; 400 | } 401 | 402 | if (in_quote){ 403 | errors.push_back("quote is not closed"); 404 | return false; 405 | } 406 | 407 | if (buf.length()>0) 408 | args.push_back(buf); 409 | 410 | for (size_t i=0; i &args){ 417 | int argc=static_cast(args.size()); 418 | std::vector argv(argc); 419 | 420 | for (int i=0; i lookup; 438 | for (std::map::iterator p=options.begin(); 439 | p!=options.end(); p++){ 440 | if (p->first.length()==0) continue; 441 | char initial=p->second->short_name(); 442 | if (initial){ 443 | if (lookup.count(initial)>0){ 444 | lookup[initial]=""; 445 | errors.push_back(std::string("short option '")+initial+"' is ambiguous"); 446 | return false; 447 | } 448 | else lookup[initial]=p->first; 449 | } 450 | } 451 | 452 | for (int i=1; i &args){ 534 | if (!options.count("help")) 535 | add("help", '?', "print this message"); 536 | check(args.size(), parse(args)); 537 | } 538 | 539 | void parse_check(int argc, char *argv[]){ 540 | if (!options.count("help")) 541 | add("help", '?', "print this message"); 542 | check(argc, parse(argc, argv)); 543 | } 544 | 545 | std::string error() const{ 546 | return errors.size()>0?errors[0]:""; 547 | } 548 | 549 | std::string error_full() const{ 550 | std::ostringstream oss; 551 | for (size_t i=0; imust()) 561 | oss<short_description()<<" "; 562 | } 563 | 564 | oss<<"[options] ... "<name().length()); 570 | } 571 | for (size_t i=0; ishort_name()){ 573 | oss<<" -"<short_name()<<", "; 574 | } 575 | else{ 576 | oss<<" "; 577 | } 578 | 579 | oss<<"--"<name(); 580 | for (size_t j=ordered[i]->name().length(); jdescription()<set()){ 607 | errors.push_back("option needs value: --"+name); 608 | return; 609 | } 610 | } 611 | 612 | void set_option(const std::string &name, const std::string &value){ 613 | if (options.count(name)==0){ 614 | errors.push_back("undefined option: --"+name); 615 | return; 616 | } 617 | if (!options[name]->set(value)){ 618 | errors.push_back("option value is invalid: --"+name+"="+value); 619 | return; 620 | } 621 | } 622 | 623 | class option_base{ 624 | public: 625 | virtual ~option_base(){} 626 | 627 | virtual bool has_value() const=0; 628 | virtual bool set()=0; 629 | virtual bool set(const std::string &value)=0; 630 | virtual bool has_set() const=0; 631 | virtual bool valid() const=0; 632 | virtual bool must() const=0; 633 | 634 | virtual const std::string &name() const=0; 635 | virtual char short_name() const=0; 636 | virtual const std::string &description() const=0; 637 | virtual std::string short_description() const=0; 638 | }; 639 | 640 | class option_without_value : public option_base { 641 | public: 642 | option_without_value(const std::string &name, 643 | char short_name, 644 | const std::string &desc) 645 | :nam(name), snam(short_name), desc(desc), has(false){ 646 | } 647 | ~option_without_value(){} 648 | 649 | bool has_value() const { return false; } 650 | 651 | bool set(){ 652 | has=true; 653 | return true; 654 | } 655 | 656 | bool set(const std::string &){ 657 | return false; 658 | } 659 | 660 | bool has_set() const { 661 | return has; 662 | } 663 | 664 | bool valid() const{ 665 | return true; 666 | } 667 | 668 | bool must() const{ 669 | return false; 670 | } 671 | 672 | const std::string &name() const{ 673 | return nam; 674 | } 675 | 676 | char short_name() const{ 677 | return snam; 678 | } 679 | 680 | const std::string &description() const { 681 | return desc; 682 | } 683 | 684 | std::string short_description() const{ 685 | return "--"+nam; 686 | } 687 | 688 | private: 689 | std::string nam; 690 | char snam; 691 | std::string desc; 692 | bool has; 693 | }; 694 | 695 | template 696 | class option_with_value : public option_base { 697 | public: 698 | option_with_value(const std::string &name, 699 | char short_name, 700 | bool need, 701 | const T &def, 702 | const std::string &desc) 703 | : nam(name), snam(short_name), need(need), has(false) 704 | , def(def), actual(def) { 705 | this->desc=full_description(desc); 706 | } 707 | ~option_with_value(){} 708 | 709 | const T &get() const { 710 | return actual; 711 | } 712 | 713 | bool has_value() const { return true; } 714 | 715 | bool set(){ 716 | return false; 717 | } 718 | 719 | bool set(const std::string &value){ 720 | try{ 721 | actual=read(value); 722 | has=true; 723 | } 724 | catch(const std::exception &e){ 725 | return false; 726 | } 727 | return true; 728 | } 729 | 730 | bool has_set() const{ 731 | return has; 732 | } 733 | 734 | bool valid() const{ 735 | if (need && !has) return false; 736 | return true; 737 | } 738 | 739 | bool must() const{ 740 | return need; 741 | } 742 | 743 | const std::string &name() const{ 744 | return nam; 745 | } 746 | 747 | char short_name() const{ 748 | return snam; 749 | } 750 | 751 | const std::string &description() const { 752 | return desc; 753 | } 754 | 755 | std::string short_description() const{ 756 | return "--"+nam+"="+detail::readable_typename(); 757 | } 758 | 759 | protected: 760 | std::string full_description(const std::string &desc){ 761 | return 762 | desc+" ("+detail::readable_typename()+ 763 | (need?"":" [="+detail::default_value(def)+"]") 764 | +")"; 765 | } 766 | 767 | virtual T read(const std::string &s)=0; 768 | 769 | std::string nam; 770 | char snam; 771 | bool need; 772 | std::string desc; 773 | 774 | bool has; 775 | T def; 776 | T actual; 777 | }; 778 | 779 | template 780 | class option_with_value_with_reader : public option_with_value { 781 | public: 782 | option_with_value_with_reader(const std::string &name, 783 | char short_name, 784 | bool need, 785 | const T def, 786 | const std::string &desc, 787 | F reader) 788 | : option_with_value(name, short_name, need, def, desc), reader(reader){ 789 | } 790 | 791 | private: 792 | T read(const std::string &s){ 793 | return reader(s); 794 | } 795 | 796 | F reader; 797 | }; 798 | 799 | std::map options; 800 | std::vector ordered; 801 | std::string ftr; 802 | 803 | std::string prog_name; 804 | std::vector others; 805 | 806 | std::vector errors; 807 | }; 808 | 809 | } // cmdline 810 | -------------------------------------------------------------------------------- /tsl/robin_set.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Tessil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #ifndef TSL_ROBIN_SET_H 25 | #define TSL_ROBIN_SET_H 26 | 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "robin_hash.h" 35 | 36 | 37 | namespace tsl { 38 | 39 | 40 | /** 41 | * Implementation of a hash set using open-adressing and the robin hood hashing algorithm with backward shift deletion. 42 | * 43 | * For operations modifying the hash set (insert, erase, rehash, ...), the strong exception guarantee 44 | * is only guaranteed when the expression `std::is_nothrow_swappable::value && 45 | * std::is_nothrow_move_constructible::value` is true, otherwise if an exception 46 | * is thrown during the swap or the move, the hash set may end up in a undefined state. Per the standard 47 | * a `Key` with a noexcept copy constructor and no move constructor also satisfies the 48 | * `std::is_nothrow_move_constructible::value` criterion (and will thus guarantee the 49 | * strong exception for the set). 50 | * 51 | * When `StoreHash` is true, 32 bits of the hash are stored alongside the values. It can improve 52 | * the performance during lookups if the `KeyEqual` function takes time (or engenders a cache-miss for example) 53 | * as we then compare the stored hashes before comparing the keys. When `tsl::rh::power_of_two_growth_policy` is used 54 | * as `GrowthPolicy`, it may also speed-up the rehash process as we can avoid to recalculate the hash. 55 | * When it is detected that storing the hash will not incur any memory penality due to alignement (i.e. 56 | * `sizeof(tsl::detail_robin_hash::bucket_entry) == 57 | * sizeof(tsl::detail_robin_hash::bucket_entry)`) and `tsl::rh::power_of_two_growth_policy` is 58 | * used, the hash will be stored even if `StoreHash` is false so that we can speed-up the rehash (but it will 59 | * not be used on lookups unless `StoreHash` is true). 60 | * 61 | * `GrowthPolicy` defines how the set grows and consequently how a hash value is mapped to a bucket. 62 | * By default the set uses `tsl::rh::power_of_two_growth_policy`. This policy keeps the number of buckets 63 | * to a power of two and uses a mask to set the hash to a bucket instead of the slow modulo. 64 | * Other growth policies are available and you may define your own growth policy, 65 | * check `tsl::rh::power_of_two_growth_policy` for the interface. 66 | * 67 | * `Key` must be swappable. 68 | * 69 | * `Key` must be copy and/or move constructible. 70 | * 71 | * If the destructor of `Key` throws an exception, the behaviour of the class is undefined. 72 | * 73 | * Iterators invalidation: 74 | * - clear, operator=, reserve, rehash: always invalidate the iterators. 75 | * - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators. 76 | * - erase: always invalidate the iterators. 77 | */ 78 | template, 80 | class KeyEqual = std::equal_to, 81 | class Allocator = std::allocator, 82 | bool StoreHash = false, 83 | class GrowthPolicy = tsl::rh::power_of_two_growth_policy<2>> 84 | class robin_set { 85 | private: 86 | template 87 | using has_is_transparent = tsl::detail_robin_hash::has_is_transparent; 88 | 89 | class KeySelect { 90 | public: 91 | using key_type = Key; 92 | 93 | const key_type& operator()(const Key& key) const noexcept { 94 | return key; 95 | } 96 | 97 | key_type& operator()(Key& key) noexcept { 98 | return key; 99 | } 100 | }; 101 | 102 | using ht = detail_robin_hash::robin_hash; 104 | 105 | public: 106 | using key_type = typename ht::key_type; 107 | using value_type = typename ht::value_type; 108 | using size_type = typename ht::size_type; 109 | using difference_type = typename ht::difference_type; 110 | using hasher = typename ht::hasher; 111 | using key_equal = typename ht::key_equal; 112 | using allocator_type = typename ht::allocator_type; 113 | using reference = typename ht::reference; 114 | using const_reference = typename ht::const_reference; 115 | using pointer = typename ht::pointer; 116 | using const_pointer = typename ht::const_pointer; 117 | using iterator = typename ht::iterator; 118 | using const_iterator = typename ht::const_iterator; 119 | 120 | 121 | /* 122 | * Constructors 123 | */ 124 | robin_set(): robin_set(ht::DEFAULT_INIT_BUCKETS_SIZE) { 125 | } 126 | 127 | explicit robin_set(size_type bucket_count, 128 | const Hash& hash = Hash(), 129 | const KeyEqual& equal = KeyEqual(), 130 | const Allocator& alloc = Allocator()): 131 | m_ht(bucket_count, hash, equal, alloc) 132 | { 133 | } 134 | 135 | robin_set(size_type bucket_count, 136 | const Allocator& alloc): robin_set(bucket_count, Hash(), KeyEqual(), alloc) 137 | { 138 | } 139 | 140 | robin_set(size_type bucket_count, 141 | const Hash& hash, 142 | const Allocator& alloc): robin_set(bucket_count, hash, KeyEqual(), alloc) 143 | { 144 | } 145 | 146 | explicit robin_set(const Allocator& alloc): robin_set(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { 147 | } 148 | 149 | template 150 | robin_set(InputIt first, InputIt last, 151 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, 152 | const Hash& hash = Hash(), 153 | const KeyEqual& equal = KeyEqual(), 154 | const Allocator& alloc = Allocator()): robin_set(bucket_count, hash, equal, alloc) 155 | { 156 | insert(first, last); 157 | } 158 | 159 | template 160 | robin_set(InputIt first, InputIt last, 161 | size_type bucket_count, 162 | const Allocator& alloc): robin_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) 163 | { 164 | } 165 | 166 | template 167 | robin_set(InputIt first, InputIt last, 168 | size_type bucket_count, 169 | const Hash& hash, 170 | const Allocator& alloc): robin_set(first, last, bucket_count, hash, KeyEqual(), alloc) 171 | { 172 | } 173 | 174 | robin_set(std::initializer_list init, 175 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, 176 | const Hash& hash = Hash(), 177 | const KeyEqual& equal = KeyEqual(), 178 | const Allocator& alloc = Allocator()): 179 | robin_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) 180 | { 181 | } 182 | 183 | robin_set(std::initializer_list init, 184 | size_type bucket_count, 185 | const Allocator& alloc): 186 | robin_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) 187 | { 188 | } 189 | 190 | robin_set(std::initializer_list init, 191 | size_type bucket_count, 192 | const Hash& hash, 193 | const Allocator& alloc): 194 | robin_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) 195 | { 196 | } 197 | 198 | 199 | robin_set& operator=(std::initializer_list ilist) { 200 | m_ht.clear(); 201 | 202 | m_ht.reserve(ilist.size()); 203 | m_ht.insert(ilist.begin(), ilist.end()); 204 | 205 | return *this; 206 | } 207 | 208 | allocator_type get_allocator() const { return m_ht.get_allocator(); } 209 | 210 | 211 | /* 212 | * Iterators 213 | */ 214 | iterator begin() noexcept { return m_ht.begin(); } 215 | const_iterator begin() const noexcept { return m_ht.begin(); } 216 | const_iterator cbegin() const noexcept { return m_ht.cbegin(); } 217 | 218 | iterator end() noexcept { return m_ht.end(); } 219 | const_iterator end() const noexcept { return m_ht.end(); } 220 | const_iterator cend() const noexcept { return m_ht.cend(); } 221 | 222 | 223 | /* 224 | * Capacity 225 | */ 226 | bool empty() const noexcept { return m_ht.empty(); } 227 | size_type size() const noexcept { return m_ht.size(); } 228 | size_type max_size() const noexcept { return m_ht.max_size(); } 229 | 230 | /* 231 | * Modifiers 232 | */ 233 | void clear() noexcept { m_ht.clear(); } 234 | 235 | 236 | 237 | 238 | std::pair insert(const value_type& value) { 239 | return m_ht.insert(value); 240 | } 241 | 242 | std::pair insert(value_type&& value) { 243 | return m_ht.insert(std::move(value)); 244 | } 245 | 246 | iterator insert(const_iterator hint, const value_type& value) { 247 | return m_ht.insert_hint(hint, value); 248 | } 249 | 250 | iterator insert(const_iterator hint, value_type&& value) { 251 | return m_ht.insert_hint(hint, std::move(value)); 252 | } 253 | 254 | template 255 | void insert(InputIt first, InputIt last) { 256 | m_ht.insert(first, last); 257 | } 258 | 259 | void insert(std::initializer_list ilist) { 260 | m_ht.insert(ilist.begin(), ilist.end()); 261 | } 262 | 263 | 264 | 265 | 266 | /** 267 | * Due to the way elements are stored, emplace will need to move or copy the key-value once. 268 | * The method is equivalent to insert(value_type(std::forward(args)...)); 269 | * 270 | * Mainly here for compatibility with the std::unordered_map interface. 271 | */ 272 | template 273 | std::pair emplace(Args&&... args) { 274 | return m_ht.emplace(std::forward(args)...); 275 | } 276 | 277 | 278 | 279 | /** 280 | * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. 281 | * The method is equivalent to insert(hint, value_type(std::forward(args)...)); 282 | * 283 | * Mainly here for compatibility with the std::unordered_map interface. 284 | */ 285 | template 286 | iterator emplace_hint(const_iterator hint, Args&&... args) { 287 | return m_ht.emplace_hint(hint, std::forward(args)...); 288 | } 289 | 290 | 291 | 292 | iterator erase(iterator pos) { return m_ht.erase(pos); } 293 | iterator erase(const_iterator pos) { return m_ht.erase(pos); } 294 | iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } 295 | size_type erase(const key_type& key) { return m_ht.erase(key); } 296 | 297 | /** 298 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 299 | * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. 300 | */ 301 | size_type erase(const key_type& key, std::size_t precalculated_hash) { 302 | return m_ht.erase(key, precalculated_hash); 303 | } 304 | 305 | /** 306 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 307 | * If so, K must be hashable and comparable to Key. 308 | */ 309 | template::value>::type* = nullptr> 310 | size_type erase(const K& key) { return m_ht.erase(key); } 311 | 312 | /** 313 | * @copydoc erase(const K& key) 314 | * 315 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 316 | * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. 317 | */ 318 | template::value>::type* = nullptr> 319 | size_type erase(const K& key, std::size_t precalculated_hash) { 320 | return m_ht.erase(key, precalculated_hash); 321 | } 322 | 323 | 324 | 325 | void swap(robin_set& other) { other.m_ht.swap(m_ht); } 326 | 327 | 328 | 329 | /* 330 | * Lookup 331 | */ 332 | size_type count(const Key& key) const { return m_ht.count(key); } 333 | 334 | /** 335 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 336 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 337 | */ 338 | size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } 339 | 340 | /** 341 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 342 | * If so, K must be hashable and comparable to Key. 343 | */ 344 | template::value>::type* = nullptr> 345 | size_type count(const K& key) const { return m_ht.count(key); } 346 | 347 | /** 348 | * @copydoc count(const K& key) const 349 | * 350 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 351 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 352 | */ 353 | template::value>::type* = nullptr> 354 | size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } 355 | 356 | 357 | 358 | 359 | iterator find(const Key& key) { return m_ht.find(key); } 360 | 361 | /** 362 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 363 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 364 | */ 365 | iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } 366 | 367 | const_iterator find(const Key& key) const { return m_ht.find(key); } 368 | 369 | /** 370 | * @copydoc find(const Key& key, std::size_t precalculated_hash) 371 | */ 372 | const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } 373 | 374 | /** 375 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 376 | * If so, K must be hashable and comparable to Key. 377 | */ 378 | template::value>::type* = nullptr> 379 | iterator find(const K& key) { return m_ht.find(key); } 380 | 381 | /** 382 | * @copydoc find(const K& key) 383 | * 384 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 385 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 386 | */ 387 | template::value>::type* = nullptr> 388 | iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } 389 | 390 | /** 391 | * @copydoc find(const K& key) 392 | */ 393 | template::value>::type* = nullptr> 394 | const_iterator find(const K& key) const { return m_ht.find(key); } 395 | 396 | /** 397 | * @copydoc find(const K& key) 398 | * 399 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 400 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 401 | */ 402 | template::value>::type* = nullptr> 403 | const_iterator find(const K& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } 404 | 405 | 406 | 407 | 408 | std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } 409 | 410 | /** 411 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 412 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 413 | */ 414 | std::pair equal_range(const Key& key, std::size_t precalculated_hash) { 415 | return m_ht.equal_range(key, precalculated_hash); 416 | } 417 | 418 | std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } 419 | 420 | /** 421 | * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) 422 | */ 423 | std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { 424 | return m_ht.equal_range(key, precalculated_hash); 425 | } 426 | 427 | /** 428 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 429 | * If so, K must be hashable and comparable to Key. 430 | */ 431 | template::value>::type* = nullptr> 432 | std::pair equal_range(const K& key) { return m_ht.equal_range(key); } 433 | 434 | /** 435 | * @copydoc equal_range(const K& key) 436 | * 437 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 438 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 439 | */ 440 | template::value>::type* = nullptr> 441 | std::pair equal_range(const K& key, std::size_t precalculated_hash) { 442 | return m_ht.equal_range(key, precalculated_hash); 443 | } 444 | 445 | /** 446 | * @copydoc equal_range(const K& key) 447 | */ 448 | template::value>::type* = nullptr> 449 | std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } 450 | 451 | /** 452 | * @copydoc equal_range(const K& key, std::size_t precalculated_hash) 453 | */ 454 | template::value>::type* = nullptr> 455 | std::pair equal_range(const K& key, std::size_t precalculated_hash) const { 456 | return m_ht.equal_range(key, precalculated_hash); 457 | } 458 | 459 | 460 | 461 | 462 | /* 463 | * Bucket interface 464 | */ 465 | size_type bucket_count() const { return m_ht.bucket_count(); } 466 | size_type max_bucket_count() const { return m_ht.max_bucket_count(); } 467 | 468 | 469 | /* 470 | * Hash policy 471 | */ 472 | float load_factor() const { return m_ht.load_factor(); } 473 | 474 | float min_load_factor() const { return m_ht.min_load_factor(); } 475 | float max_load_factor() const { return m_ht.max_load_factor(); } 476 | 477 | /** 478 | * Set the `min_load_factor` to `ml`. When the `load_factor` of the set goes 479 | * below `min_load_factor` after some erase operations, the set will be 480 | * shrunk when an insertion occurs. The erase method itself never shrinks 481 | * the set. 482 | * 483 | * The default value of `min_load_factor` is 0.0f, the set never shrinks by default. 484 | */ 485 | void min_load_factor(float ml) { m_ht.min_load_factor(ml); } 486 | void max_load_factor(float ml) { m_ht.max_load_factor(ml); } 487 | 488 | void rehash(size_type count) { m_ht.rehash(count); } 489 | void reserve(size_type count) { m_ht.reserve(count); } 490 | 491 | 492 | /* 493 | * Observers 494 | */ 495 | hasher hash_function() const { return m_ht.hash_function(); } 496 | key_equal key_eq() const { return m_ht.key_eq(); } 497 | 498 | 499 | /* 500 | * Other 501 | */ 502 | 503 | /** 504 | * Convert a const_iterator to an iterator. 505 | */ 506 | iterator mutable_iterator(const_iterator pos) { 507 | return m_ht.mutable_iterator(pos); 508 | } 509 | 510 | friend bool operator==(const robin_set& lhs, const robin_set& rhs) { 511 | if(lhs.size() != rhs.size()) { 512 | return false; 513 | } 514 | 515 | for(const auto& element_lhs: lhs) { 516 | const auto it_element_rhs = rhs.find(element_lhs); 517 | if(it_element_rhs == rhs.cend()) { 518 | return false; 519 | } 520 | } 521 | 522 | return true; 523 | } 524 | 525 | friend bool operator!=(const robin_set& lhs, const robin_set& rhs) { 526 | return !operator==(lhs, rhs); 527 | } 528 | 529 | friend void swap(robin_set& lhs, robin_set& rhs) { 530 | lhs.swap(rhs); 531 | } 532 | 533 | private: 534 | ht m_ht; 535 | }; 536 | 537 | 538 | /** 539 | * Same as `tsl::robin_set`. 540 | */ 541 | template, 543 | class KeyEqual = std::equal_to, 544 | class Allocator = std::allocator, 545 | bool StoreHash = false> 546 | using robin_pg_set = robin_set; 547 | 548 | } // end namespace tsl 549 | 550 | #endif 551 | 552 | -------------------------------------------------------------------------------- /tsl/robin_map.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Tessil 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #ifndef TSL_ROBIN_MAP_H 25 | #define TSL_ROBIN_MAP_H 26 | 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "robin_hash.h" 35 | 36 | 37 | namespace tsl { 38 | 39 | 40 | /** 41 | * Implementation of a hash map using open-adressing and the robin hood hashing algorithm with backward shift deletion. 42 | * 43 | * For operations modifying the hash map (insert, erase, rehash, ...), the strong exception guarantee 44 | * is only guaranteed when the expression `std::is_nothrow_swappable>::value && 45 | * std::is_nothrow_move_constructible>::value` is true, otherwise if an exception 46 | * is thrown during the swap or the move, the hash map may end up in a undefined state. Per the standard 47 | * a `Key` or `T` with a noexcept copy constructor and no move constructor also satisfies the 48 | * `std::is_nothrow_move_constructible>::value` criterion (and will thus guarantee the 49 | * strong exception for the map). 50 | * 51 | * When `StoreHash` is true, 32 bits of the hash are stored alongside the values. It can improve 52 | * the performance during lookups if the `KeyEqual` function takes time (if it engenders a cache-miss for example) 53 | * as we then compare the stored hashes before comparing the keys. When `tsl::rh::power_of_two_growth_policy` is used 54 | * as `GrowthPolicy`, it may also speed-up the rehash process as we can avoid to recalculate the hash. 55 | * When it is detected that storing the hash will not incur any memory penality due to alignement (i.e. 56 | * `sizeof(tsl::detail_robin_hash::bucket_entry) == 57 | * sizeof(tsl::detail_robin_hash::bucket_entry)`) and `tsl::rh::power_of_two_growth_policy` is 58 | * used, the hash will be stored even if `StoreHash` is false so that we can speed-up the rehash (but it will 59 | * not be used on lookups unless `StoreHash` is true). 60 | * 61 | * `GrowthPolicy` defines how the map grows and consequently how a hash value is mapped to a bucket. 62 | * By default the map uses `tsl::rh::power_of_two_growth_policy`. This policy keeps the number of buckets 63 | * to a power of two and uses a mask to map the hash to a bucket instead of the slow modulo. 64 | * Other growth policies are available and you may define your own growth policy, 65 | * check `tsl::rh::power_of_two_growth_policy` for the interface. 66 | * 67 | * `std::pair` must be swappable. 68 | * 69 | * `Key` and `T` must be copy and/or move constructible. 70 | * 71 | * If the destructor of `Key` or `T` throws an exception, the behaviour of the class is undefined. 72 | * 73 | * Iterators invalidation: 74 | * - clear, operator=, reserve, rehash: always invalidate the iterators. 75 | * - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators. 76 | * - erase: always invalidate the iterators. 77 | */ 78 | template, 81 | class KeyEqual = std::equal_to, 82 | class Allocator = std::allocator>, 83 | bool StoreHash = false, 84 | class GrowthPolicy = tsl::rh::power_of_two_growth_policy<2>> 85 | class robin_map { 86 | private: 87 | template 88 | using has_is_transparent = tsl::detail_robin_hash::has_is_transparent; 89 | 90 | class KeySelect { 91 | public: 92 | using key_type = Key; 93 | 94 | const key_type& operator()(const std::pair& key_value) const noexcept { 95 | return key_value.first; 96 | } 97 | 98 | key_type& operator()(std::pair& key_value) noexcept { 99 | return key_value.first; 100 | } 101 | }; 102 | 103 | class ValueSelect { 104 | public: 105 | using value_type = T; 106 | 107 | const value_type& operator()(const std::pair& key_value) const noexcept { 108 | return key_value.second; 109 | } 110 | 111 | value_type& operator()(std::pair& key_value) noexcept { 112 | return key_value.second; 113 | } 114 | }; 115 | 116 | using ht = detail_robin_hash::robin_hash, KeySelect, ValueSelect, 117 | Hash, KeyEqual, Allocator, StoreHash, GrowthPolicy>; 118 | 119 | public: 120 | using key_type = typename ht::key_type; 121 | using mapped_type = T; 122 | using value_type = typename ht::value_type; 123 | using size_type = typename ht::size_type; 124 | using difference_type = typename ht::difference_type; 125 | using hasher = typename ht::hasher; 126 | using key_equal = typename ht::key_equal; 127 | using allocator_type = typename ht::allocator_type; 128 | using reference = typename ht::reference; 129 | using const_reference = typename ht::const_reference; 130 | using pointer = typename ht::pointer; 131 | using const_pointer = typename ht::const_pointer; 132 | using iterator = typename ht::iterator; 133 | using const_iterator = typename ht::const_iterator; 134 | 135 | 136 | public: 137 | /* 138 | * Constructors 139 | */ 140 | robin_map(): robin_map(ht::DEFAULT_INIT_BUCKETS_SIZE) { 141 | } 142 | 143 | explicit robin_map(size_type bucket_count, 144 | const Hash& hash = Hash(), 145 | const KeyEqual& equal = KeyEqual(), 146 | const Allocator& alloc = Allocator()): 147 | m_ht(bucket_count, hash, equal, alloc) 148 | { 149 | } 150 | 151 | robin_map(size_type bucket_count, 152 | const Allocator& alloc): robin_map(bucket_count, Hash(), KeyEqual(), alloc) 153 | { 154 | } 155 | 156 | robin_map(size_type bucket_count, 157 | const Hash& hash, 158 | const Allocator& alloc): robin_map(bucket_count, hash, KeyEqual(), alloc) 159 | { 160 | } 161 | 162 | explicit robin_map(const Allocator& alloc): robin_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { 163 | } 164 | 165 | template 166 | robin_map(InputIt first, InputIt last, 167 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, 168 | const Hash& hash = Hash(), 169 | const KeyEqual& equal = KeyEqual(), 170 | const Allocator& alloc = Allocator()): robin_map(bucket_count, hash, equal, alloc) 171 | { 172 | insert(first, last); 173 | } 174 | 175 | template 176 | robin_map(InputIt first, InputIt last, 177 | size_type bucket_count, 178 | const Allocator& alloc): robin_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) 179 | { 180 | } 181 | 182 | template 183 | robin_map(InputIt first, InputIt last, 184 | size_type bucket_count, 185 | const Hash& hash, 186 | const Allocator& alloc): robin_map(first, last, bucket_count, hash, KeyEqual(), alloc) 187 | { 188 | } 189 | 190 | robin_map(std::initializer_list init, 191 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, 192 | const Hash& hash = Hash(), 193 | const KeyEqual& equal = KeyEqual(), 194 | const Allocator& alloc = Allocator()): 195 | robin_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) 196 | { 197 | } 198 | 199 | robin_map(std::initializer_list init, 200 | size_type bucket_count, 201 | const Allocator& alloc): 202 | robin_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) 203 | { 204 | } 205 | 206 | robin_map(std::initializer_list init, 207 | size_type bucket_count, 208 | const Hash& hash, 209 | const Allocator& alloc): 210 | robin_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) 211 | { 212 | } 213 | 214 | robin_map& operator=(std::initializer_list ilist) { 215 | m_ht.clear(); 216 | 217 | m_ht.reserve(ilist.size()); 218 | m_ht.insert(ilist.begin(), ilist.end()); 219 | 220 | return *this; 221 | } 222 | 223 | allocator_type get_allocator() const { return m_ht.get_allocator(); } 224 | 225 | 226 | /* 227 | * Iterators 228 | */ 229 | iterator begin() noexcept { return m_ht.begin(); } 230 | const_iterator begin() const noexcept { return m_ht.begin(); } 231 | const_iterator cbegin() const noexcept { return m_ht.cbegin(); } 232 | 233 | iterator end() noexcept { return m_ht.end(); } 234 | const_iterator end() const noexcept { return m_ht.end(); } 235 | const_iterator cend() const noexcept { return m_ht.cend(); } 236 | 237 | 238 | /* 239 | * Capacity 240 | */ 241 | bool empty() const noexcept { return m_ht.empty(); } 242 | size_type size() const noexcept { return m_ht.size(); } 243 | size_type max_size() const noexcept { return m_ht.max_size(); } 244 | 245 | /* 246 | * Modifiers 247 | */ 248 | void clear() noexcept { m_ht.clear(); } 249 | 250 | 251 | 252 | std::pair insert(const value_type& value) { 253 | return m_ht.insert(value); 254 | } 255 | 256 | template::value>::type* = nullptr> 257 | std::pair insert(P&& value) { 258 | return m_ht.emplace(std::forward

(value)); 259 | } 260 | 261 | std::pair insert(value_type&& value) { 262 | return m_ht.insert(std::move(value)); 263 | } 264 | 265 | 266 | iterator insert(const_iterator hint, const value_type& value) { 267 | return m_ht.insert_hint(hint, value); 268 | } 269 | 270 | template::value>::type* = nullptr> 271 | iterator insert(const_iterator hint, P&& value) { 272 | return m_ht.emplace_hint(hint, std::forward

(value)); 273 | } 274 | 275 | iterator insert(const_iterator hint, value_type&& value) { 276 | return m_ht.insert_hint(hint, std::move(value)); 277 | } 278 | 279 | 280 | template 281 | void insert(InputIt first, InputIt last) { 282 | m_ht.insert(first, last); 283 | } 284 | 285 | void insert(std::initializer_list ilist) { 286 | m_ht.insert(ilist.begin(), ilist.end()); 287 | } 288 | 289 | 290 | 291 | 292 | template 293 | std::pair insert_or_assign(const key_type& k, M&& obj) { 294 | return m_ht.insert_or_assign(k, std::forward(obj)); 295 | } 296 | 297 | template 298 | std::pair insert_or_assign(key_type&& k, M&& obj) { 299 | return m_ht.insert_or_assign(std::move(k), std::forward(obj)); 300 | } 301 | 302 | template 303 | iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { 304 | return m_ht.insert_or_assign(hint, k, std::forward(obj)); 305 | } 306 | 307 | template 308 | iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { 309 | return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); 310 | } 311 | 312 | 313 | 314 | /** 315 | * Due to the way elements are stored, emplace will need to move or copy the key-value once. 316 | * The method is equivalent to insert(value_type(std::forward(args)...)); 317 | * 318 | * Mainly here for compatibility with the std::unordered_map interface. 319 | */ 320 | template 321 | std::pair emplace(Args&&... args) { 322 | return m_ht.emplace(std::forward(args)...); 323 | } 324 | 325 | 326 | 327 | /** 328 | * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. 329 | * The method is equivalent to insert(hint, value_type(std::forward(args)...)); 330 | * 331 | * Mainly here for compatibility with the std::unordered_map interface. 332 | */ 333 | template 334 | iterator emplace_hint(const_iterator hint, Args&&... args) { 335 | return m_ht.emplace_hint(hint, std::forward(args)...); 336 | } 337 | 338 | 339 | 340 | 341 | template 342 | std::pair try_emplace(const key_type& k, Args&&... args) { 343 | return m_ht.try_emplace(k, std::forward(args)...); 344 | } 345 | 346 | template 347 | std::pair try_emplace(key_type&& k, Args&&... args) { 348 | return m_ht.try_emplace(std::move(k), std::forward(args)...); 349 | } 350 | 351 | template 352 | iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { 353 | return m_ht.try_emplace_hint(hint, k, std::forward(args)...); 354 | } 355 | 356 | template 357 | iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { 358 | return m_ht.try_emplace_hint(hint, std::move(k), std::forward(args)...); 359 | } 360 | 361 | 362 | 363 | 364 | iterator erase(iterator pos) { return m_ht.erase(pos); } 365 | iterator erase(const_iterator pos) { return m_ht.erase(pos); } 366 | iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } 367 | size_type erase(const key_type& key) { return m_ht.erase(key); } 368 | 369 | /** 370 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 371 | * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. 372 | */ 373 | size_type erase(const key_type& key, std::size_t precalculated_hash) { 374 | return m_ht.erase(key, precalculated_hash); 375 | } 376 | 377 | /** 378 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 379 | * If so, K must be hashable and comparable to Key. 380 | */ 381 | template::value>::type* = nullptr> 382 | size_type erase(const K& key) { return m_ht.erase(key); } 383 | 384 | /** 385 | * @copydoc erase(const K& key) 386 | * 387 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 388 | * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. 389 | */ 390 | template::value>::type* = nullptr> 391 | size_type erase(const K& key, std::size_t precalculated_hash) { 392 | return m_ht.erase(key, precalculated_hash); 393 | } 394 | 395 | 396 | 397 | void swap(robin_map& other) { other.m_ht.swap(m_ht); } 398 | 399 | 400 | 401 | /* 402 | * Lookup 403 | */ 404 | T& at(const Key& key) { return m_ht.at(key); } 405 | 406 | /** 407 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 408 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 409 | */ 410 | T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } 411 | 412 | 413 | const T& at(const Key& key) const { return m_ht.at(key); } 414 | 415 | /** 416 | * @copydoc at(const Key& key, std::size_t precalculated_hash) 417 | */ 418 | const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } 419 | 420 | 421 | /** 422 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 423 | * If so, K must be hashable and comparable to Key. 424 | */ 425 | template::value>::type* = nullptr> 426 | T& at(const K& key) { return m_ht.at(key); } 427 | 428 | /** 429 | * @copydoc at(const K& key) 430 | * 431 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 432 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 433 | */ 434 | template::value>::type* = nullptr> 435 | T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } 436 | 437 | 438 | /** 439 | * @copydoc at(const K& key) 440 | */ 441 | template::value>::type* = nullptr> 442 | const T& at(const K& key) const { return m_ht.at(key); } 443 | 444 | /** 445 | * @copydoc at(const K& key, std::size_t precalculated_hash) 446 | */ 447 | template::value>::type* = nullptr> 448 | const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } 449 | 450 | 451 | 452 | 453 | T& operator[](const Key& key) { return m_ht[key]; } 454 | T& operator[](Key&& key) { return m_ht[std::move(key)]; } 455 | 456 | 457 | 458 | 459 | size_type count(const Key& key) const { return m_ht.count(key); } 460 | 461 | /** 462 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 463 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 464 | */ 465 | size_type count(const Key& key, std::size_t precalculated_hash) const { 466 | return m_ht.count(key, precalculated_hash); 467 | } 468 | 469 | /** 470 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 471 | * If so, K must be hashable and comparable to Key. 472 | */ 473 | template::value>::type* = nullptr> 474 | size_type count(const K& key) const { return m_ht.count(key); } 475 | 476 | /** 477 | * @copydoc count(const K& key) const 478 | * 479 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 480 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 481 | */ 482 | template::value>::type* = nullptr> 483 | size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } 484 | 485 | 486 | 487 | 488 | iterator find(const Key& key) { return m_ht.find(key); } 489 | 490 | /** 491 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 492 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 493 | */ 494 | iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } 495 | 496 | const_iterator find(const Key& key) const { return m_ht.find(key); } 497 | 498 | /** 499 | * @copydoc find(const Key& key, std::size_t precalculated_hash) 500 | */ 501 | const_iterator find(const Key& key, std::size_t precalculated_hash) const { 502 | return m_ht.find(key, precalculated_hash); 503 | } 504 | 505 | /** 506 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 507 | * If so, K must be hashable and comparable to Key. 508 | */ 509 | template::value>::type* = nullptr> 510 | iterator find(const K& key) { return m_ht.find(key); } 511 | 512 | /** 513 | * @copydoc find(const K& key) 514 | * 515 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 516 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 517 | */ 518 | template::value>::type* = nullptr> 519 | iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } 520 | 521 | /** 522 | * @copydoc find(const K& key) 523 | */ 524 | template::value>::type* = nullptr> 525 | const_iterator find(const K& key) const { return m_ht.find(key); } 526 | 527 | /** 528 | * @copydoc find(const K& key) 529 | * 530 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 531 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 532 | */ 533 | template::value>::type* = nullptr> 534 | const_iterator find(const K& key, std::size_t precalculated_hash) const { 535 | return m_ht.find(key, precalculated_hash); 536 | } 537 | 538 | 539 | 540 | 541 | std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } 542 | 543 | /** 544 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 545 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 546 | */ 547 | std::pair equal_range(const Key& key, std::size_t precalculated_hash) { 548 | return m_ht.equal_range(key, precalculated_hash); 549 | } 550 | 551 | std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } 552 | 553 | /** 554 | * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) 555 | */ 556 | std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { 557 | return m_ht.equal_range(key, precalculated_hash); 558 | } 559 | 560 | /** 561 | * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. 562 | * If so, K must be hashable and comparable to Key. 563 | */ 564 | template::value>::type* = nullptr> 565 | std::pair equal_range(const K& key) { return m_ht.equal_range(key); } 566 | 567 | 568 | /** 569 | * @copydoc equal_range(const K& key) 570 | * 571 | * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same 572 | * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. 573 | */ 574 | template::value>::type* = nullptr> 575 | std::pair equal_range(const K& key, std::size_t precalculated_hash) { 576 | return m_ht.equal_range(key, precalculated_hash); 577 | } 578 | 579 | /** 580 | * @copydoc equal_range(const K& key) 581 | */ 582 | template::value>::type* = nullptr> 583 | std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } 584 | 585 | /** 586 | * @copydoc equal_range(const K& key, std::size_t precalculated_hash) 587 | */ 588 | template::value>::type* = nullptr> 589 | std::pair equal_range(const K& key, std::size_t precalculated_hash) const { 590 | return m_ht.equal_range(key, precalculated_hash); 591 | } 592 | 593 | 594 | 595 | 596 | /* 597 | * Bucket interface 598 | */ 599 | size_type bucket_count() const { return m_ht.bucket_count(); } 600 | size_type max_bucket_count() const { return m_ht.max_bucket_count(); } 601 | 602 | 603 | /* 604 | * Hash policy 605 | */ 606 | float load_factor() const { return m_ht.load_factor(); } 607 | 608 | float min_load_factor() const { return m_ht.min_load_factor(); } 609 | float max_load_factor() const { return m_ht.max_load_factor(); } 610 | 611 | /** 612 | * Set the `min_load_factor` to `ml`. When the `load_factor` of the map goes 613 | * below `min_load_factor` after some erase operations, the map will be 614 | * shrunk when an insertion occurs. The erase method itself never shrinks 615 | * the map. 616 | * 617 | * The default value of `min_load_factor` is 0.0f, the map never shrinks by default. 618 | */ 619 | void min_load_factor(float ml) { m_ht.min_load_factor(ml); } 620 | void max_load_factor(float ml) { m_ht.max_load_factor(ml); } 621 | 622 | void rehash(size_type count) { m_ht.rehash(count); } 623 | void reserve(size_type count) { m_ht.reserve(count); } 624 | 625 | 626 | /* 627 | * Observers 628 | */ 629 | hasher hash_function() const { return m_ht.hash_function(); } 630 | key_equal key_eq() const { return m_ht.key_eq(); } 631 | 632 | /* 633 | * Other 634 | */ 635 | 636 | /** 637 | * Convert a const_iterator to an iterator. 638 | */ 639 | iterator mutable_iterator(const_iterator pos) { 640 | return m_ht.mutable_iterator(pos); 641 | } 642 | 643 | friend bool operator==(const robin_map& lhs, const robin_map& rhs) { 644 | if(lhs.size() != rhs.size()) { 645 | return false; 646 | } 647 | 648 | for(const auto& element_lhs: lhs) { 649 | const auto it_element_rhs = rhs.find(element_lhs.first); 650 | if(it_element_rhs == rhs.cend() || element_lhs.second != it_element_rhs->second) { 651 | return false; 652 | } 653 | } 654 | 655 | return true; 656 | } 657 | 658 | friend bool operator!=(const robin_map& lhs, const robin_map& rhs) { 659 | return !operator==(lhs, rhs); 660 | } 661 | 662 | friend void swap(robin_map& lhs, robin_map& rhs) { 663 | lhs.swap(rhs); 664 | } 665 | 666 | private: 667 | ht m_ht; 668 | }; 669 | 670 | 671 | /** 672 | * Same as `tsl::robin_map`. 673 | */ 674 | template, 677 | class KeyEqual = std::equal_to, 678 | class Allocator = std::allocator>, 679 | bool StoreHash = false> 680 | using robin_pg_map = robin_map; 681 | 682 | } // end namespace tsl 683 | 684 | #endif 685 | --------------------------------------------------------------------------------