├── src ├── config.cpp ├── main.cpp ├── utils.cpp ├── configReader.cpp ├── configReader.h ├── constString.h ├── singleton.h ├── utils.h ├── error.h ├── Makefile ├── configManager.h ├── fuzzyMatch.h ├── queue.h ├── fuzzyEngine.h ├── config.h ├── threadPool.h ├── argParser.h ├── consts.h ├── app.h ├── fuzzyEngine.cpp ├── tty.h ├── color.h ├── tui.h ├── argParser.cpp ├── ringBuffer.h ├── tui.cpp └── tty.cpp ├── Makefile ├── .gitignore ├── test ├── Makefile ├── ttyTest.cpp └── ringBufferTest.cpp ├── rules.mk ├── README.md └── LICENSE /src/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | 4 | namespace leaf 5 | { 6 | 7 | } // end namespace leaf 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOALS = $(if $(MAKECMDGOALS), $(MAKECMDGOALS), all) 2 | SRC_DIR = ./src 3 | 4 | .PHONY: $(GOALS) 5 | 6 | $(GOALS): 7 | @$(MAKE) -k $@ -C $(SRC_DIR) || exit 1 8 | 9 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "app.h" 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | leaf::Application app(argc, argv); 6 | app.start(); 7 | 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | namespace utils 4 | { 5 | 6 | bool is_utf8_boundary(uint8_t c) { 7 | return c < 128 || (c & (1 << 6)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/configReader.cpp: -------------------------------------------------------------------------------- 1 | #include "configReader.h" 2 | 3 | 4 | namespace leaf 5 | { 6 | 7 | void ConfigReader::readConfig(std::vector>& cfg) { 8 | 9 | } 10 | 11 | } // end namespace leaf 12 | -------------------------------------------------------------------------------- /src/configReader.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include "config.h" 6 | 7 | namespace leaf 8 | { 9 | 10 | class ConfigReader 11 | { 12 | public: 13 | void readConfig(std::vector>& cfg); 14 | 15 | private: 16 | 17 | }; 18 | 19 | 20 | } // end namespace leaf 21 | -------------------------------------------------------------------------------- /src/constString.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | 5 | namespace leaf 6 | { 7 | 8 | // POD 9 | struct ConstString 10 | { 11 | const char* str; 12 | uint32_t len; 13 | }; 14 | 15 | static inline ConstString makeConstString(const char* str, uint32_t len) { 16 | ConstString const_str; 17 | const_str.str = str; 18 | const_str.len = len; 19 | return const_str; 20 | } 21 | 22 | } // end namespace leaf 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | .*.sw? 35 | build/ 36 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | TOP_DIR = ./.. 2 | BUILD_DIR = $(TOP_DIR)/build 3 | INCLUDE_DIR = $(TOP_DIR)/src 4 | 5 | LDFLAGS = 6 | LDLIBS = 7 | 8 | VPATH = $(BUILD_DIR) $(INCLUDE_DIR) 9 | 10 | include $(TOP_DIR)/rules.mk 11 | 12 | .PHONY: clean 13 | 14 | test: build ringBufferTest ttyTest 15 | 16 | build: 17 | @mkdir -p $(BUILD_DIR) 18 | 19 | ringBufferTest: ringBufferTest.o 20 | -cd $(BUILD_DIR) && \ 21 | $(CXX) $(CXXFLAGS) $(^F) -o $@ 22 | 23 | ttyTest: ttyTest.o tty.o 24 | -cd $(BUILD_DIR) && \ 25 | $(CXX) $(CXXFLAGS) $(^F) -lpthread -o $@ 26 | 27 | clean: 28 | - rm $(BUILD_DIR)/*Test 29 | -------------------------------------------------------------------------------- /src/singleton.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | 5 | namespace leaf 6 | { 7 | 8 | template 9 | class Singleton 10 | { 11 | public: 12 | 13 | Singleton(const Singleton&) = delete; 14 | Singleton& operator=(const Singleton&) = delete; 15 | Singleton(Singleton&&) = delete; 16 | Singleton& operator=(Singleton&&) = delete; 17 | 18 | template 19 | static T& getInstance(Args&&... args) { 20 | static T t(std::forward(args)...); 21 | return t; 22 | } 23 | 24 | protected: 25 | Singleton() = default; 26 | 27 | }; 28 | 29 | } // end namespace leaf 30 | -------------------------------------------------------------------------------- /rules.mk: -------------------------------------------------------------------------------- 1 | .SUFFIXES: 2 | .SUFFIXES: .cpp .c .cc .o 3 | 4 | SHELL = /bin/sh 5 | 6 | CPP_VERSION = -std=c++14 7 | 8 | CXX = g++ $(CPP_VERSION) 9 | CXXFLAGS = -Wall -I$(INCLUDE_DIR) 10 | LDFLAGS = 11 | LDLIBS = -lpthread 12 | 13 | SOURCE = $(wildcard *.c) $(wildcard *.cc) $(wildcard *.cpp) 14 | OBJS = $(addsuffix .o,$(basename $(SOURCE))) 15 | DEPS = $(patsubst %.o,%.d,$(OBJS)) 16 | 17 | %.o: %.cpp 18 | $(CXX) $(CXXFLAGS) -c $< -o $(BUILD_DIR)/$@ 19 | 20 | %.o: %.cc 21 | $(CXX) $(CXXFLAGS) -c $< -o $(BUILD_DIR)/$@ 22 | 23 | %.d: %.cpp 24 | @$(CXX) -I$(INCLUDE_DIR) -MM $< -MF $(BUILD_DIR)/$@ 25 | 26 | %.d: %.cc 27 | @$(CXX) -I$(INCLUDE_DIR) -MM $< -MF $(BUILD_DIR)/$@ 28 | 29 | %.d: %.c 30 | @$(CXX) -I$(INCLUDE_DIR) -MM $< -MF $(BUILD_DIR)/$@ 31 | 32 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace utils 8 | { 9 | template 10 | std::string strFormat(const char* format, Args... args) 11 | { 12 | char buffer[N]; 13 | snprintf(buffer, sizeof(buffer), format, args...); 14 | return std::string(buffer); 15 | } 16 | 17 | template 18 | // return true if str1 ends with str2 19 | bool endswith(T1&& str1, T2&& str2) { 20 | static_assert(std::is_same, std::string>::value 21 | && std::is_same, std::string>::value, "type must be std::string!"); 22 | return str1.length() >= str2.length() 23 | && str1.compare(str1.length() - str2.length(), str2.length(), str2) == 0; 24 | } 25 | 26 | bool is_utf8_boundary(uint8_t c); 27 | } 28 | -------------------------------------------------------------------------------- /src/error.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "singleton.h" 10 | #include "utils.h" 11 | 12 | 13 | namespace leaf 14 | { 15 | 16 | #define ErrorMessage \ 17 | utils::strFormat("%s:%d:%s", __FILE__, __LINE__, strerror(errno)) 18 | 19 | class Error final : public Singleton 20 | { 21 | friend Singleton; 22 | public: 23 | template , std::string>::value> 25 | > 26 | void appendError(T&& err) { 27 | std::lock_guard lock(mutex_); 28 | err_msgs_.emplace_back(std::forward(err)); 29 | } 30 | 31 | ~Error() { 32 | for ( const auto& err : err_msgs_ ) { 33 | fprintf(stderr, "%s\r\n", err.c_str()); 34 | } 35 | } 36 | private: 37 | std::mutex mutex_; 38 | std::vector err_msgs_; 39 | }; 40 | 41 | } // end namespace leaf 42 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | TOP_DIR = ./.. 2 | BUILD_DIR = $(TOP_DIR)/build 3 | INCLUDE_DIR = . 4 | TEST_DIR = $(TOP_DIR)/test 5 | 6 | LDFLAGS = 7 | LDLIBS = -lpthread 8 | 9 | VPATH = $(BUILD_DIR) $(TOP_DIR) 10 | 11 | include $(TOP_DIR)/rules.mk 12 | 13 | TARGET = yy 14 | ALIAS = leaf 15 | 16 | .PHONY: $(if $(MAKECMDGOALS), $(MAKECMDGOALS), all) 17 | 18 | all: CXXFLAGS += -O3 19 | all: goal 20 | 21 | debug: CXXFLAGS += -g 22 | debug: LDFLAGS += -rdynamic -fno-omit-frame-pointer -fasynchronous-unwind-tables 23 | debug: goal 24 | 25 | asan: CXXFLAGS += -g 26 | asan: LDFLAGS += -fsanitize=address -fno-omit-frame-pointer -fasynchronous-unwind-tables 27 | asan: goal 28 | 29 | goal: build $(DEPS) $(TARGET) $(ALIAS) 30 | 31 | build: 32 | @mkdir -p $(BUILD_DIR) 33 | 34 | $(TARGET): $(OBJS) 35 | -cd $(BUILD_DIR) && \ 36 | $(CXX) $(^F) $(LDFLAGS) $(LDLIBS) -o $@ 37 | 38 | $(ALIAS): $(TARGET) 39 | -cd $(BUILD_DIR) && \ 40 | ln -sf $( 4 | #include 5 | #include "singleton.h" 6 | #include "configReader.h" 7 | #include "argParser.h" 8 | 9 | namespace leaf 10 | { 11 | 12 | class ConfigManager : public Singleton, 13 | private ConfigReader, private ArgumentParser 14 | { 15 | friend Singleton; 16 | public: 17 | 18 | template 19 | decltype(auto) getConfigValue() const { 20 | auto idx = static_cast(T); 21 | return static_cast::type>*>(cfg_[idx].get())->getValue(); 22 | } 23 | 24 | void loadConfig(int argc, char* argv[]) { 25 | readConfig(cfg_); 26 | parseArgs(argc, argv, cfg_); 27 | } 28 | 29 | 30 | void setBorderCharWidth(uint32_t width) { 31 | border_char_width_ = width; 32 | } 33 | 34 | uint32_t getBorderCharWidth() const noexcept { 35 | return border_char_width_; 36 | } 37 | 38 | private: 39 | 40 | ConfigManager() { 41 | cfg_.resize(static_cast(ConfigType::MaxConfigNum)); 42 | SetConfigValue(cfg_, Reverse, false); 43 | SetConfigValue(cfg_, Height, 0); 44 | SetConfigValue(cfg_, Indentation, 2); 45 | SetConfigValue(cfg_, SortPreference, Preference::End); 46 | SetConfigValue(cfg_, Border, ""); 47 | SetConfigValue(cfg_, BorderChars, 48 | std::vector({"─","│","─","│","╭","╮","╯","╰"})); 49 | SetConfigValue(cfg_, Margin, std::vector({0, 0, 0, 0})); 50 | } 51 | 52 | std::vector> cfg_; 53 | uint32_t border_char_width_{ 0 }; 54 | 55 | }; 56 | 57 | } // end namespace leaf 58 | -------------------------------------------------------------------------------- /src/fuzzyMatch.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include "config.h" 6 | 7 | namespace leaf 8 | { 9 | 10 | #define MIN_WEIGHT (-2147483648) 11 | 12 | struct PatternContext 13 | { 14 | const uint8_t* pattern; 15 | int64_t pattern_mask[256]; 16 | uint16_t pattern_len; 17 | uint16_t actual_pattern_len; 18 | bool is_lower; 19 | }; 20 | 21 | struct HighlightPos 22 | { 23 | uint16_t col; 24 | uint16_t len; 25 | }; 26 | 27 | struct HighlightContext 28 | { 29 | int32_t score; 30 | uint16_t beg; 31 | uint16_t end; 32 | HighlightPos positions[64]; 33 | uint16_t end_index; 34 | }; 35 | 36 | 37 | struct Destroyer 38 | { 39 | template 40 | void operator()(T* p) { 41 | free(p); 42 | } 43 | }; 44 | 45 | template 46 | using Unique_ptr = std::unique_ptr; 47 | 48 | class FuzzyMatch 49 | { 50 | public: 51 | 52 | PatternContext* initPattern(const char* pattern, 53 | uint16_t pattern_len); 54 | 55 | int32_t getWeight(const char* text, 56 | uint16_t text_len, 57 | PatternContext* p_pattern_ctxt, 58 | Preference preference); 59 | 60 | Unique_ptr getHighlights(const char* text, 61 | uint16_t text_len, 62 | PatternContext* p_pattern_ctxt); 63 | 64 | uint32_t getPathWeight(const char* filename, 65 | const char* suffix, 66 | const char* dirname, 67 | const char* path, uint32_t path_len); 68 | 69 | }; 70 | 71 | } // end namespace leaf 72 | -------------------------------------------------------------------------------- /src/queue.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace leaf 8 | { 9 | 10 | template 11 | class BlockingQueue 12 | { 13 | public: 14 | BlockingQueue(const BlockingQueue&) = delete; 15 | BlockingQueue& operator=(const BlockingQueue&) = delete; 16 | BlockingQueue(BlockingQueue&&) = delete; 17 | BlockingQueue& operator=(BlockingQueue&&) = delete; 18 | 19 | BlockingQueue() = default; 20 | 21 | void put(const T& data) { 22 | std::lock_guard lock(mutex_); 23 | queue_.push_back(data); 24 | not_empty_.notify_one(); 25 | } 26 | 27 | void put(T&& data) { 28 | std::lock_guard lock(mutex_); 29 | queue_.push_back(std::move(data)); 30 | not_empty_.notify_one(); 31 | } 32 | 33 | T take() { 34 | std::unique_lock lock(mutex_); 35 | while ( queue_.empty() && running_ ) { 36 | not_empty_.wait(lock); 37 | } 38 | 39 | // queue_ is empty only when notifyAll() is called 40 | if ( queue_.empty() ) { 41 | return T(); 42 | } 43 | 44 | T front(std::move(queue_.front())); 45 | queue_.pop_front(); 46 | return front; 47 | } 48 | 49 | // take care to call this function 50 | void notifyAll() { 51 | std::lock_guard lock(mutex_); 52 | running_ = false; 53 | not_empty_.notify_all(); 54 | } 55 | 56 | void initState() { 57 | std::lock_guard lock(mutex_); 58 | running_ = true; 59 | } 60 | 61 | size_t size() const { 62 | std::lock_guard lock(mutex_); 63 | return queue_.size(); 64 | } 65 | 66 | private: 67 | bool running_{ true }; 68 | mutable std::mutex mutex_; 69 | std::condition_variable not_empty_; 70 | std::deque queue_; 71 | }; 72 | 73 | } // end namespace leaf 74 | -------------------------------------------------------------------------------- /src/fuzzyEngine.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "constString.h" 9 | #include "fuzzyMatch.h" 10 | #include "threadPool.h" 11 | #include "ringBuffer.h" 12 | 13 | namespace leaf 14 | { 15 | 16 | using weight_t = int32_t; 17 | using StrType = ConstString; 18 | using StrContainer = RingBuffer; 19 | using WeightContainer = RingBuffer; 20 | using Result = std::tuple; 21 | using DigestFn = std::function; 22 | 23 | struct MatchResult 24 | { 25 | weight_t weight; 26 | uint32_t index; 27 | }; 28 | 29 | 30 | class FuzzyEngine : private FuzzyMatch 31 | { 32 | public: 33 | explicit FuzzyEngine(uint32_t cpus): cpu_count_(cpus) {} 34 | 35 | Result fuzzyMatch(const StrContainer::const_iterator& source_begin, 36 | uint32_t source_size, 37 | const std::string& pattern, 38 | Preference preference=Preference::Begin, 39 | DigestFn get_digest=DigestFn(), 40 | bool sort_results=true); 41 | 42 | Result merge(const Result& a, const Result& b); 43 | 44 | std::vector> 45 | getHighlights(const StrContainer::const_iterator& source_begin, 46 | uint32_t source_size, 47 | const std::string& pattern, 48 | DigestFn get_digest=DigestFn()); 49 | private: 50 | void _merge(MatchResult* results, 51 | MatchResult* buffer, 52 | uint32_t offset_1, 53 | uint32_t length_1, 54 | uint32_t length_2); 55 | private: 56 | using PatternContextPtr = std::unique_ptr; 57 | uint32_t cpu_count_; 58 | ThreadPool thread_pool_; 59 | std::string pattern_; 60 | PatternContextPtr pattern_ctxt_; 61 | 62 | }; 63 | 64 | 65 | } // end namespace leaf 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yoyo-leaf 2 | Yoyo-leaf is an awesome command-line fuzzy finder. 3 | 4 | ## Usage 5 | 6 | ``` 7 | usage: yy [options] 8 | 9 | Layout 10 | --border [BORDER[:STYLE]] Display the T(top), R(right), B(bottom), L(left) border. BORDER 11 | is a string combined by 'T', 'R', 'B' or 'L'. If BORDER is not 12 | specified, display borders all around. STYLE is 1, 2, 3 and 4 13 | which denotes "[─,│,─,│,╭,╮,╯,╰]", 14 | "[─,│,─,│,┌,┐,┘,└]", 15 | "[━,┃,━,┃,┏,┓,┛,┗]", 16 | "[═,║,═,║,╔,╗,╝,╚]" respectively. 17 | --border-chars= Specify the character to use for the top/right/bottom/left 18 | border, followed by the character to use for the 19 | topleft/topright/botright/botleft corner. Default value is 20 | "[─,│,─,│,╭,╮,╯,╰]" 21 | --height= Display window with the given height N[%] instead of using 22 | fullscreen. 23 | --margin= Specify the width of the top/right/bottom/left margin. MARGIN is 24 | a list of integers or percentages. The list can have 1, 2 and 4 25 | elements. For example, [10]: margins all around are 10. 26 | [10,20%]: top and bottom margin are 10, left and right margin 27 | are 20% of the terminal width. 28 | -r, --reverse Display from the bottom of the screen to top. 29 | 30 | Search 31 | --sort-preference= 32 | Specify the sort preference to apply, value can be [begin|end]. 33 | (default: end) 34 | 35 | alias: leaf 36 | ``` 37 | 38 | ## License 39 | This software is released under the Apache License, Version 2.0 (the "License"). 40 | 41 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace leaf 10 | { 11 | 12 | enum class ConfigType 13 | { 14 | Reverse, 15 | Height, 16 | Indentation, 17 | SortPreference, 18 | Border, 19 | BorderChars, 20 | Margin, 21 | 22 | MaxConfigNum 23 | }; 24 | 25 | enum class Preference { 26 | Begin, 27 | End, 28 | }; 29 | 30 | template 31 | struct ConfigValueType { 32 | using type = bool; 33 | }; 34 | 35 | #define DefineConfigValue(cfg_type, val_type) \ 36 | template <> \ 37 | struct ConfigValueType { \ 38 | using type = val_type; \ 39 | static_assert(std::is_same, type>::value, ""); \ 40 | }; 41 | 42 | DefineConfigValue(Height, uint32_t) 43 | DefineConfigValue(Indentation, uint32_t) 44 | DefineConfigValue(SortPreference, Preference) 45 | DefineConfigValue(Border, std::string) 46 | DefineConfigValue(BorderChars, std::vector) 47 | DefineConfigValue(Margin, std::vector) 48 | 49 | #define SetConfigValue(container, cfg_type, value) \ 50 | container[static_cast(ConfigType::cfg_type)].reset( \ 51 | new Config::type>(value)) 52 | 53 | class ConfigBase 54 | { 55 | public: 56 | virtual ~ConfigBase() = default; 57 | 58 | }; 59 | 60 | template 61 | class Config : public ConfigBase 62 | { 63 | static_assert(std::is_same, T>::value, ""); 64 | public: 65 | explicit Config(const T& val): value_(val) { 66 | 67 | } 68 | 69 | explicit Config(T&& val): value_(std::move(val)) { 70 | 71 | } 72 | 73 | std::conditional_t::value, T, T&> getValue() noexcept { 74 | return value_; 75 | } 76 | 77 | private: 78 | T value_; 79 | 80 | }; 81 | 82 | } // end namespace leaf 83 | -------------------------------------------------------------------------------- /src/threadPool.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "queue.h" 8 | 9 | namespace leaf 10 | { 11 | 12 | class ThreadPool 13 | { 14 | public: 15 | using Task = std::function; 16 | 17 | ThreadPool() = default; 18 | 19 | ~ThreadPool() { 20 | if ( running_ ) { 21 | stop(); 22 | } 23 | } 24 | 25 | void start(uint32_t num) { 26 | if ( running_ ) { 27 | return; 28 | } 29 | 30 | running_ = true; 31 | size_ = num; 32 | task_queue_.initState(); 33 | workers_.reserve(size_); 34 | for ( uint32_t i = 0; i < size_; ++i ) { 35 | workers_.emplace_back(&ThreadPool::_run, this); 36 | } 37 | } 38 | 39 | void stop() { 40 | if ( !running_ ) { 41 | return; 42 | } 43 | 44 | running_ = false; 45 | task_queue_.notifyAll(); 46 | 47 | for (auto& t : workers_) { 48 | t.join(); 49 | } 50 | 51 | workers_.clear(); 52 | } 53 | 54 | void enqueueTask(Task&& task) { 55 | { 56 | std::lock_guard lock(mutex_); 57 | unfinished_tasks_++; 58 | } 59 | task_queue_.put(std::move(task)); 60 | } 61 | 62 | void join() { 63 | std::unique_lock lock(mutex_); 64 | while ( unfinished_tasks_ > 0 ) { 65 | task_cond_.wait(lock); 66 | } 67 | } 68 | 69 | size_t size() const noexcept { 70 | return workers_.size(); 71 | } 72 | 73 | private: 74 | void _run() { 75 | while ( running_ ) { 76 | auto task = task_queue_.take(); 77 | if ( task ) { 78 | task(); 79 | } 80 | { 81 | std::lock_guard lock(mutex_); 82 | unfinished_tasks_--; 83 | if ( unfinished_tasks_ == 0 ) { 84 | task_cond_.notify_all(); 85 | } 86 | } 87 | } 88 | } 89 | 90 | private: 91 | uint32_t size_{ 0 }; 92 | std::atomic running_{ false }; 93 | std::vector workers_; 94 | BlockingQueue task_queue_; 95 | uint32_t unfinished_tasks_{ 0 }; 96 | std::mutex mutex_; 97 | std::condition_variable task_cond_; 98 | 99 | }; 100 | 101 | } // end namespace leaf 102 | -------------------------------------------------------------------------------- /src/argParser.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "error.h" 9 | #include "utils.h" 10 | #include "config.h" 11 | 12 | namespace leaf 13 | { 14 | 15 | enum class ArgCategory { 16 | Layout, 17 | Search, 18 | 19 | MaxNum 20 | }; 21 | 22 | struct Argument 23 | { 24 | ArgCategory category; 25 | std::string alias; 26 | ConfigType cfg_type; 27 | std::string nargs; 28 | std::string metavar; 29 | std::string help; 30 | }; 31 | 32 | class ArgumentParser 33 | { 34 | public: 35 | ArgumentParser(); 36 | void printHelp(); 37 | void parseArgs(int argc, char *argv[], std::vector>& cfg); 38 | private: 39 | template 40 | static void appendError(Args... args) { 41 | Error::getInstance().appendError(utils::strFormat<512>(std::forward(args)...)); 42 | } 43 | 44 | template 45 | static std::vector parseList(const std::string& key, const std::string& str, 46 | std::function convert); 47 | 48 | private: 49 | std::unordered_map alias_; 50 | std::unordered_map category_name_; 51 | std::unordered_map args_; 52 | 53 | }; 54 | 55 | /** 56 | * str is a string like "[1, 2, 3, 4]" 57 | */ 58 | template 59 | std::vector ArgumentParser::parseList(const std::string& key, const std::string& str, 60 | std::function convert) { 61 | auto left = str.find('['); 62 | auto right = str.find(']'); 63 | if ( left == std::string::npos || right == std::string::npos ) { 64 | appendError("invalid value: %s for %s", str.c_str(), key.c_str()); 65 | std::exit(EXIT_FAILURE); 66 | } 67 | 68 | std::vector res; 69 | res.reserve(4); 70 | for ( auto i = left + 1; i < right; ++i ) { 71 | while ( str[i] == ' ' ) { 72 | ++i; 73 | } 74 | 75 | auto start = i; 76 | while ( i < right && str[i] != ' ' && str[i] != ',' ) { 77 | ++i; 78 | } 79 | if ( i == start ) { 80 | appendError("invalid value: %s for %s", str.c_str(), key.c_str()); 81 | std::exit(EXIT_FAILURE); 82 | } 83 | res.emplace_back(convert(str.substr(start, i - start))); 84 | 85 | while ( str[i] == ' ' ) { 86 | ++i; 87 | } 88 | 89 | if ( i < right && str[i] != ',' ) { 90 | appendError("invalid value: %s for %s", str.c_str(), key.c_str()); 91 | std::exit(EXIT_FAILURE); 92 | } 93 | } 94 | 95 | return res; 96 | } 97 | 98 | } // end namespace leaf 99 | -------------------------------------------------------------------------------- /src/consts.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | namespace leaf 4 | { 5 | 6 | enum class Key 7 | { 8 | Ctrl_At = 0, // ctrl-@, ctrl-space 9 | Ctrl_A = 1, 10 | Ctrl_B = 2, 11 | Ctrl_C = 3, 12 | Ctrl_D = 4, 13 | Ctrl_E = 5, 14 | Ctrl_F = 6, 15 | Ctrl_G = 7, 16 | Ctrl_H = 8, 17 | Ctrl_I = 9, 18 | Ctrl_J = 10, 19 | Ctrl_K = 11, 20 | Ctrl_L = 12, 21 | Ctrl_M = 13, 22 | Ctrl_N = 14, 23 | Ctrl_O = 15, 24 | Ctrl_P = 16, 25 | Ctrl_Q = 17, 26 | Ctrl_R = 18, 27 | Ctrl_S = 19, 28 | Ctrl_T = 20, 29 | Ctrl_U = 21, 30 | Ctrl_V = 22, 31 | Ctrl_W = 23, 32 | Ctrl_X = 24, 33 | Ctrl_Y = 25, 34 | Ctrl_Z = 26, 35 | ESC = 27, // ctrl-[ 36 | FS = 28, // ctrl-backslash 37 | GS = 29, // ctrl-] 38 | RS = 30, // ctrl-^, ctrl-6 39 | US = 31, // ctrl-_, ctrl-/ 40 | Backspace = 127, 41 | 42 | Up, 43 | Down, 44 | Right, 45 | Left, 46 | Ctrl_Up, 47 | Ctrl_Down, 48 | Ctrl_Right, 49 | Ctrl_Left, 50 | Shift_Up, 51 | Shift_Down, 52 | Shift_Right, 53 | Shift_Left, 54 | Home, 55 | End, 56 | Insert, 57 | Delete, 58 | Page_Up, 59 | Page_Down, 60 | F1, 61 | F2, 62 | F3, 63 | F4, 64 | F5, 65 | F6, 66 | F7, 67 | F8, 68 | F9, 69 | F10, 70 | F11, 71 | F12, 72 | Shift_F1, 73 | Shift_F2, 74 | Shift_F3, 75 | Shift_F4, 76 | Shift_F5, 77 | Shift_F6, 78 | Shift_F7, 79 | Shift_F8, 80 | Shift_F9, 81 | Shift_F10, 82 | Shift_F11, 83 | Shift_F12, 84 | Ctrl_F1, 85 | Ctrl_F2, 86 | Ctrl_F3, 87 | Ctrl_F4, 88 | Ctrl_F5, 89 | Ctrl_F6, 90 | Ctrl_F7, 91 | Ctrl_F8, 92 | Ctrl_F9, 93 | Ctrl_F10, 94 | Ctrl_F11, 95 | Ctrl_F12, 96 | Shift_Tab, 97 | Single_Click, 98 | Double_Click, 99 | Right_Click, 100 | Middle_Click, 101 | Wheel_Down, 102 | Wheel_Up, 103 | Ctrl_LeftMouse, 104 | Shift_LeftMouse, 105 | 106 | Alt_a, 107 | Alt_b, 108 | Alt_c, 109 | Alt_d, 110 | Alt_e, 111 | Alt_f, 112 | Alt_g, 113 | Alt_h, 114 | Alt_i, 115 | Alt_j, 116 | Alt_k, 117 | Alt_l, 118 | Alt_m, 119 | Alt_n, 120 | Alt_o, 121 | Alt_p, 122 | Alt_q, 123 | Alt_r, 124 | Alt_s, 125 | Alt_t, 126 | Alt_u, 127 | Alt_v, 128 | Alt_w, 129 | Alt_x, 130 | Alt_y, 131 | Alt_z, 132 | 133 | Alt_A, 134 | Alt_B, 135 | Alt_C, 136 | Alt_D, 137 | Alt_E, 138 | Alt_F, 139 | Alt_G, 140 | Alt_H, 141 | Alt_I, 142 | Alt_J, 143 | Alt_K, 144 | Alt_L, 145 | Alt_M, 146 | Alt_N, 147 | Alt_O, 148 | Alt_P, 149 | Alt_Q, 150 | Alt_R, 151 | Alt_S, 152 | Alt_T, 153 | Alt_U, 154 | Alt_V, 155 | Alt_W, 156 | Alt_X, 157 | Alt_Y, 158 | Alt_Z, 159 | 160 | Char = 997, 161 | Resize = 998, 162 | Timeout = 999, 163 | 164 | Unknown = 1000, 165 | Exit = 1001 166 | }; 167 | 168 | enum class CursorDirection 169 | { 170 | Up, 171 | Down, 172 | Right, 173 | Left, 174 | NextLine, 175 | PrevLine, 176 | HorizontalAbsolute, 177 | }; 178 | 179 | enum class EraseMode 180 | { 181 | ToScreenEnd, 182 | ToScreenBegin, 183 | EntireScreen, 184 | EntireScreenAndScroll, 185 | 186 | ToLineEnd, 187 | ToLineBegin, 188 | EntireLine, 189 | }; 190 | 191 | } // end namespace leaf 192 | -------------------------------------------------------------------------------- /test/ttyTest.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_RINGBUFFER 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "tty.h" 9 | #include "color.h" 10 | 11 | using namespace leaf; 12 | using namespace std; 13 | 14 | auto& tty = Tty::getInstance(); 15 | 16 | std::vector source1 = { 17 | { "a" }, 18 | { "ab" }, 19 | { "a\r" }, 20 | { "a\r\x7f" }, 21 | { "a\rb" }, 22 | { "a\033" }, 23 | { "a\033b" }, 24 | { "\033" }, 25 | { "\033\033" }, 26 | { "\033\r" }, 27 | { "\r" }, 28 | { "\r\n" }, 29 | { "\r\n\n" }, 30 | { "简单\r" }, 31 | { "\033简单\r" }, 32 | { "\033a" }, 33 | { "\033[A" }, 34 | { "\033[A\033[B" }, 35 | { "\033[A\033[B\033" }, 36 | }; 37 | 38 | std::vector> source2 = { 39 | { '\0' }, 40 | { '\0', '\0' }, 41 | { '\0', '\0', '\0' }, 42 | { '\0', '\r' }, 43 | { 'a', '\0' }, 44 | { 'a', '\0', 'a' }, 45 | { 'q' }, 46 | }; 47 | 48 | const char* tmpfile_name = "/tmp/test_getchar"; 49 | 50 | void getchar_print() { 51 | while ( true ) { 52 | auto tu = tty.getchar(); 53 | auto key = std::get<0>(tu); 54 | if ( key == Key::Timeout ) { 55 | printf("timeout\r\n"); 56 | if ( std::get<1>(tu).empty() ) { 57 | break; 58 | } 59 | } 60 | else { 61 | printf("%-5d", int(key)); 62 | auto str = std::get<1>(tu); 63 | if ( !str.empty() ) { 64 | if (str[0] == '\033') 65 | printf("\\033%s", str.substr(1).c_str()); 66 | else { 67 | printf("%s", str.c_str()); 68 | } 69 | } 70 | printf("\r\n"); 71 | } 72 | } 73 | } 74 | 75 | void test_tty() { 76 | cout << "Makefile" << "\r\n"; 77 | cout << "src/utils.cpp" << "\r\n"; 78 | cout << "src/tty.h" << "\r\n"; 79 | cout << "src/consts.h" << "\r\n"; 80 | cout << "src/app.cpp" << "\r\n"; 81 | cout << "src/utils.h" << "\r\n"; 82 | cout << "src/tui.h" << "\r\n"; 83 | cout << "src/main.cpp" << "\r\n"; 84 | cout << "src/tty.cpp" << "\r\n"; 85 | cout << "src/Makefile" << "\r\n"; 86 | cout << "src/tui.cpp" << "\r\n"; 87 | cout << "src/color.h" << "\r\n"; 88 | cout << "README.md" << "\r\n"; 89 | cout << "LICENSE" << "\r\n"; 90 | 91 | while ( true ) { 92 | auto tu = tty.getchar(); 93 | auto ch = std::get<1>(tu); 94 | if ( ch == "q" ) { 95 | break; 96 | } 97 | switch ( ch[0] ) 98 | { 99 | case 'h': 100 | tty.moveCursor(CursorDirection::Left); 101 | break; 102 | case 'j': 103 | tty.moveCursor(CursorDirection::Down); 104 | break; 105 | case 'k': 106 | tty.moveCursor(CursorDirection::Up); 107 | break; 108 | case 'l': 109 | tty.moveCursor(CursorDirection::Right); 110 | break; 111 | case 'n': 112 | tty.moveCursor(CursorDirection::NextLine); 113 | break; 114 | case 'p': 115 | tty.moveCursor(CursorDirection::PrevLine); 116 | break; 117 | case 'u': 118 | tty.clear(EraseMode::ToScreenBegin); 119 | break; 120 | case 'd': 121 | tty.clear(EraseMode::ToScreenEnd); 122 | break; 123 | case 'D': 124 | tty.clear(EraseMode::EntireScreen); 125 | break; 126 | case '0': 127 | tty.clear(EraseMode::ToLineBegin); 128 | break; 129 | case '1': 130 | tty.clear(EraseMode::ToLineEnd); 131 | break; 132 | case 'L': 133 | tty.clear(EraseMode::EntireLine); 134 | break; 135 | case 'c': 136 | tty.hideCursor(); 137 | break; 138 | case 'C': 139 | tty.showCursor(); 140 | break; 141 | case '[': 142 | tty.setForegroundColor("137"); 143 | break; 144 | case ']': 145 | tty.setBackgroundColor("235"); 146 | break; 147 | case '{': 148 | tty.resetForegroundColor(); 149 | break; 150 | case '}': 151 | tty.resetBackgroundColor(); 152 | break; 153 | case 'a': 154 | tty.enableAlternativeBuffer(); 155 | break; 156 | case 'b': 157 | tty.disableAlternativeBuffer(); 158 | break; 159 | case 's': 160 | tty.scrollUp(); 161 | break; 162 | case 't': 163 | tty.scrollDown(); 164 | break; 165 | case '<': 166 | tty.saveCursorPosition(); 167 | break; 168 | case '>': 169 | tty.restoreCursorPosition(); 170 | break; 171 | case ';': 172 | uint32_t x, y; 173 | tty.getCursorPosition(x, y); 174 | printf("%u, %u\n", x, y); 175 | break; 176 | case '/': 177 | tty.saveCursorPosition(); 178 | tty.addString(3,1,"disableAlternativeBuffer\r\n", Color(Color256("121", "")).getColor()); 179 | tty.restoreCursorPosition(); 180 | break; 181 | case 'm': 182 | uint32_t r, c; 183 | tty.getWindowSize(r, c); 184 | printf("%u, %u\n", r, c); 185 | break; 186 | } 187 | 188 | tty.flush(); 189 | } 190 | } 191 | 192 | void test_getchar() { 193 | auto destroy = [](FILE* f) { fclose(f); }; 194 | std::unique_ptr file(fopen(tmpfile_name, "w"), destroy); 195 | if ( file == nullptr ) { 196 | cout << "fopen failed!" << endl; 197 | return; 198 | } 199 | 200 | if ( freopen(tmpfile_name, "r", stdin) == nullptr ) { 201 | cout << "freopen failed!" << endl; 202 | return; 203 | } 204 | 205 | auto f = file.get(); 206 | for ( auto& s: source1 ) { 207 | cout << "------------------------------------------------------\r\n"; 208 | fwrite(s.c_str(), s.length(), 1, f); 209 | fflush(f); 210 | getchar_print(); 211 | } 212 | 213 | for ( auto& v: source2 ) { 214 | cout << "------------------------------------------------------\r\n"; 215 | fwrite(v.data(), v.size(), 1, f); 216 | fflush(f); 217 | getchar_print(); 218 | } 219 | 220 | (void)freopen("/dev/tty", "r", stdin); 221 | 222 | } 223 | 224 | int main(int argc, const char *argv[]) 225 | { 226 | test_tty(); 227 | test_getchar(); 228 | 229 | return 0; 230 | } 231 | 232 | -------------------------------------------------------------------------------- /src/app.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "tui.h" 14 | #include "queue.h" 15 | #include "error.h" 16 | #include "constString.h" 17 | #include "fuzzyEngine.h" 18 | #include "configManager.h" 19 | 20 | namespace leaf 21 | { 22 | 23 | constexpr uint32_t BufferLen = 16 * 1024; 24 | 25 | enum class Operation 26 | { 27 | Input, 28 | Exit, 29 | Accept, 30 | Backspace, 31 | Delete, 32 | ClearLeft, 33 | DeleteLeftWord, 34 | PageUp, 35 | PageDown, 36 | NormalMode, 37 | CursorMoveLeft, 38 | CursorMoveRight, 39 | CursorMoveToBegin, 40 | CursorMoveToEnd, 41 | CursorLineMoveUp, 42 | CursorLineMoveDown, 43 | Single_Click, 44 | Double_Click, 45 | Right_Click, 46 | Middle_Click, 47 | Wheel_Down, 48 | Wheel_Up, 49 | Ctrl_LeftMouse, 50 | Shift_LeftMouse, 51 | 52 | Invalid 53 | }; 54 | 55 | struct DataBuffer 56 | { 57 | DataBuffer(const DataBuffer&) = delete; 58 | DataBuffer& operator=(const DataBuffer&) = delete; 59 | DataBuffer(DataBuffer&&) = delete; 60 | DataBuffer& operator=(DataBuffer&&) = delete; 61 | 62 | DataBuffer() = default; 63 | 64 | explicit DataBuffer(uint32_t buf_len) 65 | : buffer(new char [buf_len]), len(buf_len) {} 66 | 67 | DataBuffer(const char* buf, uint32_t buf_len) 68 | : DataBuffer(buf_len) { 69 | memcpy(buffer, buf, buf_len); 70 | } 71 | 72 | ~DataBuffer() { 73 | delete [] buffer; 74 | } 75 | 76 | char* buffer{ nullptr }; 77 | uint32_t len{ 0 }; 78 | }; 79 | 80 | using DataBufferPtr = std::shared_ptr; 81 | 82 | class BufferStorage 83 | { 84 | public: 85 | BufferStorage() { 86 | buffers_.reserve(8); 87 | } 88 | 89 | void put(DataBufferPtr sp_buffer) { 90 | buffers_.emplace_back(std::move(sp_buffer)); 91 | } 92 | 93 | void extend(BufferStorage&& storage) { 94 | buffers_.insert(buffers_.end(), storage.buffers_.begin(), storage.buffers_.end()); 95 | } 96 | 97 | bool empty() const noexcept { 98 | return buffers_.empty(); 99 | } 100 | 101 | const std::vector& getBuffers() const noexcept { 102 | return buffers_; 103 | } 104 | 105 | private: 106 | std::vector buffers_; 107 | }; 108 | 109 | class SignalManager 110 | { 111 | public: 112 | SignalManager() { 113 | // make sure sigmask is set in main thread before any thread 114 | sigset_t sig_set; 115 | sigfillset(&sig_set); 116 | // If SIGBUS, SIGFPE, SIGILL, or SIGSEGV are generated while they are blocked, 117 | // the result is undefined, unless the signal was generated by kill(2), sigqueue(3), or raise(3). 118 | // On Linux, the default handler is invoked instead of the user handler. 119 | // so unblock them. 120 | sigdelset(&sig_set, SIGBUS); 121 | sigdelset(&sig_set, SIGFPE); 122 | sigdelset(&sig_set, SIGILL); 123 | sigdelset(&sig_set, SIGSEGV); 124 | 125 | sigdelset(&sig_set, SIGCHLD); 126 | 127 | // support running as a background job 128 | // unblock SIGTTIN and SIGTTOU 129 | sigdelset(&sig_set, SIGTTIN); 130 | sigdelset(&sig_set, SIGTTOU); 131 | 132 | if ( pthread_sigmask(SIG_BLOCK, &sig_set, nullptr) != 0 ) { 133 | Error::getInstance().appendError(ErrorMessage); 134 | std::exit(EXIT_FAILURE); 135 | } 136 | 137 | installHandler(); 138 | } 139 | 140 | void installHandler(); 141 | 142 | static void catchSignal(int sig, siginfo_t *info, void *ctxt); 143 | }; 144 | 145 | class Configuration : private SignalManager 146 | { 147 | public: 148 | Configuration(int argc, char* argv[]) { 149 | Error::getInstance(); // make sure Error object instance is created before Cleanup object instance 150 | ConfigManager::getInstance().loadConfig(argc, argv); 151 | } 152 | }; 153 | 154 | class Application : public Configuration 155 | { 156 | public: 157 | Application(int argc, char* argv[]); 158 | void start(); 159 | 160 | private: 161 | using Task = std::function; 162 | 163 | void _handleSignal(); 164 | void _readConfig(); 165 | void _readData(); 166 | void _processData(BufferStorage&& storage); 167 | void _input(); 168 | void _shorten(const std::string& pattern, uint32_t cursor_pos); 169 | void _search(bool is_continue); 170 | void _doWork(BlockingQueue& q); 171 | void _updateResult(uint32_t result_size, const std::string& pattern); 172 | void _initBuffer(); 173 | void _notifyExit(); 174 | void _showFlag(); 175 | void _resume(); 176 | 177 | static int _exec(const char* cmd); 178 | 179 | std::vector _generateHighlightStr(uint32_t first, uint32_t last, 180 | const std::string& pattern); 181 | private: 182 | Result previous_result_; 183 | StrContainer& result_content_; 184 | StrContainer content_; 185 | StrContainer cb_content_; 186 | BufferStorage buffer_storage_; 187 | std::string incomplete_str_; 188 | 189 | BlockingQueue task_queue_; 190 | BlockingQueue ui_queue_; // should be called in task_queue_ thread 191 | BlockingQueue cmdline_queue_; 192 | 193 | uint32_t access_count_{ 0 }; 194 | std::mutex result_mutex_; 195 | std::condition_variable result_cond_; 196 | 197 | std::atomic running_{ true }; 198 | std::atomic flag_running_{ true }; 199 | std::atomic search_count_{ 0 }; 200 | std::atomic result_content_size_{ 0 }; 201 | 202 | Tui tui_; 203 | std::string pattern_; 204 | bool already_zero_{ true }; 205 | uint32_t index_{ 0 }; 206 | uint32_t cpu_count_; 207 | const uint32_t step_; 208 | Point current_yx_; 209 | uint32_t indent_{ ConfigManager::getInstance().getConfigValue() }; 210 | bool normal_mode_{ false }; 211 | bool sigstop_{ false }; 212 | 213 | FuzzyEngine fuzzy_engine_; 214 | Preference preference_{ ConfigManager::getInstance().getConfigValue() }; 215 | std::unordered_map key_map_; 216 | std::unordered_map op_map_; 217 | std::unordered_map key_op_map_; 218 | std::unordered_map task_map_; 219 | using CmdlineTask = std::function; 220 | std::unordered_map cmdline_task_map_; 221 | 222 | }; 223 | 224 | } // end namespace leaf 225 | -------------------------------------------------------------------------------- /test/ringBufferTest.cpp: -------------------------------------------------------------------------------- 1 | #define TEST_RINGBUFFER 2 | 3 | #include "ringBuffer.h" 4 | #include 5 | 6 | using namespace leaf; 7 | using namespace std; 8 | 9 | 10 | void print(RingBuffer& ring_buffer) { 11 | cout << "capacity_ = " << ring_buffer.capacity() 12 | << ", head_ = " << ring_buffer.head() 13 | << ", tail_ = " << ring_buffer.tail() << endl; 14 | if ( ring_buffer.tail() > ring_buffer.head() ) { 15 | for ( size_t i = 0; i < ring_buffer.head(); ++i ) { 16 | ring_buffer.buffer()[i] = 0; 17 | } 18 | for ( size_t i = ring_buffer.tail(); i < ring_buffer.capacity(); ++i ) { 19 | ring_buffer.buffer()[i] = 0; 20 | } 21 | } 22 | else { 23 | for ( size_t i = ring_buffer.tail(); i < ring_buffer.head(); ++i ) { 24 | ring_buffer.buffer()[i] = 0; 25 | } 26 | } 27 | 28 | for ( size_t i = 0; i < ring_buffer.capacity(); ++i ) { 29 | cout << ring_buffer.buffer()[i] << " "; 30 | } 31 | cout << endl; 32 | 33 | } 34 | 35 | void init(RingBuffer& ring_buffer) { 36 | uint32_t i = 1; 37 | for ( auto it = ring_buffer.begin(); it != ring_buffer.end(); ++it ) { 38 | *it = i++; 39 | } 40 | } 41 | 42 | int main(int argc, const char *argv[]) 43 | { 44 | RingBuffer ring_buffer = {1, 2, 3, 4, 5, 6, 7, 8}; 45 | 46 | for ( auto i : ring_buffer) { 47 | cout << i << " "; 48 | } 49 | cout << endl; 50 | { 51 | RingBuffer test1(8, 2, 7); 52 | init(test1); 53 | print(test1); 54 | test1.push_back(test1.begin(), test1.end()-1); 55 | print(test1); 56 | } 57 | cout << "--------------------------------------" << endl; 58 | { 59 | RingBuffer test1(16, 3, 8); 60 | init(test1); 61 | print(test1); 62 | 63 | RingBuffer test2(16, 3, 8); 64 | init(test2); 65 | print(test2); 66 | 67 | test1.push_back(test2.begin(), test2.end()); 68 | print(test1); 69 | } 70 | cout << "--------------------------------------" << endl; 71 | { 72 | RingBuffer test1(16, 8, 14); 73 | init(test1); 74 | print(test1); 75 | 76 | RingBuffer test2(16, 3, 8); 77 | init(test2); 78 | print(test2); 79 | 80 | test1.push_back(test2.begin(), test2.end()); 81 | print(test1); 82 | } 83 | cout << "--------------------------------------" << endl; 84 | { 85 | RingBuffer test1(16, 3, 8); 86 | init(test1); 87 | print(test1); 88 | 89 | RingBuffer test2(16, 14, 3); 90 | init(test2); 91 | print(test2); 92 | 93 | test1.push_back(test2.begin(), test2.end()); 94 | print(test1); 95 | } 96 | cout << "--------------------------------------" << endl; 97 | { 98 | RingBuffer test1(16, 8, 14); 99 | init(test1); 100 | print(test1); 101 | 102 | RingBuffer test2(16, 15, 3); 103 | init(test2); 104 | print(test2); 105 | 106 | test1.push_back(test2.begin(), test2.end()); 107 | print(test1); 108 | } 109 | cout << "--------------------------------------" << endl; 110 | { 111 | RingBuffer test1(16, 8, 14); 112 | init(test1); 113 | print(test1); 114 | 115 | RingBuffer test2(16, 12, 3); 116 | init(test2); 117 | print(test2); 118 | 119 | test1.push_back(test2.begin(), test2.end()); 120 | print(test1); 121 | } 122 | cout << "--------------------------------------" << endl; 123 | { 124 | RingBuffer test1(16, 14, 4); 125 | init(test1); 126 | print(test1); 127 | 128 | RingBuffer test2(16, 3, 6); 129 | init(test2); 130 | print(test2); 131 | 132 | test1.push_back(test2.begin(), test2.end()); 133 | print(test1); 134 | } 135 | cout << "--------------------------------------" << endl; 136 | { 137 | RingBuffer test1(16, 14, 4); 138 | init(test1); 139 | print(test1); 140 | 141 | RingBuffer test2(16, 12, 3); 142 | init(test2); 143 | print(test2); 144 | 145 | test1.push_back(test2.begin(), test2.end()); 146 | print(test1); 147 | } 148 | cout << "--------------------------------------" << endl; 149 | { 150 | RingBuffer test1; 151 | print(test1); 152 | 153 | RingBuffer test2(16, 3, 8); 154 | init(test2); 155 | print(test2); 156 | 157 | test1.push_back(test2.begin(), test2.end()); 158 | print(test1); 159 | } 160 | cout << "--------------------------------------" << endl; 161 | { 162 | RingBuffer test1; 163 | print(test1); 164 | 165 | RingBuffer test2(16, 12, 3); 166 | init(test2); 167 | print(test2); 168 | 169 | test1.push_back(test2.begin(), test2.end()); 170 | print(test1); 171 | } 172 | cout << "--------------------------------------" << endl; 173 | { 174 | RingBuffer test1(16, 3, 3); 175 | print(test1); 176 | 177 | RingBuffer test2(16, 3, 8); 178 | init(test2); 179 | print(test2); 180 | 181 | test1.push_back(test2.begin(), test2.end()); 182 | print(test1); 183 | } 184 | cout << "--------------------------------------" << endl; 185 | { 186 | RingBuffer test1(16, 3, 3); 187 | print(test1); 188 | 189 | RingBuffer test2(16, 12, 3); 190 | init(test2); 191 | print(test2); 192 | 193 | test1.push_back(test2.begin(), test2.end()); 194 | print(test1); 195 | } 196 | 197 | cout << endl; 198 | cout << "----------- push_front ---------------" << endl; 199 | { 200 | RingBuffer test1(8, 2, 7); 201 | init(test1); 202 | print(test1); 203 | test1.push_front(test1.begin(), test1.end()-1); 204 | print(test1); 205 | } 206 | cout << "--------------------------------------" << endl; 207 | { 208 | RingBuffer test1(16, 8, 14); 209 | init(test1); 210 | print(test1); 211 | 212 | RingBuffer test2(16, 3, 10); 213 | init(test2); 214 | print(test2); 215 | 216 | test1.push_front(test2.begin(), test2.end()); 217 | print(test1); 218 | } 219 | cout << "--------------------------------------" << endl; 220 | { 221 | RingBuffer test1(16, 3, 8); 222 | init(test1); 223 | print(test1); 224 | 225 | RingBuffer test2(16, 3, 10); 226 | init(test2); 227 | print(test2); 228 | 229 | test1.push_front(test2.begin(), test2.end()); 230 | print(test1); 231 | } 232 | cout << "--------------------------------------" << endl; 233 | { 234 | RingBuffer test1(16, 8, 12); 235 | init(test1); 236 | print(test1); 237 | 238 | RingBuffer test2(16, 13, 5); 239 | init(test2); 240 | print(test2); 241 | 242 | test1.push_front(test2.begin(), test2.end()); 243 | print(test1); 244 | } 245 | cout << "--------------------------------------" << endl; 246 | { 247 | RingBuffer test1(16, 3, 8); 248 | init(test1); 249 | print(test1); 250 | 251 | RingBuffer test2(16, 9, 2); 252 | init(test2); 253 | print(test2); 254 | 255 | test1.push_front(test2.begin(), test2.end()); 256 | print(test1); 257 | } 258 | cout << "--------------------------------------" << endl; 259 | { 260 | RingBuffer test1(16, 3, 8); 261 | init(test1); 262 | print(test1); 263 | 264 | RingBuffer test2(16, 14, 5); 265 | init(test2); 266 | print(test2); 267 | 268 | test1.push_front(test2.begin(), test2.end()); 269 | print(test1); 270 | } 271 | cout << "--------------------------------------" << endl; 272 | { 273 | RingBuffer test1(16, 14, 4); 274 | init(test1); 275 | print(test1); 276 | 277 | RingBuffer test2(16, 3, 6); 278 | init(test2); 279 | print(test2); 280 | 281 | test1.push_front(test2.begin(), test2.end()); 282 | print(test1); 283 | } 284 | cout << "--------------------------------------" << endl; 285 | { 286 | RingBuffer test1(16, 14, 4); 287 | init(test1); 288 | print(test1); 289 | 290 | RingBuffer test2(16, 12, 3); 291 | init(test2); 292 | print(test2); 293 | 294 | test1.push_front(test2.begin(), test2.end()); 295 | print(test1); 296 | } 297 | cout << "--------------------------------------" << endl; 298 | { 299 | RingBuffer test1; 300 | print(test1); 301 | 302 | RingBuffer test2(16, 3, 8); 303 | init(test2); 304 | print(test2); 305 | 306 | test1.push_front(test2.begin(), test2.end()); 307 | print(test1); 308 | } 309 | cout << "--------------------------------------" << endl; 310 | { 311 | RingBuffer test1; 312 | print(test1); 313 | 314 | RingBuffer test2(16, 12, 3); 315 | init(test2); 316 | print(test2); 317 | 318 | test1.push_front(test2.begin(), test2.end()); 319 | print(test1); 320 | } 321 | cout << "--------------------------------------" << endl; 322 | { 323 | RingBuffer test1(16, 3, 3); 324 | print(test1); 325 | 326 | RingBuffer test2(16, 3, 8); 327 | init(test2); 328 | print(test2); 329 | 330 | test1.push_front(test2.begin(), test2.end()); 331 | print(test1); 332 | } 333 | cout << "--------------------------------------" << endl; 334 | { 335 | RingBuffer test1(16, 3, 3); 336 | print(test1); 337 | 338 | RingBuffer test2(16, 12, 3); 339 | init(test2); 340 | print(test2); 341 | 342 | test1.push_front(test2.begin(), test2.end()); 343 | print(test1); 344 | } 345 | return 0; 346 | } 347 | 348 | -------------------------------------------------------------------------------- /src/fuzzyEngine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "fuzzyEngine.h" 4 | 5 | namespace leaf 6 | { 7 | 8 | #define MAX_TASK_COUNT(cpu_count) ((cpu_count) << 3) 9 | 10 | Result FuzzyEngine::fuzzyMatch(const StrContainer::const_iterator& source_begin, 11 | uint32_t source_size, 12 | const std::string& pattern, 13 | Preference preference, 14 | DigestFn get_digest, 15 | bool sort_results) 16 | { 17 | if ( source_begin == nullptr || source_size == 0 ) { 18 | return Result(); 19 | } 20 | 21 | if ( pattern != pattern_ ) { 22 | pattern_ = pattern; 23 | pattern_ctxt_.reset(initPattern(pattern_.c_str(), pattern_.length())); 24 | } 25 | 26 | if ( thread_pool_.size() == 0 ) { 27 | thread_pool_.start(cpu_count_); 28 | } 29 | 30 | uint32_t max_task_count = MAX_TASK_COUNT(cpu_count_); 31 | uint32_t chunk_size = (source_size + max_task_count - 1) / max_task_count; 32 | if ( cpu_count_ == 1 ) { 33 | chunk_size = source_size; 34 | } 35 | else if ( chunk_size < 4096 ) { 36 | chunk_size = std::max(4096u, (source_size + cpu_count_ - 1) / cpu_count_); 37 | } 38 | 39 | std::unique_ptr the_results(new MatchResult[source_size]); 40 | auto results = the_results.get(); 41 | for ( uint32_t offset = 0; offset < source_size; offset += chunk_size ) { 42 | uint32_t length = std::min(chunk_size, source_size - offset); 43 | 44 | thread_pool_.enqueueTask([&source_begin, this, results, offset, length, preference] { 45 | for ( auto i = offset; i < offset + length; ++i ) { 46 | results[i].weight = getWeight((source_begin + i)->str, (source_begin + i)->len, 47 | pattern_ctxt_.get(), preference); 48 | results[i].index = i; 49 | } 50 | }); 51 | } 52 | 53 | // blocks until all tasks are done 54 | thread_pool_.join(); 55 | 56 | uint32_t results_count = 0; 57 | for (uint32_t i = 0; i < source_size; ++i ) { 58 | if ( results[i].weight > MIN_WEIGHT ) { 59 | if ( i > results_count ) { 60 | results[results_count] = results[i]; 61 | } 62 | ++results_count; 63 | } 64 | } 65 | 66 | if ( results_count == 0 ) { 67 | return Result(); 68 | } 69 | 70 | if ( sort_results ) { 71 | if ( cpu_count_ == 1 || results_count < 50000 ) { 72 | std::sort(results, results + results_count, 73 | [](const MatchResult& a, const MatchResult& b) { 74 | return a.weight > b.weight; 75 | }); 76 | } 77 | else { 78 | chunk_size = (results_count + max_task_count - 1) / max_task_count; 79 | if ( chunk_size < 4096 ) { 80 | chunk_size = std::max(4096u, (source_size + cpu_count_ - 1) / cpu_count_); 81 | } 82 | for ( uint32_t offset = 0; offset < results_count; offset += chunk_size ) { 83 | uint32_t length = std::min(chunk_size, results_count - offset); 84 | 85 | thread_pool_.enqueueTask([results, offset, length] { 86 | std::sort(results + offset, results + (offset + length), 87 | [](const MatchResult& a, const MatchResult& b) { 88 | return a.weight > b.weight; 89 | }); 90 | }); 91 | } 92 | 93 | // blocks until all tasks are done 94 | thread_pool_.join(); 95 | 96 | auto task_count = (results_count + chunk_size - 1) / chunk_size; 97 | std::unique_ptr result_buffer(new MatchResult[chunk_size * (task_count >> 1)]); 98 | while ( chunk_size < results_count ) { 99 | uint32_t two_chunk_size = chunk_size << 1; 100 | uint32_t q = results_count / two_chunk_size; 101 | uint32_t r = results_count % two_chunk_size; 102 | for (uint32_t i = 0; i < q; ++i ) { 103 | auto offset_1 = i * two_chunk_size; 104 | auto length_1 = chunk_size; 105 | auto length_2 = chunk_size; 106 | auto buffer = result_buffer.get() + (offset_1 >> 1); 107 | 108 | thread_pool_.enqueueTask([this, results, offset_1, length_1, length_2, buffer] { 109 | _merge(results, buffer, offset_1, length_1, length_2); 110 | }); 111 | } 112 | 113 | if ( r > chunk_size ) { 114 | auto offset_1 = q * two_chunk_size; 115 | auto length_1 = chunk_size; 116 | auto length_2 = r - chunk_size; 117 | auto buffer = result_buffer.get() + (offset_1 >> 1); 118 | 119 | thread_pool_.enqueueTask([this, results, offset_1, length_1, length_2, buffer] { 120 | _merge(results, buffer, offset_1, length_1, length_2); 121 | }); 122 | } 123 | 124 | chunk_size <<= 1; 125 | 126 | // blocks until all tasks are done 127 | thread_pool_.join(); 128 | } 129 | } 130 | } 131 | 132 | Result r{ WeightContainer(results_count), StrContainer(results_count) }; 133 | auto& weight_list = std::get<0>(r); 134 | auto& str_list = std::get<1>(r); 135 | if ( cpu_count_ == 1 || results_count < 50000 ) { 136 | for (uint32_t i = 0; i < results_count; ++i ) { 137 | weight_list[i] = results[i].weight; 138 | str_list[i] = *(source_begin + results[i].index); 139 | } 140 | } 141 | else 142 | { 143 | chunk_size = (results_count + max_task_count - 1) / max_task_count; 144 | if ( chunk_size < 4096 ) { 145 | chunk_size = std::max(4096u, (source_size + cpu_count_ - 1) / cpu_count_); 146 | } 147 | for ( uint32_t offset = 0; offset < results_count; offset += chunk_size ) { 148 | uint32_t length = std::min(chunk_size, results_count - offset); 149 | 150 | thread_pool_.enqueueTask([&source_begin, &weight_list, &str_list, results, offset, length] { 151 | for ( auto i = offset; i < offset + length; ++i ) { 152 | weight_list[i] = results[i].weight; 153 | str_list[i] = *(source_begin + results[i].index); 154 | } 155 | }); 156 | } 157 | thread_pool_.join(); 158 | } 159 | 160 | return r; 161 | } 162 | 163 | Result FuzzyEngine::merge(const Result& a, const Result& b) 164 | { 165 | const auto& weights_a = std::get<0>(a); 166 | auto size_a = weights_a.size(); 167 | if ( size_a == 0 ) { 168 | return b; 169 | } 170 | const auto& weights_b = std::get<0>(b); 171 | auto size_b = weights_b.size(); 172 | if ( size_b == 0 ) { 173 | return a; 174 | } 175 | 176 | Result result{ WeightContainer(size_a + size_b), StrContainer(size_a + size_b) }; 177 | auto& weight_list = std::get<0>(result); 178 | auto& str_list = std::get<1>(result); 179 | 180 | decltype(size_a) i = 0; 181 | decltype(size_b) j = 0; 182 | 183 | const auto& weights_a_iter = weights_a.begin(); 184 | const auto& weights_b_iter = weights_b.begin(); 185 | const auto& str_list_a_iter = std::get<1>(a).begin(); 186 | const auto& str_list_b_iter = std::get<1>(b).begin(); 187 | while ( i < size_a && j < size_b ) { 188 | if ( *(weights_a_iter + i) > *(weights_b_iter + j) ) { 189 | weight_list[i + j] = *(weights_a_iter + i); 190 | str_list[i + j] = *(str_list_a_iter + i); 191 | ++i; 192 | } 193 | else { 194 | weight_list[i + j] = *(weights_b_iter + j); 195 | str_list[i + j] = *(str_list_b_iter + j); 196 | ++j; 197 | } 198 | } 199 | 200 | if ( i < size_a ) { 201 | // move tail_ pointer back 202 | weight_list.pop_back(size_a - i); 203 | weight_list.push_back(weights_a_iter + i, weights_a.cend()); 204 | // move tail_ pointer back 205 | str_list.pop_back(size_a - i); 206 | str_list.push_back(str_list_a_iter + i, str_list_a_iter + size_a); 207 | } 208 | 209 | if ( j < size_b ) { 210 | // move tail_ pointer back 211 | weight_list.pop_back(size_b - j); 212 | weight_list.push_back(weights_b.cbegin() + j, weights_b.cend()); 213 | // move tail_ pointer back 214 | str_list.pop_back(size_b - j); 215 | str_list.push_back(str_list_b_iter + j, str_list_b_iter + size_b); 216 | } 217 | 218 | return result; 219 | } 220 | 221 | std::vector> 222 | FuzzyEngine::getHighlights(const StrContainer::const_iterator& source_begin, 223 | uint32_t source_size, 224 | const std::string& pattern, 225 | DigestFn get_digest) 226 | { 227 | std::vector> res; 228 | 229 | if ( source_begin == nullptr || source_size == 0 ) { 230 | return res; 231 | } 232 | 233 | PatternContextPtr pattern_ctxt(initPattern(pattern.c_str(), pattern.length())); 234 | res.reserve(source_size); 235 | auto end = source_begin + source_size; 236 | for ( auto iter = source_begin; iter != end; ++iter ) { 237 | res.emplace_back(FuzzyMatch::getHighlights(iter->str, iter->len, pattern_ctxt.get())); 238 | } 239 | 240 | return res; 241 | } 242 | 243 | void FuzzyEngine::_merge(MatchResult* results, 244 | MatchResult* buffer, 245 | uint32_t offset_1, 246 | uint32_t length_1, 247 | uint32_t length_2) 248 | { 249 | auto list_1 = results + offset_1; 250 | auto list_2 = list_1 + length_1; 251 | memcpy(buffer, list_2, length_2 * sizeof(MatchResult)); 252 | int32_t i = length_1 - 1; 253 | int32_t j = length_2 - 1; 254 | int32_t k = length_1 + j; 255 | while ( i >= 0 && j >= 0 ) { 256 | if ( list_1[i].weight < buffer[j].weight ) { 257 | list_1[k--] = list_1[i--]; 258 | } 259 | else { 260 | list_1[k--] = buffer[j--]; 261 | } 262 | } 263 | while ( j >= 0 ) { 264 | list_1[k--] = buffer[j--]; 265 | } 266 | } 267 | 268 | 269 | } // end namespace leaf 270 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/tty.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "singleton.h" 14 | #include "consts.h" 15 | 16 | 17 | namespace leaf 18 | { 19 | 20 | class Tty final : public Singleton 21 | { 22 | friend Singleton; 23 | public: 24 | ~Tty(); 25 | 26 | std::tuple getchar(); 27 | 28 | // Moves the cursor n (default 1) cells in the given direction. 29 | // If the cursor is already at the edge of the screen, this has no effect. 30 | void moveCursor(CursorDirection dirction, uint32_t n=1) { 31 | switch ( dirction ) 32 | { 33 | case CursorDirection::Up: 34 | fprintf(stdout_, "\033[%uA", n); 35 | break; 36 | case CursorDirection::Down: 37 | fprintf(stdout_, "\033[%uB", n); 38 | break; 39 | case CursorDirection::Right: 40 | fprintf(stdout_, "\033[%uC", n); 41 | break; 42 | case CursorDirection::Left: 43 | fprintf(stdout_, "\033[%uD", n); 44 | break; 45 | case CursorDirection::NextLine: 46 | // Moves cursor to beginning of the line n (default 1) lines down 47 | fprintf(stdout_, "\033[%uE", n); 48 | break; 49 | case CursorDirection::PrevLine: 50 | // Moves cursor to beginning of the line n (default 1) lines up 51 | fprintf(stdout_, "\033[%uF", n); 52 | break; 53 | case CursorDirection::HorizontalAbsolute: 54 | // Moves the cursor to column n (default 1) 55 | fprintf(stdout_, "\033[%uG", n); 56 | break; 57 | } 58 | } 59 | 60 | // Moves the cursor to line n, column m. 61 | // The values are 1-based, and default to 1 (top left corner) if omitted. 62 | void moveCursorTo(uint32_t line=1, uint32_t column=1) { 63 | fprintf(stdout_, "\033[%u;%uH", line, column); 64 | } 65 | 66 | void clear(EraseMode e) { 67 | switch ( e ) 68 | { 69 | case EraseMode::ToScreenEnd: 70 | fprintf(stdout_, "\033[0J"); 71 | break; 72 | case EraseMode::ToScreenBegin: 73 | fprintf(stdout_, "\033[1J"); 74 | break; 75 | case EraseMode::EntireScreen: 76 | fprintf(stdout_, "\033[2J"); 77 | break; 78 | case EraseMode::EntireScreenAndScroll: 79 | fprintf(stdout_, "\033[3J"); 80 | break; 81 | case EraseMode::ToLineEnd: 82 | fprintf(stdout_, "\033[0K"); 83 | break; 84 | case EraseMode::ToLineBegin: 85 | fprintf(stdout_, "\033[1K"); 86 | break; 87 | case EraseMode::EntireLine: 88 | fprintf(stdout_, "\033[2K"); 89 | break; 90 | } 91 | } 92 | 93 | // Scroll whole page up by n (default 1) lines. 94 | // New lines are added at the bottom. 95 | void scrollUp(uint32_t n=1) { 96 | fprintf(stdout_, "\033[%uS", n); 97 | } 98 | 99 | // Scroll whole page down by n (default 1) lines. 100 | // New lines are added at the top. 101 | void scrollDown(uint32_t n=1) { 102 | fprintf(stdout_, "\033[%uT", n); 103 | } 104 | 105 | void saveCursorPosition() { 106 | //fprintf(stdout_, "\033[s"); 107 | fprintf(stdout_, "\0337"); 108 | } 109 | 110 | void saveCursorPosition(uint32_t line, uint32_t col) { 111 | //fprintf(stdout_, "\033[%u;%uH\033[s", line, col); 112 | fprintf(stdout_, "\033[%u;%uH\0337", line, col); 113 | } 114 | 115 | void restoreCursorPosition() { 116 | //fprintf(stdout_, "\033[u"); 117 | fprintf(stdout_, "\0338"); 118 | fflush(stdout_); 119 | } 120 | 121 | void showCursor() { 122 | fprintf(stdout_, "\033[?25h"); 123 | fflush(stdout_); 124 | } 125 | 126 | void showCursor_s() { 127 | write(term_stdout_, "\033[?25h", strlen("\033[?25h")); 128 | } 129 | 130 | void hideCursor() { 131 | fprintf(stdout_, "\033[?25l"); 132 | } 133 | 134 | void enableAlternativeBuffer() { 135 | fprintf(stdout_, "\033[?1049h"); 136 | } 137 | 138 | void disableAlternativeBuffer() { 139 | fprintf(stdout_, "\033[?1049l"); 140 | } 141 | 142 | void disableAlternativeBuffer_s() { 143 | write(term_stdout_, "\033[?1049l", strlen("\033[?1049l")); 144 | } 145 | 146 | void enableMouse() { 147 | fprintf(stdout_, "\033[?1000;1006h"); 148 | } 149 | 150 | void disableMouse() { 151 | fprintf(stdout_, "\033[?1000;1006l"); 152 | } 153 | 154 | void disableMouse_s() { 155 | write(term_stdout_, "\033[?1000;1006l", strlen("\033[?1000;1006l")); 156 | } 157 | 158 | void enableAutoWrap() { 159 | fprintf(stdout_, "\033[?7h"); 160 | } 161 | 162 | void enableAutoWrap_s() { 163 | write(term_stdout_, "\033[?7h", strlen("\033[?7h")); 164 | } 165 | 166 | void disableAutoWrap() { 167 | fprintf(stdout_, "\033[?7l"); 168 | } 169 | 170 | // color is 0 - 255 or #000000 - #FFFFFF 171 | void setForegroundColor(const std::string& color) { 172 | if ( color[0] == '#' ) { 173 | fprintf(stdout_, "\033]10;%s\a", color.c_str()); 174 | } 175 | else { 176 | auto index = stoi(color); 177 | if ( index >= 0 && index < 256 ) { 178 | fprintf(stdout_, "\033]10;%s\a", color_names_[index]); 179 | } 180 | else { 181 | } 182 | } 183 | } 184 | 185 | // color is 0 - 255 or #000000 - #FFFFFF 186 | void setBackgroundColor(const std::string& color) { 187 | if ( color[0] == '#' ) { 188 | fprintf(stdout_, "\033]11;%s\a", color.c_str()); 189 | } 190 | else { 191 | auto index = stoi(color); 192 | if ( index >= 0 && index < 256 ) { 193 | fprintf(stdout_, "\033]11;%s\a", color_names_[index]); 194 | } 195 | else { 196 | } 197 | } 198 | } 199 | 200 | void resetForegroundColor() { 201 | fprintf(stdout_, "\033]110\a"); 202 | } 203 | 204 | void resetBackgroundColor() { 205 | fprintf(stdout_, "\033]111\a"); 206 | } 207 | 208 | void flush() { 209 | fflush(stdout_); 210 | } 211 | 212 | int getCursorPosition(uint32_t& line, uint32_t& col) { 213 | if ( write(term_stdout_, "\033[6n", 4) != 4 ) { 214 | perror("write"); 215 | return -1; 216 | } 217 | 218 | char buffer[32] = { 0 }; 219 | if ( read(term_stdin_, buffer, sizeof(buffer) - 1) < 0 ) { 220 | perror("read"); 221 | return -1; 222 | } 223 | 224 | uint32_t i = 0; 225 | while ( buffer[i] != '\0' ) { 226 | if ( buffer[i] == '\033' ) { 227 | break; 228 | } 229 | ++i; 230 | } 231 | 232 | if ( sscanf(&buffer[i], "\033[%u;%uR", &line, &col) != 2 ) { 233 | return -1; 234 | } 235 | 236 | return 0; 237 | } 238 | 239 | int getCursorPosition2(uint32_t& line, uint32_t& col) { 240 | cursor_pos_ = true; 241 | if ( write(term_stdout_, "\033[6n", 4) != 4 ) { 242 | perror("write"); 243 | return -1; 244 | } 245 | 246 | std::unique_lock lock(mutex_); 247 | while ( cursor_pos_ == true ) { 248 | cursor_cond_.wait(lock); 249 | } 250 | 251 | line = cursor_line_; 252 | col = cursor_col_; 253 | 254 | return 0; 255 | } 256 | 257 | int getWindowSize(uint32_t& lines, uint32_t& cols) { 258 | struct winsize ws; 259 | if (ioctl(term_stdout_, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { 260 | saveCursorPosition(); 261 | moveCursorTo(1024, 1024); 262 | fflush(stdout_); 263 | int ret = getCursorPosition(lines, cols); 264 | restoreCursorPosition(); 265 | return ret; 266 | } 267 | else { 268 | cols = ws.ws_col; 269 | lines = ws.ws_row; 270 | return 0; 271 | } 272 | } 273 | 274 | int getWindowSize2(uint32_t& lines, uint32_t& cols) { 275 | struct winsize ws; 276 | if (ioctl(term_stdout_, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { 277 | saveCursorPosition(); 278 | moveCursorTo(1024, 1024); 279 | fflush(stdout_); 280 | int ret = getCursorPosition2(lines, cols); 281 | restoreCursorPosition(); 282 | return ret; 283 | } 284 | else { 285 | cols = ws.ws_col; 286 | lines = ws.ws_row; 287 | return 0; 288 | } 289 | } 290 | 291 | struct String 292 | { 293 | template , std::string>::value> 295 | > 296 | static const char* c_str(T&& str) { 297 | return str.c_str(); 298 | } 299 | 300 | template , const char*>::value 302 | || std::is_same, char*>::value> 303 | > 304 | static const char* c_str(T str) { 305 | return str; 306 | } 307 | }; 308 | 309 | template 310 | void addString(T&& str) { 311 | fprintf(stdout_, "%s", String::c_str(std::forward(str))); 312 | } 313 | 314 | template 315 | void addString(T&& str, C&& color) { 316 | static_assert(std::is_same, std::string>::value, "color must be std::string!"); 317 | fprintf(stdout_, "%s%s\033[0m", color.c_str(), String::c_str(std::forward(str))); 318 | } 319 | 320 | template 321 | void addString(uint32_t line, uint32_t col, T&& str) { 322 | fprintf(stdout_, "\033[%u;%uH%s", line, col, String::c_str(std::forward(str))); 323 | } 324 | 325 | template 326 | void addString(uint32_t line, uint32_t col, T&& str, C&& color) { 327 | static_assert(std::is_same, std::string>::value, "color must be std::string!"); 328 | fprintf(stdout_, "\033[%u;%uH%s%s\033[0m", line, col, color.c_str(), String::c_str(std::forward(str))); 329 | } 330 | 331 | template 332 | void addStringAndSave(uint32_t line, uint32_t col, T&& str) { 333 | //fprintf(stdout_, "\033[%u;%uH%s\033[s", line, col, String::c_str(std::forward(str))); 334 | fprintf(stdout_, "\033[%u;%uH%s\0337", line, col, String::c_str(std::forward(str))); 335 | } 336 | 337 | template 338 | void addStringAndSave(uint32_t line, uint32_t col, T&& str, C&& color) { 339 | static_assert(std::is_same, std::string>::value, "color must be std::string!"); 340 | //fprintf(stdout_, "\033[%u;%uH%s%s\033[0m\033[s", line, col, color.c_str(), String::c_str(std::forward(str))); 341 | fprintf(stdout_, "\033[%u;%uH%s%s\033[0m\0337", line, col, color.c_str(), String::c_str(std::forward(str))); 342 | } 343 | 344 | void exit() { 345 | running_.store(false, std::memory_order_relaxed); 346 | } 347 | 348 | void restoreOrigTerminal(); 349 | void restoreOrigTerminal_s(); 350 | void setNewTerminal(); 351 | private: 352 | Tty(); 353 | void _init(); 354 | std::string _mouseTracking(const std::string& esc_code, Key& key) const; 355 | bool _getCursorPos(const std::string& esc_code); 356 | private: 357 | int term_stdin_{ STDIN_FILENO }; 358 | int term_stdout_{ STDOUT_FILENO }; 359 | FILE* stdin_{ stdin }; 360 | FILE* stdout_{ stdout }; 361 | const char* color_names_[256]; 362 | struct termios orig_term_; 363 | struct termios new_term_; 364 | using EscCodeMap = std::unordered_map; 365 | EscCodeMap esc_codes_; 366 | std::atomic running_{ true }; 367 | bool cursor_pos_{ false }; 368 | mutable std::mutex mutex_; 369 | std::condition_variable cursor_cond_; 370 | uint32_t cursor_line_{ 0 }; 371 | uint32_t cursor_col_{ 0 }; 372 | 373 | }; 374 | 375 | } // end namespace leaf 376 | -------------------------------------------------------------------------------- /src/color.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "utils.h" 9 | 10 | namespace leaf 11 | { 12 | 13 | enum class Attribute 14 | { 15 | Normal = 0, 16 | Bold = 1, 17 | Dim = 2, 18 | Italic = 3, 19 | Underline = 4, 20 | SlowBlink = 5, 21 | RapidBlink = 6, 22 | Reverse = 7, 23 | Conceal = 8, 24 | Strikethrough = 9, 25 | 26 | MaxAttrNum 27 | }; 28 | 29 | enum class HighlightGroup 30 | { 31 | Normal, 32 | Match0, 33 | Match1, 34 | Match2, 35 | Match3, 36 | Match4, 37 | Prompt, 38 | CursorLine, 39 | Indicator, 40 | LineInfo, 41 | Flag, 42 | NormalMode, 43 | Border, 44 | 45 | MaxGroupNum 46 | }; 47 | 48 | enum class ColorPriority { 49 | Normal, 50 | High 51 | }; 52 | 53 | class Color8 54 | { 55 | public: 56 | Color8() = default; 57 | Color8(const std::string& fg, const std::string& bg, 58 | Attribute attr=Attribute::Normal, 59 | ColorPriority priority=ColorPriority::Normal) { 60 | if ( fg.empty() && bg.empty() && attr == Attribute::Normal) { 61 | return; 62 | } 63 | 64 | std::string attribute; 65 | if ( attr != Attribute::Normal ) { 66 | attribute = ";" + std::to_string(static_cast(attr)); 67 | } 68 | 69 | auto str = utils::strFormat<16>("%s%s%s%s%s%s", 70 | priority == ColorPriority::Normal ? "" : "0", 71 | attribute.c_str(), 72 | fg.empty() ? "" : ";", 73 | fg.c_str(), 74 | bg.empty() ? "" : ";", 75 | bg.c_str()); 76 | 77 | auto p_str = str.c_str(); 78 | esc_code_ = utils::strFormat<16>("\033[%sm", *p_str == ';' ? p_str + 1 : p_str); 79 | } 80 | 81 | Color8(const std::string& fg, const std::string& bg, 82 | const std::bitset(Attribute::MaxAttrNum)>& attr, 83 | ColorPriority priority=ColorPriority::Normal) { 84 | std::string attributes; 85 | uint32_t max_num = static_cast(Attribute::MaxAttrNum); 86 | for ( uint32_t i = 1; i < max_num; ++i ) { 87 | if ( attr.test(i) ) { 88 | attributes += ";" + std::to_string(i); 89 | } 90 | } 91 | 92 | if ( fg.empty() && bg.empty() && attributes.empty() ) { 93 | return; 94 | } 95 | 96 | auto str = utils::strFormat<64>("%s%s%s%s%s%s", 97 | priority == ColorPriority::Normal ? "" : "0", 98 | attributes.c_str(), 99 | fg.empty() ? "" : ";", 100 | fg.c_str(), 101 | bg.empty() ? "" : ";", 102 | bg.c_str()); 103 | 104 | auto p_str = str.c_str(); 105 | esc_code_ = utils::strFormat<64>("\033[%sm", *p_str == ';' ? p_str + 1 : p_str); 106 | } 107 | 108 | const std::string& getColor() const noexcept { 109 | return esc_code_; 110 | } 111 | 112 | private: 113 | std::string esc_code_{ "\033[0m" }; 114 | }; 115 | 116 | class Color256 117 | { 118 | public: 119 | Color256() = default; 120 | // fg and bg is 0-255 121 | Color256(const std::string& fg, const std::string& bg, 122 | Attribute attr=Attribute::Normal, 123 | ColorPriority priority=ColorPriority::Normal) { 124 | if ( fg.empty() && bg.empty() && attr == Attribute::Normal) { 125 | return; 126 | } 127 | 128 | std::string attribute; 129 | if ( attr != Attribute::Normal ) { 130 | attribute = ";" + std::to_string(static_cast(attr)); 131 | } 132 | 133 | auto str = utils::strFormat<32>("%s%s%s%s%s%s", 134 | priority == ColorPriority::Normal ? "" : "0", 135 | attribute.c_str(), 136 | fg.empty() ? "" : ";38;5;", 137 | fg.c_str(), 138 | bg.empty() ? "" : ";48;5;", 139 | bg.c_str()); 140 | 141 | auto p_str = str.c_str(); 142 | esc_code_ = utils::strFormat<32>("\033[%sm", *p_str == ';' ? p_str + 1 : p_str); 143 | } 144 | 145 | Color256(const std::string& fg, const std::string& bg, 146 | const std::bitset(Attribute::MaxAttrNum)>& attr, 147 | ColorPriority priority=ColorPriority::Normal) { 148 | std::string attributes; 149 | uint32_t max_num = static_cast(Attribute::MaxAttrNum); 150 | for ( uint32_t i = 1; i < max_num; ++i ) { 151 | if ( attr.test(i) ) { 152 | attributes += ";" + std::to_string(i); 153 | } 154 | } 155 | 156 | if ( fg.empty() && bg.empty() && attributes.empty() ) { 157 | return; 158 | } 159 | 160 | auto str = utils::strFormat<64>("%s%s%s%s%s%s", 161 | priority == ColorPriority::Normal ? "" : "0", 162 | attributes.c_str(), 163 | fg.empty() ? "" : ";38;5;", 164 | fg.c_str(), 165 | bg.empty() ? "" : ";48;5;", 166 | bg.c_str()); 167 | 168 | auto p_str = str.c_str(); 169 | esc_code_ = utils::strFormat<64>("\033[%sm", *p_str == ';' ? p_str + 1 : p_str); 170 | } 171 | 172 | const std::string& getColor() const noexcept { 173 | return esc_code_; 174 | } 175 | 176 | private: 177 | std::string esc_code_{ "\033[0m" }; 178 | }; 179 | 180 | 181 | class Color24Bit 182 | { 183 | public: 184 | Color24Bit() = default; 185 | // fg and bg is Hex color code, e.g., #FF7F50, which represents RGB(255,127,80) 186 | Color24Bit(const std::string& fg, const std::string& bg, 187 | Attribute attr=Attribute::Normal, 188 | ColorPriority priority=ColorPriority::Normal) { 189 | if ( fg.empty() && bg.empty() && attr == Attribute::Normal) { 190 | return; 191 | } 192 | 193 | std::string attribute; 194 | if ( attr != Attribute::Normal ) { 195 | attribute = ";" + std::to_string(static_cast(attr)); 196 | } 197 | 198 | auto str = utils::strFormat<64>("%s%s%s%s%s%s", 199 | priority == ColorPriority::Normal ? "" : "0", 200 | attribute.c_str(), 201 | fg.empty() ? "" : ";38;2;", 202 | hex2Rgb(fg).c_str(), 203 | bg.empty() ? "" : ";48;2;", 204 | hex2Rgb(bg).c_str()); 205 | 206 | auto p_str = str.c_str(); 207 | esc_code_ = utils::strFormat<64>("\033[%sm", *p_str == ';' ? p_str + 1 : p_str); 208 | } 209 | 210 | Color24Bit(const std::string& fg, const std::string& bg, 211 | const std::bitset(Attribute::MaxAttrNum)>& attr, 212 | ColorPriority priority=ColorPriority::Normal) { 213 | std::string attributes; 214 | uint32_t max_num = static_cast(Attribute::MaxAttrNum); 215 | for ( uint32_t i = 1; i < max_num; ++i ) { 216 | if ( attr.test(i) ) { 217 | attributes += ";" + std::to_string(i); 218 | } 219 | } 220 | 221 | if ( fg.empty() && bg.empty() && attributes.empty() ) { 222 | return; 223 | } 224 | 225 | auto str = utils::strFormat<64>("%s%s%s%s%s%s", 226 | priority == ColorPriority::Normal ? "" : "0", 227 | attributes.c_str(), 228 | fg.empty() ? "" : ";38;2;", 229 | hex2Rgb(fg).c_str(), 230 | bg.empty() ? "" : ";48;2;", 231 | hex2Rgb(bg).c_str()); 232 | 233 | auto p_str = str.c_str(); 234 | esc_code_ = utils::strFormat<64>("\033[%sm", *p_str == ';' ? p_str + 1 : p_str); 235 | } 236 | 237 | const std::string& getColor() const noexcept { 238 | return esc_code_; 239 | } 240 | 241 | // e.g., hex is #FF7F50 242 | std::string hex2Rgb(const std::string& hex) { 243 | std::string rgb; 244 | if ( !hex.empty() ) { 245 | int hex_color = std::stoi(hex.substr(1), nullptr, 16); 246 | int r = (hex_color >> 16) & 0xFF; 247 | int g = (hex_color >> 8) & 0xFF; 248 | int b = hex_color & 0xFF; 249 | rgb = utils::strFormat<16>("%d;%d;%d", r, g, b); 250 | } 251 | return rgb; 252 | } 253 | private: 254 | std::string esc_code_{ "\033[0m" }; 255 | }; 256 | 257 | class Color final 258 | { 259 | class ColorInterface 260 | { 261 | public: 262 | virtual ~ColorInterface() = default; 263 | virtual const std::string& getColor() const = 0; 264 | }; 265 | 266 | struct ColorImplTag {}; 267 | 268 | template 269 | class ColorImpl final: public ColorInterface 270 | { 271 | public: 272 | 273 | template 274 | explicit ColorImpl(ColorImplTag, U&& color): color_(std::forward(color)) { 275 | static_assert(std::is_same::value, "Must be the same type!"); 276 | } 277 | 278 | const std::string& getColor() const override { 279 | return color_.getColor(); 280 | } 281 | 282 | private: 283 | T color_; 284 | }; 285 | 286 | public: 287 | Color() = default; 288 | 289 | template , Color>::value> 291 | > 292 | explicit Color(T&& c): p_color_(new ColorImpl(ColorImplTag(), std::forward(c))) {} 293 | 294 | const std::string& getColor() const noexcept { 295 | return p_color_ ? p_color_->getColor() : getDefaultColor(); 296 | } 297 | 298 | template 299 | void setColor(T&& color) { 300 | p_color_.reset(new ColorImpl(ColorImplTag(), std::forward(color))); 301 | } 302 | private: 303 | const std::string& getDefaultColor() const noexcept { 304 | static std::string color{ "\033[0m" }; 305 | return color; 306 | } 307 | 308 | private: 309 | std::unique_ptr p_color_; 310 | }; 311 | 312 | 313 | using ColorArray = std::array(HighlightGroup::MaxGroupNum)>; 314 | 315 | class Colorscheme 316 | { 317 | public: 318 | Colorscheme() { 319 | colors_[static_cast(HighlightGroup::Prompt)].setColor(Color256("1", "", Attribute::Bold)); 320 | colors_[static_cast(HighlightGroup::CursorLine)].setColor(Color256("228", "", Attribute::Bold)); 321 | colors_[static_cast(HighlightGroup::Indicator)].setColor(Color256("5", "", Attribute::Bold)); 322 | colors_[static_cast(HighlightGroup::Match0)].setColor(Color256("155", "", Attribute::Bold)); 323 | colors_[static_cast(HighlightGroup::LineInfo)].setColor(Color256("254", "59")); 324 | colors_[static_cast(HighlightGroup::Flag)].setColor(Color256("218", "")); 325 | colors_[static_cast(HighlightGroup::NormalMode)].setColor(Color256("1", "", Attribute::Reverse)); 326 | colors_[static_cast(HighlightGroup::Border)].setColor(Color256("246", "")); 327 | //colors_[static_cast(HighlightGroup::Normal)].setColor(Color256("", "", Attribute::Normal, ColorPriority::High)); 328 | } 329 | 330 | const std::string& getColor(HighlightGroup group) const noexcept { 331 | return colors_[static_cast(group)].getColor(); 332 | } 333 | 334 | template 335 | void setColor(HighlightGroup group, T&& color) { 336 | colors_[static_cast(group)].setColor(std::forward(color)); 337 | } 338 | 339 | const ColorArray& getColorArray() const noexcept { 340 | return colors_; 341 | } 342 | private: 343 | ColorArray colors_; 344 | }; 345 | 346 | 347 | } // end namespace leaf 348 | -------------------------------------------------------------------------------- /src/tui.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "constString.h" 9 | #include "color.h" 10 | #include "tty.h" 11 | #include "configManager.h" 12 | #include "singleton.h" 13 | 14 | namespace leaf 15 | { 16 | 17 | class Point 18 | { 19 | public: 20 | Point() = default; 21 | Point(uint32_t l, uint32_t c): line(l), col(c) {} 22 | uint32_t line; 23 | uint32_t col; 24 | }; 25 | 26 | struct HighlightString 27 | { 28 | template 29 | HighlightString(T&& s, const ConstString& const_str): str(s), raw_str(const_str) {} 30 | 31 | std::string str; 32 | ConstString raw_str; 33 | }; 34 | 35 | using Generator = std::function()>; 36 | 37 | class Window 38 | { 39 | public: 40 | Window(const Point& tl, const Point& br, const Colorscheme& cs); 41 | virtual ~Window() = default; 42 | 43 | void _init(const Point& tl, const Point& br); 44 | 45 | uint32_t getHeight() const noexcept { 46 | return height_; 47 | } 48 | 49 | uint32_t getWidth() const noexcept { 50 | return width_; 51 | } 52 | 53 | uint32_t getCoreHeight() const noexcept { 54 | return core_height_; 55 | } 56 | 57 | uint32_t getCoreWidth() const noexcept { 58 | return core_width_; 59 | } 60 | 61 | void setBorder() { 62 | auto& border = ConfigManager::getInstance().getConfigValue(); 63 | if ( border.empty() ) { 64 | return; 65 | } 66 | 67 | if ( border.find("T") != std::string::npos ) { 68 | border_mask_ |= 0b0001; 69 | core_height_ -= 1; 70 | core_top_left_.line += 1; 71 | } 72 | 73 | auto border_char_width = ConfigManager::getInstance().getBorderCharWidth(); 74 | if ( border.find("R") != std::string::npos ) { 75 | border_mask_ |= 0b0010; 76 | core_width_ -= border_char_width; 77 | } 78 | 79 | if ( border.find("B") != std::string::npos ) { 80 | border_mask_ |= 0b0100; 81 | core_height_ -= 1; 82 | } 83 | 84 | if ( border.find("L") != std::string::npos ) { 85 | border_mask_ |= 0b1000; 86 | core_width_ -= border_char_width; 87 | core_top_left_.col += border_char_width; 88 | } 89 | } 90 | 91 | void setBuffer(Generator&& generator); 92 | void setBuffer(); 93 | 94 | const std::vector& getBuffer() const noexcept { 95 | return buffer_; 96 | } 97 | 98 | void scrollUp() { 99 | if ( is_reverse_ ) { 100 | _scrollDown(); 101 | } 102 | else { 103 | _scrollUp(); 104 | } 105 | } 106 | 107 | void _scrollUp(); 108 | 109 | void scrollDown() { 110 | if ( is_reverse_ ) { 111 | _scrollUp(); 112 | } 113 | else { 114 | _scrollDown(); 115 | } 116 | } 117 | 118 | void _scrollDown(); 119 | 120 | void pageUp() { 121 | if ( is_reverse_ ) { 122 | _pageDown(); 123 | } 124 | else { 125 | _pageUp(); 126 | } 127 | } 128 | 129 | void _pageUp(); 130 | 131 | void pageDown() { 132 | if ( is_reverse_ ) { 133 | _pageUp(); 134 | } 135 | else { 136 | _pageDown(); 137 | } 138 | } 139 | 140 | void _pageDown(); 141 | 142 | bool inWindow(const Point& point) const noexcept { 143 | return point.line >= top_left_.line 144 | && point.line <= bottom_right_.line 145 | && point.col >= top_left_.col 146 | && point.col <= bottom_right_.col; 147 | } 148 | 149 | bool inCoreWindow(const Point& point) const noexcept { 150 | if ( is_reverse_ ) { 151 | return point.line <= core_top_left_.line 152 | && point.line > core_top_left_.line - core_height_ 153 | && point.col >= core_top_left_.col 154 | && point.col < core_top_left_.col + core_width_; 155 | } 156 | else { 157 | return point.line >= core_top_left_.line 158 | && point.line < core_top_left_.line + core_height_ 159 | && point.col >= core_top_left_.col 160 | && point.col < core_top_left_.col + core_width_; 161 | } 162 | } 163 | 164 | void printAcceptedStrings() const noexcept { 165 | if ( !buffer_.empty() ) { 166 | printf("%s\n", std::string(buffer_[cursor_line_].raw_str.str, 167 | buffer_[cursor_line_].raw_str.len).c_str()); 168 | } 169 | } 170 | 171 | void singleClick(const Point& yx) { 172 | if ( is_reverse_ ) { 173 | _updateCursorline(first_line_ + core_top_left_.line - yx.line); 174 | } 175 | else { 176 | _updateCursorline(first_line_ + yx.line - core_top_left_.line); 177 | } 178 | } 179 | 180 | protected: 181 | void _render(uint32_t orig_cursorline_y); 182 | 183 | Point _getCursorlinePosition(uint32_t cursor_line) { 184 | if ( is_reverse_ ) { 185 | return Point(core_top_left_.line - (cursor_line - first_line_), core_top_left_.col + indent_); 186 | } 187 | else { 188 | return Point(core_top_left_.line + cursor_line - first_line_, core_top_left_.col + indent_); 189 | } 190 | } 191 | 192 | void _renderCursorline(uint32_t cursor_line); 193 | void _updateCursorline(uint32_t new_cursorline); 194 | 195 | virtual void _drawIndicator(uint32_t cursor_line_y) {} 196 | virtual void _clearIndicator(uint32_t cursor_line_y) {} 197 | protected: 198 | Point top_left_; 199 | Point bottom_right_; 200 | Point core_top_left_; // if is_reverse_, core_top_left_ is actually core bottom left 201 | uint32_t height_; 202 | uint32_t width_; 203 | uint32_t core_height_; 204 | uint32_t core_width_; 205 | std::vector buffer_; 206 | Generator generator_; 207 | bool is_reverse_{ ConfigManager::getInstance().getConfigValue() }; 208 | uint32_t cursor_line_{ 0 }; // index of string under cursor line 209 | uint32_t first_line_{ 0 }; // index of string at the top of window 210 | uint32_t last_line_{ 0 }; 211 | uint32_t border_mask_{ 0 }; 212 | uint32_t indent_{ ConfigManager::getInstance().getConfigValue() }; 213 | const Colorscheme& cs_; 214 | }; 215 | 216 | class MainWindow : public Window 217 | { 218 | public: 219 | MainWindow(const Point& tl, const Point& br, const Colorscheme& cs): Window(tl, br, cs) { 220 | _setCmdline(); 221 | } 222 | 223 | void reset(const Point& tl, const Point& br) { 224 | _init(tl, br); 225 | _setCmdline(); 226 | } 227 | 228 | void display() const { 229 | drawBorder(); 230 | _displayCmdline(); 231 | } 232 | 233 | void updateLineInfo(uint32_t result_size, uint32_t total_size); 234 | void updateLineInfo() { 235 | updateLineInfo(result_size_, total_size_); 236 | } 237 | 238 | void updateCmdline(const std::string& pattern, uint32_t cursor_pos); 239 | void updateCmdline(const std::string& pattern) { 240 | updateCmdline(pattern, cursor_pos_); 241 | } 242 | 243 | void showFlag(bool show) { 244 | auto flag_col = flag_col_.load(std::memory_order_relaxed); 245 | if ( flag_col >= core_top_left_.col + core_width_ ) { 246 | return; 247 | } 248 | 249 | const std::string flag[] = { "◐", "◒", "◑", "◓" }; 250 | if ( show ) { 251 | static uint32_t idx = 0; 252 | Tty::getInstance().addString(line_info_.line, 253 | flag_col, 254 | flag[(idx++) & 3], 255 | cs_.getColor(HighlightGroup::Flag)); 256 | } 257 | else { 258 | Tty::getInstance().addString(line_info_.line, 259 | flag_col, 260 | std::string(2, ' '), 261 | cs_.getColor(HighlightGroup::Normal)); 262 | Tty::getInstance().restoreCursorPosition(); 263 | show_flag_.store(false, std::memory_order_relaxed); 264 | } 265 | } 266 | 267 | void redrawPrompt(bool normal_mode) { 268 | if ( normal_mode ) { 269 | Tty::getInstance().addString(cmdline_y_, core_top_left_.col, prompt_, 270 | cs_.getColor(HighlightGroup::NormalMode)); 271 | } 272 | else { 273 | Tty::getInstance().addString(cmdline_y_, core_top_left_.col, prompt_, 274 | cs_.getColor(HighlightGroup::Prompt)); 275 | } 276 | Tty::getInstance().restoreCursorPosition(); 277 | } 278 | 279 | void drawBorder() const; 280 | 281 | private: 282 | 283 | void _setCmdline(); 284 | 285 | void _displayCmdline() const { 286 | Tty::getInstance().addString(cmdline_y_, core_top_left_.col, prompt_, 287 | cs_.getColor(HighlightGroup::Prompt)); 288 | Tty::getInstance().saveCursorPosition(); 289 | } 290 | 291 | void _drawIndicator(uint32_t cursor_line_y) override { 292 | Tty::getInstance().addString(cursor_line_y, core_top_left_.col, indicator_, 293 | cs_.getColor(HighlightGroup::Indicator)); 294 | } 295 | 296 | void _clearIndicator(uint32_t cursor_line_y) override { 297 | Tty::getInstance().addString(cursor_line_y, core_top_left_.col, std::string(indent_, ' '), 298 | cs_.getColor(HighlightGroup::Normal)); 299 | } 300 | private: 301 | std::string prompt_{ "> " }; 302 | std::string indicator_{ "➤" }; 303 | uint32_t cmdline_y_{ 1 }; 304 | Point line_info_; // line_info_.col is the end position of the string if on the cmdline, 305 | // otherwise, it is the beginning position of the string 306 | uint32_t result_size_{ 0 }; 307 | uint32_t total_size_{ 0 }; 308 | uint32_t cursor_pos_{ 0 }; 309 | static constexpr uint32_t right_boundary_{ 50 }; 310 | 311 | std::atomic flag_col_{ 1 }; 312 | std::atomic show_flag_{ true }; 313 | }; 314 | 315 | class PreviewWindow : public Window 316 | { 317 | public: 318 | PreviewWindow(const Point& tl, const Point& br, const Colorscheme& cs): Window(tl, br, cs) { 319 | is_reverse_ = false; 320 | indent_ = 0; 321 | } 322 | 323 | protected: 324 | 325 | }; 326 | 327 | class Cleanup final : public Singleton 328 | { 329 | friend Singleton; 330 | public: 331 | ~Cleanup(); 332 | void doWork(bool once=true); 333 | void saveCursorPosition(const Point& orig_cursor_pos) { 334 | orig_cursor_pos_ = orig_cursor_pos; 335 | } 336 | 337 | void setTopBorder() { 338 | top_border_ = true; 339 | } 340 | 341 | void saveCoreHeight(uint32_t height) { 342 | core_height_ = height; 343 | } 344 | 345 | void setFullScreen() { 346 | is_full_screen_ = true; 347 | } 348 | 349 | private: 350 | bool done_{ false }; 351 | bool is_full_screen_{ false }; 352 | bool top_border_{ false }; 353 | Point orig_cursor_pos_{ 0, 0 }; 354 | uint32_t core_height_{ 1 }; 355 | }; 356 | 357 | class Tui final 358 | { 359 | public: 360 | Tui(); 361 | ~Tui(); 362 | 363 | void init(bool resume); 364 | 365 | void setMargin(Point& tl, Point& br, uint32_t height); 366 | 367 | template 368 | void setMainWindow(Args&&... args) { 369 | p_main_win_.reset(new MainWindow(std::forward(args)...)); 370 | } 371 | 372 | template 373 | void setPreviewWindow(Args&&... args) { 374 | p_preview_win_.reset(new PreviewWindow(std::forward(args)...)); 375 | } 376 | 377 | template 378 | struct WindowType{}; 379 | 380 | const std::unique_ptr& getWindow(WindowType) const noexcept { 381 | return p_main_win_; 382 | } 383 | 384 | const std::unique_ptr& getWindow(WindowType) const noexcept { 385 | return p_preview_win_; 386 | } 387 | 388 | Tty& getTty() const noexcept { 389 | return tty_; 390 | } 391 | 392 | const std::string& getColor(HighlightGroup group) const noexcept { 393 | return cs_.getColor(group); 394 | } 395 | 396 | template 397 | void setBuffer(Generator&& generator) { 398 | auto& w = getWindow(WindowType()); 399 | if ( w ) { 400 | w->setBuffer(std::move(generator)); 401 | } 402 | } 403 | 404 | template 405 | void setBuffer() { 406 | auto& w = getWindow(WindowType()); 407 | if ( w ) { 408 | w->setBuffer(); 409 | } 410 | } 411 | 412 | template 413 | void scrollUp() { 414 | auto& w = getWindow(WindowType()); 415 | if ( w ) { 416 | w->scrollUp(); 417 | } 418 | } 419 | 420 | template 421 | void scrollDown() { 422 | auto& w = getWindow(WindowType()); 423 | if ( w ) { 424 | w->scrollDown(); 425 | } 426 | } 427 | 428 | template 429 | void pageUp() { 430 | auto& w = getWindow(WindowType()); 431 | if ( w ) { 432 | w->pageUp(); 433 | } 434 | } 435 | 436 | template 437 | void pageDown() { 438 | auto& w = getWindow(WindowType()); 439 | if ( w ) { 440 | w->pageDown(); 441 | } 442 | } 443 | 444 | void singleClick(const Point& yx) { 445 | if ( p_main_win_->inCoreWindow(yx) ) { 446 | p_main_win_->singleClick(yx); 447 | } 448 | } 449 | 450 | void wheelDown(const Point& yx) { 451 | if ( p_main_win_->inWindow(yx) ) { 452 | p_main_win_->scrollDown(); 453 | } 454 | } 455 | 456 | void wheelUp(const Point& yx) { 457 | if ( p_main_win_->inWindow(yx) ) { 458 | p_main_win_->scrollUp(); 459 | } 460 | } 461 | 462 | template 463 | uint32_t getCoreHeight() const noexcept { 464 | auto& w = getWindow(WindowType()); 465 | if ( w ) { 466 | return w->getCoreHeight(); 467 | } 468 | else { 469 | return 0; 470 | } 471 | } 472 | 473 | template 474 | uint32_t getCoreWidth() const noexcept { 475 | auto& w = getWindow(WindowType()); 476 | if ( w ) { 477 | return w->getCoreWidth(); 478 | } 479 | else { 480 | return 0; 481 | } 482 | } 483 | 484 | void updateCmdline(const std::string& pattern, uint32_t cursor_pos) { 485 | p_main_win_->updateCmdline(pattern, cursor_pos); 486 | } 487 | 488 | void updateCmdline(const std::string& pattern) { 489 | p_main_win_->updateCmdline(pattern); 490 | } 491 | 492 | void updateLineInfo(uint32_t result_size, uint32_t total_size) { 493 | p_main_win_->updateLineInfo(result_size, total_size); 494 | } 495 | 496 | void updateLineInfo() { 497 | p_main_win_->updateLineInfo(); 498 | } 499 | 500 | void showFlag(bool show) { 501 | p_main_win_->showFlag(show); 502 | } 503 | 504 | void redrawPrompt(bool normal_mode) { 505 | p_main_win_->redrawPrompt(normal_mode); 506 | } 507 | 508 | void drawBorder() const { 509 | p_main_win_->drawBorder(); 510 | } 511 | 512 | void setAccept() { 513 | accept_ = true; 514 | } 515 | 516 | private: 517 | Tty& tty_; 518 | const ConfigManager& cfg_; 519 | Cleanup& cleanup_; 520 | Colorscheme cs_; 521 | std::unique_ptr p_main_win_; 522 | std::unique_ptr p_preview_win_; 523 | bool accept_{ false }; 524 | 525 | }; 526 | 527 | } // end namespace leaf 528 | -------------------------------------------------------------------------------- /src/argParser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "argParser.h" 6 | #include "tty.h" 7 | 8 | 9 | namespace leaf 10 | { 11 | 12 | ArgumentParser::ArgumentParser() 13 | : alias_{ 14 | { "-r", "--reverse" } 15 | }, category_name_ { 16 | { ArgCategory::Layout, "Layout" }, 17 | { ArgCategory::Search, "Search" }, 18 | }, args_{ 19 | { "--reverse", 20 | { 21 | ArgCategory::Layout, 22 | "-r", 23 | ConfigType::Reverse, 24 | "0", 25 | "", 26 | "Display from the bottom of the screen to top.", 27 | } 28 | }, 29 | { "--height", 30 | { 31 | ArgCategory::Layout, 32 | "", 33 | ConfigType::Height, 34 | "1", 35 | "N[%]", 36 | "Display window with the given height N[%] instead of using fullscreen." 37 | } 38 | }, 39 | { "--border", 40 | { 41 | ArgCategory::Layout, 42 | "", 43 | ConfigType::Border, 44 | "?", 45 | "BORDER[:STYLE]", 46 | "Display the T(top), R(right), B(bottom), L(left) border. BORDER is a string " 47 | "combined by 'T', 'R', 'B' or 'L'. If BORDER is not specified, display borders all around. " 48 | "STYLE is 1, 2, 3 and 4 which denotes \"[─,│,─,│,╭,╮,╯,╰]\", \"[─,│,─,│,┌,┐,┘,└]\", " 49 | "\"[━,┃,━,┃,┏,┓,┛,┗]\", \"[═,║,═,║,╔,╗,╝,╚]\" respectively." 50 | } 51 | }, 52 | { "--border-chars", 53 | { 54 | ArgCategory::Layout, 55 | "", 56 | ConfigType::BorderChars, 57 | "1", 58 | "CHARS", 59 | "Specify the character to use for the top/right/bottom/left border, " 60 | "followed by the character to use for the topleft/topright/botright/botleft corner. " 61 | "Default value is \"[─,│,─,│,╭,╮,╯,╰]\"" 62 | } 63 | }, 64 | { "--margin", 65 | { 66 | ArgCategory::Layout, 67 | "", 68 | ConfigType::Margin, 69 | "1", 70 | "MARGIN", 71 | "Specify the width of the top/right/bottom/left margin. MARGIN is a list of integers or percentages. " 72 | "The list can have 1, 2 and 4 elements. For example, [10]: margins all around are 10. " 73 | "[10,20%]: top and bottom margin are 10, left and right margin are 20% of the terminal width." 74 | } 75 | }, 76 | { "--sort-preference", 77 | { 78 | ArgCategory::Search, 79 | "", 80 | ConfigType::SortPreference, 81 | "1", 82 | "PREFERENCE", 83 | "Specify the sort preference to apply, value can be [begin|end]. (default: end)" 84 | } 85 | }, 86 | } 87 | { 88 | 89 | } 90 | 91 | void ArgumentParser::printHelp() { 92 | std::string help("usage: yy [options]\n"); 93 | help.reserve(1024); 94 | std::vector> arg_groups(static_cast(ArgCategory::MaxNum)); 95 | 96 | for ( auto& arg : args_ ) { 97 | auto category = static_cast(arg.second.category); 98 | arg_groups[category].emplace(arg.first, &arg.second); 99 | } 100 | 101 | uint32_t name_column_width = 32; 102 | for ( auto& group : arg_groups ) { 103 | bool header = false; 104 | for ( auto& g : group ) { 105 | auto& arg = *g.second; 106 | if ( header == false ) { 107 | header = true; 108 | help += "\n " + category_name_[arg.category] + "\n"; 109 | } 110 | 111 | std::string arg_name = g.first; 112 | if ( !arg.alias.empty() ) { 113 | arg_name = arg.alias + ", " + arg_name; 114 | } 115 | 116 | if ( arg.nargs == "1" ) { 117 | arg_name += "=<" + arg.metavar + ">"; 118 | } 119 | else if ( arg.nargs == "?" ) { 120 | arg_name += " [" + arg.metavar + "]"; 121 | } 122 | else if ( arg.nargs == "*" ) { 123 | arg_name += " [" + arg.metavar + "]..."; 124 | } 125 | else if ( arg.nargs == "+" ) { 126 | arg_name += " <" + arg.metavar + ">..."; 127 | } 128 | 129 | auto name_column = utils::strFormat<64>("%4c%-28s", ' ', arg_name.c_str()); 130 | help += name_column; 131 | if ( name_column.length() > name_column_width 132 | || name_column[name_column_width-1] != ' ' ) { 133 | help += "\n" + std::string(name_column_width, ' '); 134 | } 135 | 136 | uint32_t width = 64; 137 | uint32_t start = 0; 138 | uint32_t end = start + width; 139 | while ( end < arg.help.length() ) { 140 | if ( start > 0 ) { 141 | help += std::string(name_column_width, ' '); 142 | } 143 | 144 | while ( end > start && arg.help[end] != ' ' && arg.help[end] != '\t' ) { 145 | --end; 146 | } 147 | 148 | if ( end == start ) { 149 | end = start + width; 150 | } 151 | 152 | help += arg.help.substr(start, end - start) + "\n"; 153 | 154 | start = end; 155 | while ( start < arg.help.length() && (arg.help[start] == ' ' || arg.help[start] == '\t') ) { 156 | ++start; 157 | } 158 | end = start + width; 159 | } 160 | 161 | if ( start < arg.help.length() ) { 162 | if ( start > 0 ) { 163 | help += std::string(name_column_width, ' '); 164 | } 165 | help += arg.help.substr(start) + "\n"; 166 | } 167 | } 168 | } 169 | 170 | printf("%s\nalias: leaf\n", help.c_str()); 171 | } 172 | 173 | void ArgumentParser::parseArgs(int argc, char* argv[], std::vector>& cfg) { 174 | for ( int i = 1; i < argc; ++i ) { 175 | if ( strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0 ) { 176 | printHelp(); 177 | std::exit(0); 178 | } 179 | } 180 | 181 | for ( int i = 1; i < argc; ++i ) { 182 | auto key = std::string(argv[i]); 183 | auto eq = strchr(argv[i], '='); 184 | std::vector val_list; 185 | if ( eq != nullptr ) { 186 | key = key.substr(0, eq - argv[i]); 187 | val_list.emplace_back(eq + 1); 188 | } 189 | 190 | auto iter = args_.find(key); 191 | if ( iter == args_.end() ) { 192 | auto it = alias_.find(key); 193 | if ( it == alias_.end() ) { 194 | appendError("unknown option: %s", key.c_str()); 195 | std::exit(EXIT_FAILURE); 196 | } 197 | else { 198 | iter = args_.find(it->second); 199 | if ( iter == args_.end() ) { 200 | appendError("unknown option: %s", key.c_str()); 201 | std::exit(EXIT_FAILURE); 202 | } 203 | } 204 | } 205 | 206 | auto& argument = iter->second; 207 | if ( eq != nullptr ) { 208 | if ( argument.nargs == "0" ) { 209 | appendError("unknown option: %s", argv[i]); 210 | std::exit(EXIT_FAILURE); 211 | } 212 | else if ( val_list[0].empty() ) { 213 | appendError("no value specified for: %s", argv[i]); 214 | std::exit(EXIT_FAILURE); 215 | } 216 | } 217 | else { 218 | if ( argument.nargs == "*" || argument.nargs == "+" ) { 219 | int j = i + 1; 220 | for ( ; j < argc; ++j ) { 221 | if ( argv[j][0] != '-' ) { 222 | val_list.emplace_back(argv[j]); 223 | } 224 | else { 225 | break; 226 | } 227 | } 228 | i = j - 1; 229 | 230 | if ( argument.nargs == "+" && val_list.empty() ) { 231 | appendError("no value specified for: %s", key.c_str()); 232 | std::exit(EXIT_FAILURE); 233 | } 234 | } 235 | else if ( argument.nargs == "?" ) { 236 | int j = i + 1; 237 | if ( j < argc && argv[j][0] != '-' ) { 238 | val_list.emplace_back(argv[j]); 239 | i = j; 240 | } 241 | } 242 | else { 243 | uint32_t n = std::stoi(argument.nargs); 244 | int j = i + 1; 245 | for ( ; j < argc && j <= static_cast(i + n); ++j ) { 246 | if ( argv[j][0] != '-' ) { 247 | val_list.emplace_back(argv[j]); 248 | } 249 | else { 250 | break; 251 | } 252 | } 253 | i = j - 1; 254 | 255 | if ( val_list.size() < n ) { 256 | appendError("not enough value specified for: %s", key.c_str()); 257 | std::exit(EXIT_FAILURE); 258 | } 259 | } 260 | } 261 | 262 | switch ( argument.cfg_type ) 263 | { 264 | case ConfigType::Reverse: 265 | SetConfigValue(cfg, Reverse, true); 266 | break; 267 | case ConfigType::Height: 268 | { 269 | uint32_t value = 0; 270 | auto len = val_list[0].size(); 271 | if ( val_list[0][len - 1] == '%' ) { 272 | uint32_t win_height = 0; 273 | uint32_t win_width = 0; 274 | Tty::getInstance().getWindowSize(win_height, win_width); 275 | try { 276 | value = win_height * std::stoi(val_list[0].substr(0, len - 1)) / 100; 277 | } 278 | catch(...) { 279 | appendError("invalid value: %s", val_list[0].c_str()); 280 | std::exit(EXIT_FAILURE); 281 | } 282 | } 283 | else { 284 | try { 285 | value = std::stoi(val_list[0]); 286 | } 287 | catch(...) { 288 | appendError("invalid value: %s", val_list[0].c_str()); 289 | std::exit(EXIT_FAILURE); 290 | } 291 | } 292 | 293 | SetConfigValue(cfg, Height, value); 294 | break; 295 | } 296 | case ConfigType::SortPreference: 297 | if ( val_list[0] == "begin" ) { 298 | SetConfigValue(cfg, SortPreference, Preference::Begin); 299 | } 300 | else if ( val_list[0] == "end" ) { 301 | SetConfigValue(cfg, SortPreference, Preference::End); 302 | } 303 | else { 304 | appendError("invalid value: %s for %s", val_list[0].c_str(), key.c_str()); 305 | std::exit(EXIT_FAILURE); 306 | } 307 | break; 308 | case ConfigType::Border: 309 | if ( !val_list.empty() ) { 310 | auto pos = val_list[0].find(':'); 311 | auto val = val_list[0].substr(0, pos); 312 | if ( val.empty() ) { 313 | SetConfigValue(cfg, Border, "TRBL"); 314 | } 315 | else { 316 | for ( auto c : val ) { 317 | if ( c != 'T' && c != 'R' && c != 'B' && c != 'L' ) { 318 | appendError("invalid value: %s for %s", val_list[0].c_str(), key.c_str()); 319 | std::exit(EXIT_FAILURE); 320 | } 321 | } 322 | 323 | SetConfigValue(cfg, Border, val); 324 | } 325 | 326 | if ( pos != std::string::npos ) { 327 | auto style = val_list[0].substr(pos + 1); 328 | if ( style.empty() || style == "1" ) { 329 | } 330 | else if ( style == "2" ) { 331 | SetConfigValue(cfg, BorderChars, 332 | std::vector({"─","│","─","│","┌","┐","┘","└"})); 333 | } 334 | else if ( style == "3" ) { 335 | SetConfigValue(cfg, BorderChars, 336 | std::vector({"━","┃","━","┃","┏","┓","┛","┗"})); 337 | } 338 | else if ( style == "4" ) { 339 | SetConfigValue(cfg, BorderChars, 340 | std::vector({"═","║","═","║","╔","╗","╝","╚"})); 341 | } 342 | else { 343 | appendError("invalid style: %s for %s", style.c_str(), key.c_str()); 344 | std::exit(EXIT_FAILURE); 345 | } 346 | } 347 | } 348 | else { 349 | SetConfigValue(cfg, Border, "TRBL"); 350 | } 351 | break; 352 | case ConfigType::BorderChars: 353 | { 354 | auto border_chars = parseList(key, val_list[0], [](const std::string& s) { return s; }); 355 | if ( border_chars.size() != 8 ) { 356 | appendError("invalid value: %s for %s", val_list[0].c_str(), key.c_str()); 357 | std::exit(EXIT_FAILURE); 358 | } 359 | 360 | SetConfigValue(cfg, BorderChars, std::move(border_chars)); 361 | break; 362 | } 363 | case ConfigType::Margin: 364 | { 365 | auto margins = parseList(key, val_list[0], [](const std::string& s) { return s; }); 366 | 367 | if ( margins.size() == 1 ) { 368 | for ( int k = 0; k < 3; ++k ) { 369 | margins.emplace_back(margins[0]); 370 | } 371 | } 372 | else if ( margins.size() == 2 ) { 373 | margins.resize(4); 374 | margins[2] = margins[0]; 375 | margins[3] = margins[1]; 376 | } 377 | else if ( margins.size() != 4 ) { 378 | appendError("invalid value: %s for %s", val_list[0].c_str(), key.c_str()); 379 | std::exit(EXIT_FAILURE); 380 | } 381 | 382 | std::vector int_margins; 383 | int_margins.reserve(4); 384 | 385 | uint32_t win_height = 0; 386 | uint32_t win_width = 0; 387 | for ( int k = 0; k < 4; ++k ) { 388 | auto& s = margins[k]; 389 | try { 390 | if ( s[s.size() - 1] == '%' ) { 391 | if ( win_height == 0 || win_width == 0 ) { 392 | Tty::getInstance().getWindowSize(win_height, win_width); 393 | } 394 | if ( (k & 1) == 0 ) { 395 | int_margins.emplace_back(win_height * std::stoi(s.substr(0, s.size() - 1)) / 100); 396 | } 397 | else { 398 | int_margins.emplace_back(win_width * std::stoi(s.substr(0, s.size() - 1)) / 100); 399 | } 400 | } 401 | else { 402 | int_margins.emplace_back(static_cast(std::stoi(s))); 403 | } 404 | } 405 | catch(...) { 406 | appendError("invalid value: %s", s.c_str()); 407 | std::exit(EXIT_FAILURE); 408 | } 409 | } 410 | 411 | SetConfigValue(cfg, Margin, std::move(int_margins)); 412 | break; 413 | } 414 | default: 415 | break; 416 | } 417 | } 418 | 419 | } 420 | 421 | } // end namespace leaf 422 | -------------------------------------------------------------------------------- /src/ringBuffer.h: -------------------------------------------------------------------------------- 1 | _Pragma("once"); 2 | 3 | #define NDEBUG 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef TEST_RINGBUFFER 14 | #include 15 | #endif 16 | 17 | namespace leaf 18 | { 19 | 20 | #if defined(__GNUC__) 21 | 22 | // if x is 1, result is undefined 23 | #define roundup_pow_of_two(x) (1 << ((uint32_t)((sizeof(unsigned int) << 3) - __builtin_clz(x-1)))) 24 | 25 | #elif defined(__clang__) 26 | 27 | #if __has_builtin(__builtin_clz) 28 | #define roundup_pow_of_two(x) (1 << ((uint32_t)((sizeof(unsigned int) << 3) - __builtin_clz(x-1)))) 29 | #endif 30 | 31 | #endif 32 | 33 | #if !defined(roundup_pow_of_two) 34 | uint32_t roundup_pow_of_two(uint32_t v) 35 | { 36 | v--; 37 | v |= v >> 1; 38 | v |= v >> 2; 39 | v |= v >> 4; 40 | v |= v >> 8; 41 | v |= v >> 16; 42 | v++; 43 | return v; 44 | } 45 | #endif 46 | 47 | #ifdef TEST_RINGBUFFER 48 | #define TestRingBufferLog(x) \ 49 | do { \ 50 | std::cout << x << std::endl; \ 51 | } while ( 0 ) 52 | 53 | #else 54 | #define TestRingBufferLog(x) 55 | #endif 56 | 57 | template 58 | class RingBuffer; 59 | 60 | 61 | template > 62 | class RingBufferIterator final 63 | { 64 | public: 65 | using self = RingBufferIterator; 66 | using reference = std::conditional_t::value, const T&, T&>; 67 | using pointer = std::conditional_t::value, const T*, T*>; 68 | using size_type = size_t; 69 | 70 | RingBufferIterator() = default; 71 | 72 | explicit RingBufferIterator(Container* buffer, size_type cur) 73 | : ring_buffer_(buffer), current_(cur) {} 74 | 75 | 76 | bool operator==(const self& x) const noexcept { 77 | return current_ == x.current_; 78 | } 79 | 80 | bool operator!=(const self& x) const noexcept { 81 | return current_ != x.current_; 82 | } 83 | 84 | bool operator==(std::nullptr_t) const noexcept { 85 | return ring_buffer_ == nullptr; 86 | } 87 | 88 | bool operator!=(std::nullptr_t) const noexcept { 89 | return ring_buffer_ != nullptr; 90 | } 91 | 92 | bool operator>(const self& x) const noexcept { 93 | return current_ > x.current_; 94 | } 95 | 96 | bool operator>=(const self& x) const noexcept { 97 | return current_ >= x.current_; 98 | } 99 | 100 | bool operator<(const self& x) const noexcept { 101 | return current_ < x.current_; 102 | } 103 | 104 | bool operator<=(const self& x) const noexcept { 105 | return current_ <= x.current_; 106 | } 107 | 108 | size_type operator-(const self& x) const noexcept { 109 | return (current_ + ring_buffer_->capacity_ - x.current_ ) & (ring_buffer_->capacity_ - 1); 110 | } 111 | 112 | reference operator*() const noexcept { 113 | return *(ring_buffer_->buffer_ + current_); 114 | } 115 | 116 | pointer operator->() const noexcept { 117 | return ring_buffer_->buffer_ + current_; 118 | } 119 | 120 | self& operator++() noexcept { 121 | current_ = (current_ + 1) & (ring_buffer_->capacity_ - 1); 122 | return *this; 123 | } 124 | 125 | self operator++(int) noexcept { 126 | self tmp = *this; 127 | ++*this; 128 | return tmp; 129 | } 130 | 131 | self operator+(size_type n) const noexcept { 132 | return self(ring_buffer_, (current_ + n) & (ring_buffer_->capacity_ - 1)); 133 | } 134 | 135 | self operator-(size_type n) const noexcept { 136 | n = n & (ring_buffer_->capacity_ - 1); 137 | return self(ring_buffer_, (current_ + ring_buffer_->capacity_ - n) & (ring_buffer_->capacity_ - 1)); 138 | } 139 | 140 | size_type current() const noexcept { 141 | return current_; 142 | } 143 | 144 | const T* buffer() const noexcept { 145 | return ring_buffer_->buffer_; 146 | } 147 | 148 | pointer data() const noexcept { 149 | return ring_buffer_->buffer_ + current_; 150 | } 151 | 152 | size_type capacity() const noexcept { 153 | return ring_buffer_->capacity_; 154 | } 155 | 156 | private: 157 | Container* ring_buffer_ { nullptr }; 158 | size_type current_; 159 | }; 160 | 161 | 162 | template 163 | class RingBuffer 164 | { 165 | public: 166 | using iterator = RingBufferIterator; 167 | using const_iterator = RingBufferIterator>; 168 | using reference = T&; 169 | using const_reference = const T&; 170 | using size_type = size_t; 171 | 172 | friend iterator; 173 | friend const_iterator; 174 | 175 | explicit RingBuffer(size_type count=0) { 176 | static_assert(std::is_trivial::value && std::is_standard_layout::value, 177 | "Must be POD!"); 178 | if ( count > 0 ) { 179 | capacity_ = roundup_pow_of_two(count + 1); 180 | tail_ = count; 181 | buffer_ = new T[capacity_]; 182 | } 183 | } 184 | 185 | RingBuffer(std::initializer_list il) { 186 | static_assert(std::is_trivial::value && std::is_standard_layout::value, 187 | "Must be POD!"); 188 | if ( il.size() > 0 ) { 189 | capacity_ = roundup_pow_of_two(il.size() + 1); 190 | buffer_ = new T[capacity_]; 191 | for (auto& x : il) { 192 | buffer_[tail_++] = x; 193 | } 194 | } 195 | } 196 | 197 | RingBuffer(const RingBuffer& other) 198 | : capacity_(other.capacity_), head_(other.head_), 199 | tail_(other.tail_) { 200 | if ( other.buffer_ == nullptr ) { 201 | return; 202 | } 203 | buffer_ = new T[capacity_]; 204 | if ( tail_ > head_ ) { 205 | memcpy(buffer_ + head_, other.buffer_ + head_, sizeof(T) * (tail_ - head_)); 206 | } 207 | else if ( tail_ < head_ ) { 208 | memcpy(buffer_ + head_, other.buffer_ + head_, sizeof(T) * (capacity_ - head_)); 209 | memcpy(buffer_, other.buffer_, sizeof(T) * tail_); 210 | } 211 | } 212 | 213 | RingBuffer(RingBuffer&& other) noexcept 214 | : RingBuffer() { 215 | this->swap(other); 216 | } 217 | 218 | RingBuffer& operator=(RingBuffer other) noexcept { 219 | this->swap(other); 220 | return *this; 221 | } 222 | 223 | ~RingBuffer() { 224 | if ( buffer_ != nullptr ) { 225 | delete [] buffer_; 226 | } 227 | } 228 | 229 | size_t size() const noexcept { 230 | return (tail_ - head_ + capacity_) & (capacity_ - 1); 231 | } 232 | 233 | bool empty() const noexcept { 234 | return head_ == tail_; 235 | } 236 | 237 | void clear() noexcept { 238 | //if ( buffer_ != nullptr ) { 239 | // delete [] buffer_; 240 | // buffer_ = nullptr; 241 | //} 242 | //capacity_ = 0; 243 | head_ = 0; 244 | tail_ = 0; 245 | } 246 | 247 | void reserve(size_type n) { 248 | if ( n <= capacity_ ) { 249 | return; 250 | } 251 | 252 | auto new_capacity = roundup_pow_of_two(n + 1); 253 | auto tmp = buffer_; 254 | buffer_ = new T[new_capacity]; 255 | if ( tail_ >= head_ ) { 256 | memcpy(buffer_, tmp + head_, sizeof(T) * (tail_ - head_)); 257 | } 258 | else { 259 | auto n = capacity_ - head_; 260 | memcpy(buffer_ + (new_capacity - n), tmp + head_, sizeof(T) * n); 261 | memcpy(buffer_, tmp, sizeof(T) * tail_); 262 | head_ = new_capacity - n; 263 | } 264 | capacity_ = new_capacity; 265 | delete [] tmp; 266 | } 267 | 268 | iterator begin() noexcept { 269 | return iterator(this, head_); 270 | } 271 | 272 | const_iterator begin() const noexcept { 273 | return const_iterator(this, head_); 274 | } 275 | 276 | const_iterator cbegin() const noexcept { 277 | return const_iterator(this, head_); 278 | } 279 | 280 | iterator end() noexcept { 281 | return iterator(this, tail_); 282 | } 283 | 284 | const_iterator end() const noexcept { 285 | return const_iterator(this, tail_); 286 | } 287 | 288 | const_iterator cend() const noexcept { 289 | return const_iterator(this, tail_); 290 | } 291 | 292 | reference operator[](size_type n) { 293 | assert(n >= head_ && n < tail_ || (head_ > tail_ 294 | && (n >= head_ && n < capacity_ || n < tail_))); 295 | return buffer_[n]; 296 | } 297 | 298 | const_reference operator[]( size_type n ) const { 299 | assert(n >= head_ && n < tail_ || (head_ > tail_ 300 | && (n >= head_ && n < capacity_ || n < tail_))); 301 | return buffer_[n]; 302 | } 303 | 304 | void push_front(const T& data) { 305 | _expand_space(); 306 | head_ = (head_ + capacity_ - 1) & (capacity_ - 1); 307 | buffer_[head_] = data; 308 | } 309 | 310 | void push_front(T&& data) { 311 | _expand_space(); 312 | head_ = (head_ + capacity_ - 1) & (capacity_ - 1); 313 | buffer_[head_] = std::move(data); 314 | } 315 | 316 | void push_back(const T& data) { 317 | _expand_space(); 318 | buffer_[tail_] = data; 319 | tail_ = (tail_ + 1) & (capacity_ - 1); 320 | } 321 | 322 | void push_back(T&& data) { 323 | _expand_space(); 324 | buffer_[tail_] = std::move(data); 325 | tail_ = (tail_ + 1) & (capacity_ - 1); 326 | } 327 | 328 | template 329 | void push_front(InputIterator first, InputIterator last) { 330 | if ( first == last ) { 331 | return; 332 | } 333 | auto len = last - first; 334 | auto orig_size = size(); 335 | if ( orig_size + len >= capacity_ ) { // no enough space 336 | TestRingBufferLog("0000000000"); 337 | bool is_self = first.buffer() == buffer_ ; 338 | auto offset = is_self ? first - InputIterator(this, head_) : 0; 339 | auto new_capacity = roundup_pow_of_two(orig_size + len + 1); 340 | auto tmp = buffer_; 341 | buffer_ = new T[new_capacity]; 342 | if ( tail_ >= head_ ) { 343 | memcpy(buffer_, tmp + head_, sizeof(T) * (tail_ - head_)); 344 | } 345 | else { 346 | auto n = capacity_ - head_; 347 | memcpy(buffer_, tmp + head_, sizeof(T) * n); 348 | memcpy(buffer_ + n, tmp, sizeof(T) * tail_); 349 | } 350 | capacity_ = new_capacity; 351 | head_ = 0; 352 | tail_ = orig_size; 353 | delete [] tmp; 354 | 355 | if ( is_self ) { 356 | first = InputIterator(this, head_) + offset; 357 | last = first + len; 358 | } 359 | } 360 | 361 | if ( tail_ > head_ ) { 362 | if ( last > first ) { // case 1 363 | /* n len 364 | // / \ / \ 365 | // |...._________.....| |....________......| 366 | // ^ ^ ^ ^ 367 | // | | | | 368 | // head_ tail_ first last 369 | */ 370 | auto n = head_; 371 | if ( n >= len ) { 372 | TestRingBufferLog("1111111111"); 373 | head_ -= len; 374 | memcpy(buffer_ + head_, first.data(), sizeof(T) * len); 375 | } 376 | else { 377 | TestRingBufferLog("2222222222"); 378 | memcpy(buffer_, (last-n).data(), sizeof(T) * n); 379 | head_ = capacity_ - (len - n); 380 | memcpy(buffer_ + head_, first.data(), sizeof(T) * (len - n)); 381 | } 382 | } 383 | else { 384 | /* n len2 len1 385 | // / \ /\ / \ 386 | // |...._________.....| |__........._______| 387 | // ^ ^ ^ ^ 388 | // | | | | 389 | // head_ tail_ last first 390 | */ 391 | auto n = head_; 392 | auto len1 = first.capacity() - first.current(); 393 | auto len2 = last.current(); 394 | if ( n >= len2 ) { 395 | n -= len2; 396 | memcpy(buffer_ + n, last.buffer(), sizeof(T) * len2); 397 | head_ = n; 398 | 399 | // similar as case 1 400 | if ( n >= len1 ) { 401 | TestRingBufferLog("3333333333"); 402 | head_ -= len1; 403 | memcpy(buffer_ + head_, first.data(), sizeof(T) * len1); 404 | } 405 | else { 406 | TestRingBufferLog("4444444444"); 407 | auto k = len1 - n; 408 | memcpy(buffer_, (first + k).data(), sizeof(T) * n); 409 | head_ = capacity_ - k; 410 | memcpy(buffer_ + head_, first.data(), sizeof(T) * k); 411 | } 412 | } 413 | else { 414 | TestRingBufferLog("5555555555"); 415 | memcpy(buffer_, last.buffer() + (len2 - n), sizeof(T) * n); 416 | head_ = capacity_ - (len2 - n); 417 | memcpy(buffer_ + head_, last.buffer(), sizeof(T) * (len2 - n)); 418 | head_ -= len1; 419 | memcpy(buffer_ + head_, first.data(), sizeof(T) * len1); 420 | } 421 | } 422 | } 423 | else if ( tail_ < head_ ) { 424 | if ( last > first ) { 425 | TestRingBufferLog("6666666666"); 426 | /* n len 427 | // / \ / \ 428 | // |__........._______| |.._________.......| 429 | // ^ ^ ^ ^ 430 | // | | | | 431 | // tail_ head_ first last 432 | */ 433 | head_ -= len; 434 | memcpy(buffer_ + head_, first.data(), sizeof(T) * len); 435 | } 436 | else { 437 | TestRingBufferLog("7777777777"); 438 | /* n len2 len1 439 | // / \ /\ / \ 440 | // |__........._______| |__........._______| 441 | // ^ ^ ^ ^ 442 | // | | | | 443 | // tail_ head_ last first 444 | */ 445 | auto len2 = last.current(); 446 | head_ -= len2; 447 | memcpy(buffer_ + head_, last.buffer(), sizeof(T) * len2); 448 | auto len1 = first.capacity() - first.current(); 449 | head_ -= len1; 450 | memcpy(buffer_ + head_, first.data(), sizeof(T) * len1); 451 | } 452 | } 453 | else { // empty 454 | if ( last > first ) { 455 | TestRingBufferLog("8888888888"); 456 | memcpy(buffer_, first.data(), sizeof(T) * len); 457 | } 458 | else { 459 | TestRingBufferLog("9999999999"); 460 | auto len1 = first.capacity() - first.current(); 461 | memcpy(buffer_, first.data(), sizeof(T) * len1); 462 | memcpy(buffer_ + len1, last.buffer(), sizeof(T) * last.current()); 463 | } 464 | head_ = 0; 465 | tail_ = len; 466 | } 467 | } 468 | 469 | template 470 | void push_back(InputIterator first, InputIterator last) { 471 | if ( first == last ) { 472 | return; 473 | } 474 | auto len = last - first; 475 | auto orig_size = size(); 476 | if ( orig_size + len >= capacity_ ) { // no enough space 477 | TestRingBufferLog("0000000000"); 478 | bool is_self = first.buffer() == buffer_ ; 479 | auto offset = is_self ? first - InputIterator(this, head_) : 0; 480 | auto new_capacity = roundup_pow_of_two(orig_size + len + 1); 481 | auto tmp = buffer_; 482 | buffer_ = new T[new_capacity]; 483 | if ( tail_ >= head_ ) { 484 | memcpy(buffer_, tmp + head_, sizeof(T) * (tail_ - head_)); 485 | } 486 | else { 487 | auto n = capacity_ - head_; 488 | memcpy(buffer_, tmp + head_, sizeof(T) * n); 489 | memcpy(buffer_ + n, tmp, sizeof(T) * tail_); 490 | } 491 | capacity_ = new_capacity; 492 | head_ = 0; 493 | tail_ = orig_size; 494 | delete [] tmp; 495 | 496 | if ( is_self ) { 497 | first = InputIterator(this, head_) + offset; 498 | last = first + len; 499 | } 500 | } 501 | 502 | if ( tail_ > head_ ) { 503 | if ( last > first ) { // case 1 504 | /* n len 505 | // / \ / \ 506 | // |.._________.......| |.._________.......| 507 | // ^ ^ ^ ^ 508 | // | | | | 509 | // head_ tail_ first last 510 | */ 511 | auto n = capacity_ - tail_; 512 | if ( n >= len ) { 513 | TestRingBufferLog("1111111111"); 514 | memcpy(buffer_ + tail_, first.data(), sizeof(T) * len); 515 | tail_ = (tail_ + len) & (capacity_ - 1); 516 | } 517 | else { 518 | TestRingBufferLog("2222222222"); 519 | memcpy(buffer_ + tail_, first.data(), sizeof(T) * n); 520 | tail_ = len - n; 521 | memcpy(buffer_, (first + n).data(), sizeof(T) * tail_); 522 | } 523 | } 524 | else { 525 | /* n len2 len1 526 | // / \ /\ / \ 527 | // |.._________.......| |__........._______| 528 | // ^ ^ ^ ^ 529 | // | | | | 530 | // head_ tail_ last first 531 | */ 532 | auto n = capacity_ - tail_; 533 | auto len1 = first.capacity() - first.current(); 534 | auto len2 = last.current(); 535 | if ( n >= len1 ) { 536 | memcpy(buffer_ + tail_, first.data(), sizeof(T) * len1); 537 | tail_ += len1; 538 | n = capacity_ - tail_; 539 | 540 | // same as case 1 541 | if ( n >= len2 ) { 542 | TestRingBufferLog("3333333333"); 543 | memcpy(buffer_ + tail_, last.buffer(), sizeof(T) * len2); 544 | tail_ = (tail_ + len2) & (capacity_ - 1); 545 | } 546 | else { 547 | TestRingBufferLog("4444444444"); 548 | memcpy(buffer_ + tail_, last.buffer(), sizeof(T) * n); 549 | tail_ = len2 - n; 550 | memcpy(buffer_, last.buffer() + n, sizeof(T) * tail_); 551 | } 552 | } 553 | else { 554 | TestRingBufferLog("5555555555"); 555 | memcpy(buffer_ + tail_, first.data(), sizeof(T) * n); 556 | tail_ = len1 - n; 557 | memcpy(buffer_, (first + n).data(), sizeof(T) * tail_); 558 | memcpy(buffer_ + tail_, last.buffer(), sizeof(T) * len2); 559 | tail_ += len2; 560 | } 561 | } 562 | } 563 | else if ( tail_ < head_ ) { 564 | if ( last > first ) { 565 | TestRingBufferLog("6666666666"); 566 | /* n len 567 | // / \ / \ 568 | // |__........._______| |.._________.......| 569 | // ^ ^ ^ ^ 570 | // | | | | 571 | // tail_ head_ first last 572 | */ 573 | memcpy(buffer_ + tail_, first.data(), sizeof(T) * len); 574 | tail_ += len; 575 | } 576 | else { 577 | TestRingBufferLog("7777777777"); 578 | /* n len2 len1 579 | // / \ /\ / \ 580 | // |__........._______| |__........._______| 581 | // ^ ^ ^ ^ 582 | // | | | | 583 | // tail_ head_ last first 584 | */ 585 | auto len1 = first.capacity() - first.current(); 586 | memcpy(buffer_ + tail_, first.data(), sizeof(T) * len1); 587 | tail_ += len1; 588 | auto len2 = last.current(); 589 | memcpy(buffer_ + tail_, last.buffer(), sizeof(T) * len2); 590 | tail_ += len2; 591 | } 592 | } 593 | else { // empty 594 | if ( last > first ) { 595 | TestRingBufferLog("8888888888"); 596 | memcpy(buffer_, first.data(), sizeof(T) * len); 597 | } 598 | else { 599 | TestRingBufferLog("9999999999"); 600 | auto len1 = first.capacity() - first.current(); 601 | memcpy(buffer_, first.data(), sizeof(T) * len1); 602 | memcpy(buffer_ + len1, last.buffer(), sizeof(T) * last.current()); 603 | } 604 | head_ = 0; 605 | tail_ = len; 606 | } 607 | } 608 | 609 | /** 610 | * Removes the first count elements of the container. 611 | */ 612 | void pop_front(size_type count = 1) { 613 | if ( count < size() ) { 614 | head_ = (head_ + count) & (capacity_ - 1); 615 | } 616 | else { 617 | head_ = tail_ = 0; 618 | } 619 | } 620 | 621 | /** 622 | * Removes the last count elements of the container. 623 | */ 624 | void pop_back(size_type count = 1) { 625 | if ( count < size() ) { 626 | tail_ = (tail_ + capacity_ - count) & (capacity_ - 1); 627 | } 628 | else { 629 | head_ = tail_ = 0; 630 | } 631 | } 632 | 633 | T* buffer() noexcept { 634 | return buffer_; 635 | } 636 | 637 | size_type capacity() const noexcept { 638 | return capacity_; 639 | } 640 | 641 | size_type head() const noexcept { 642 | return head_; 643 | } 644 | 645 | size_type tail() const noexcept { 646 | return tail_; 647 | } 648 | 649 | void swap(RingBuffer& other) noexcept { 650 | std::swap(buffer_, other.buffer_); 651 | std::swap(capacity_, other.capacity_); 652 | std::swap(head_, other.head_); 653 | std::swap(tail_, other.tail_); 654 | } 655 | 656 | #ifdef TEST_RINGBUFFER 657 | RingBuffer(size_type capacity, size_type head, size_type tail): head_(head), tail_(tail) { 658 | static_assert(std::is_trivial::value && std::is_standard_layout::value, 659 | "Must be POD!"); 660 | capacity_ = roundup_pow_of_two(capacity); 661 | buffer_ = new T[capacity_](); 662 | } 663 | 664 | #endif 665 | 666 | private: 667 | void _expand_space() { 668 | if ( size() + 1 < capacity_ ) { 669 | return; 670 | } 671 | auto new_capacity = capacity_ == 0 ? 4 : roundup_pow_of_two(capacity_ + 2); 672 | auto tmp = buffer_; 673 | buffer_ = new T[new_capacity]; 674 | if ( tail_ >= head_ ) { 675 | memcpy(buffer_, tmp + head_, sizeof(T) * (tail_ - head_)); 676 | } 677 | else { 678 | auto n = capacity_ - head_; 679 | memcpy(buffer_, tmp + head_, sizeof(T) * n); 680 | memcpy(buffer_ + n, tmp, sizeof(T) * tail_); 681 | } 682 | head_ = 0; 683 | tail_ = capacity_ == 0 ? 0 : capacity_ - 1; 684 | capacity_ = new_capacity; 685 | delete [] tmp; 686 | } 687 | 688 | private: 689 | T* buffer_{ nullptr }; 690 | size_type capacity_{ 0 }; 691 | size_type head_{ 0 }; 692 | size_type tail_{ 0 }; 693 | 694 | }; 695 | 696 | } // end namespace leaf 697 | -------------------------------------------------------------------------------- /src/tui.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "tui.h" 3 | 4 | namespace leaf 5 | { 6 | 7 | Window::Window(const Point& tl, const Point& br, const Colorscheme& cs) 8 | : cs_(cs) 9 | { 10 | _init(tl, br); 11 | } 12 | 13 | void Window::_init(const Point& tl, const Point& br) { 14 | top_left_ = tl; 15 | bottom_right_ = br; 16 | core_top_left_ = top_left_; 17 | 18 | height_ = bottom_right_.line - top_left_.line + 1; 19 | width_ = bottom_right_.col - top_left_.col + 1; 20 | core_height_ = height_; 21 | core_width_ = width_; 22 | setBorder(); 23 | if ( is_reverse_ ) { 24 | core_top_left_.line += core_height_ - 1; 25 | } 26 | } 27 | 28 | void Window::setBuffer(Generator&& generator) { 29 | generator_ = std::move(generator); 30 | buffer_ = generator_(); 31 | 32 | uint32_t orig_cursorline_y = core_top_left_.line + cursor_line_ - first_line_; 33 | if ( is_reverse_ ) { 34 | orig_cursorline_y = core_top_left_.line - (cursor_line_ - first_line_); 35 | } 36 | first_line_ = 0; 37 | last_line_ = std::min(core_height_, static_cast(buffer_.size())); 38 | cursor_line_ = 0; 39 | 40 | _render(orig_cursorline_y); 41 | } 42 | 43 | void Window::setBuffer() { 44 | uint32_t orig_cursorline_y = core_top_left_.line + cursor_line_ - first_line_; 45 | if ( is_reverse_ ) { 46 | orig_cursorline_y = core_top_left_.line - (cursor_line_ - first_line_); 47 | } 48 | 49 | _render(orig_cursorline_y); 50 | } 51 | 52 | void Window::_scrollUp() { 53 | if ( cursor_line_ > first_line_ ) { 54 | _updateCursorline(cursor_line_ - 1); 55 | } 56 | else if ( first_line_ > 0 ) { 57 | uint32_t orig_cursorline_y = core_top_left_.line + cursor_line_ - first_line_; 58 | if ( is_reverse_ ) { 59 | orig_cursorline_y = core_top_left_.line - (cursor_line_ - first_line_); 60 | } 61 | --first_line_; 62 | if ( last_line_ > first_line_ + core_height_ ) { 63 | --last_line_; 64 | } 65 | --cursor_line_; 66 | _render(orig_cursorline_y); 67 | } 68 | } 69 | 70 | void Window::_scrollDown() { 71 | if ( cursor_line_ + 1 < last_line_ ) { 72 | _updateCursorline(cursor_line_ + 1); 73 | return; 74 | } 75 | 76 | if ( buffer_.size() == last_line_ ) { 77 | auto buffer = generator_(); 78 | buffer_.insert(buffer_.end(), buffer.begin(), buffer.end()); 79 | } 80 | 81 | if ( buffer_.size() > last_line_ ) { 82 | uint32_t orig_cursorline_y = core_top_left_.line + cursor_line_ - first_line_; 83 | if ( is_reverse_ ) { 84 | orig_cursorline_y = core_top_left_.line - (cursor_line_ - first_line_); 85 | } 86 | ++first_line_; 87 | ++last_line_; 88 | ++cursor_line_; 89 | _render(orig_cursorline_y); 90 | } 91 | } 92 | 93 | void Window::_pageUp() { 94 | if ( first_line_ == 0 ) { 95 | _updateCursorline(first_line_); 96 | return; 97 | } 98 | 99 | uint32_t orig_cursorline_y = core_top_left_.line + cursor_line_ - first_line_; 100 | if ( is_reverse_ ) { 101 | orig_cursorline_y = core_top_left_.line - (cursor_line_ - first_line_); 102 | } 103 | if ( first_line_ >= core_height_ - 1 ) { 104 | last_line_ = first_line_ + 1; 105 | first_line_ = last_line_ - core_height_; 106 | } 107 | else { 108 | first_line_ = 0; 109 | last_line_ = std::min(core_height_, static_cast(buffer_.size())); 110 | } 111 | 112 | cursor_line_ = last_line_ - 1; 113 | _render(orig_cursorline_y); 114 | } 115 | 116 | 117 | void Window::_pageDown() { 118 | if ( buffer_.size() < last_line_ + core_height_ ) { 119 | auto buffer = generator_(); 120 | buffer_.insert(buffer_.end(), buffer.begin(), buffer.end()); 121 | } 122 | 123 | if ( last_line_ == buffer_.size() ) { // no next page 124 | _updateCursorline(last_line_ - 1); 125 | return; 126 | } 127 | 128 | uint32_t orig_cursorline_y = core_top_left_.line + cursor_line_ - first_line_; 129 | if ( is_reverse_ ) { 130 | orig_cursorline_y = core_top_left_.line - (cursor_line_ - first_line_); 131 | } 132 | first_line_ = last_line_ - 1; 133 | last_line_ = std::min(first_line_ + core_height_, static_cast(buffer_.size())); 134 | 135 | cursor_line_ = first_line_; 136 | _render(orig_cursorline_y); 137 | } 138 | 139 | 140 | void Window::_render(uint32_t orig_cursorline_y) { 141 | auto& tty = Tty::getInstance(); 142 | tty.hideCursor(); 143 | if ( is_reverse_ ) { 144 | uint32_t new_cursorline_y = core_top_left_.line - (cursor_line_ - first_line_); 145 | uint32_t j = core_top_left_.line; 146 | for ( uint32_t i = first_line_; i < last_line_; ++i, --j ) { 147 | // avoid cursor line flickering 148 | if ( new_cursorline_y == orig_cursorline_y && new_cursorline_y == j ) { 149 | continue; 150 | } 151 | int32_t space_len = core_width_ - indent_ - buffer_[i].raw_str.len; 152 | space_len = std::max(space_len, 0); 153 | tty.addString(j, core_top_left_.col, std::string(indent_, ' ') + buffer_[i].str + std::string(space_len, ' ')); 154 | } 155 | 156 | for ( ; j > core_top_left_.line - core_height_; --j ) { 157 | tty.addString(j, core_top_left_.col, std::string(core_width_, ' ')); 158 | } 159 | } 160 | else { 161 | uint32_t new_cursorline_y = core_top_left_.line + cursor_line_ - first_line_; 162 | uint32_t j = core_top_left_.line; 163 | for ( uint32_t i = first_line_; i < last_line_; ++i, ++j ) { 164 | // avoid cursor line flickering 165 | if ( new_cursorline_y == orig_cursorline_y && new_cursorline_y == j ) { 166 | continue; 167 | } 168 | int32_t space_len = core_width_ - indent_ - buffer_[i].raw_str.len; 169 | space_len = std::max(space_len, 0); 170 | tty.addString(j, core_top_left_.col, std::string(indent_, ' ') + buffer_[i].str + std::string(space_len, ' ')); 171 | } 172 | 173 | for ( ; j < core_top_left_.line + core_height_; ++j ) { 174 | tty.addString(j, core_top_left_.col, std::string(core_width_, ' ')); 175 | } 176 | } 177 | 178 | if ( first_line_ < last_line_ ) { 179 | _renderCursorline(cursor_line_); 180 | } 181 | 182 | tty.restoreCursorPosition(); 183 | tty.showCursor(); 184 | } 185 | 186 | void Window::_renderCursorline(uint32_t cursor_line) { 187 | auto& tty = Tty::getInstance(); 188 | Point point = _getCursorlinePosition(cursor_line); 189 | auto str_len = buffer_[cursor_line].raw_str.len; 190 | int32_t space_len = core_width_ - indent_ - str_len; 191 | if ( space_len > 0 ) { 192 | tty.addString(point.line, point.col + str_len, std::string(space_len, ' ')); 193 | } 194 | 195 | const std::string reset_color("\033[0m"); 196 | 197 | // no color 198 | if ( !utils::endswith(buffer_[cursor_line].str, reset_color) ) { 199 | tty.addString(point.line, point.col, 200 | buffer_[cursor_line].str, 201 | cs_.getColor(HighlightGroup::CursorLine)); 202 | } 203 | else { 204 | auto& normal_color = cs_.getColor(HighlightGroup::Normal); 205 | auto& cursor_line_color = cs_.getColor(HighlightGroup::CursorLine); 206 | auto cursor_line_str = buffer_[cursor_line].str; 207 | auto pos = cursor_line_str.rfind(reset_color); 208 | if ( pos != std::string::npos ) { 209 | auto max_pos = pos; 210 | pos -= reset_color.length(); 211 | auto normal_len = normal_color.length(); 212 | while ( pos < max_pos 213 | && (pos = cursor_line_str.rfind(normal_color, pos)) != std::string::npos ) { 214 | cursor_line_str.replace(pos, normal_len, cursor_line_color); 215 | pos -= normal_len; 216 | } 217 | 218 | tty.addString(point.line, point.col, cursor_line_str); 219 | } 220 | } 221 | 222 | _drawIndicator(point.line); 223 | } 224 | 225 | 226 | void Window::_updateCursorline(uint32_t new_cursorline) { 227 | if ( new_cursorline == cursor_line_ || new_cursorline >= buffer_.size() ) { 228 | return; 229 | } 230 | 231 | auto& tty = Tty::getInstance(); 232 | Point point = _getCursorlinePosition(cursor_line_); 233 | _clearIndicator(point.line); 234 | tty.addString(point.line, point.col, buffer_[cursor_line_].str); 235 | 236 | _renderCursorline(new_cursorline); 237 | cursor_line_ = new_cursorline; 238 | 239 | tty.restoreCursorPosition(); 240 | } 241 | 242 | void MainWindow::updateLineInfo(uint32_t result_size, uint32_t total_size) { 243 | result_size_ = result_size; 244 | total_size_ = total_size; 245 | 246 | auto total = std::to_string(total_size_); 247 | auto info_len = total.length() * 2 + 1; 248 | auto info = std::to_string(result_size_) + "/" + total; 249 | auto& tty = Tty::getInstance(); 250 | if ( line_info_.line == cmdline_y_ ) { 251 | if ( line_info_.col == 0 ) { 252 | line_info_.col = core_top_left_.col - 1 + right_boundary_; 253 | } 254 | int32_t info_visual_len = core_top_left_.col + core_width_ - (line_info_.col - info.length() + 1); 255 | if ( info_visual_len > 0 ) { 256 | tty.addString(line_info_.line, line_info_.col - info.length() + 1, 257 | info.substr(0, info_visual_len), 258 | cs_.getColor(HighlightGroup::LineInfo)); 259 | } 260 | 261 | int32_t visual_len = core_top_left_.col + core_width_ - (line_info_.col - info_len + 1); 262 | if ( visual_len > 0 ) { 263 | tty.addString(line_info_.line, line_info_.col - info_len + 1, 264 | std::string(info_len - info.length(), ' ').substr(0, visual_len)); 265 | } 266 | 267 | uint32_t flag_len = 0; 268 | if ( show_flag_.load(std::memory_order_relaxed) ) { 269 | flag_len = 3; 270 | if ( line_info_.col + 1 < core_top_left_.col + core_width_ ) { 271 | tty.addString(line_info_.line, line_info_.col + 1, " "); 272 | } 273 | } 274 | int32_t space_len = core_top_left_.col + core_width_ - (line_info_.col + flag_len + 1); 275 | if ( space_len > 0 ) { 276 | tty.addString(line_info_.line, line_info_.col + flag_len + 1, std::string(space_len, ' ')); 277 | } 278 | } 279 | else { 280 | tty.addString(line_info_.line, line_info_.col, info, 281 | cs_.getColor(HighlightGroup::LineInfo)); 282 | int32_t space_len = core_width_ - indent_ - info.length(); 283 | if ( space_len > 0 ) { 284 | tty.addString(line_info_.line, line_info_.col + info.length(), 285 | std::string(space_len, ' ')); 286 | } 287 | } 288 | tty.restoreCursorPosition(); 289 | } 290 | 291 | void MainWindow::updateCmdline(const std::string& pattern, uint32_t cursor_pos) { 292 | cursor_pos_ = cursor_pos; 293 | auto& tty = Tty::getInstance(); 294 | 295 | if ( line_info_.line == cmdline_y_ ) { 296 | auto total = std::to_string(total_size_); 297 | auto info_len = total.length() * 2 + 1; 298 | auto info = std::to_string(result_size_) + "/" + total; 299 | auto first_len = prompt_.length() + pattern.length() + 2; 300 | std::string spaces; 301 | auto border_char_width = ConfigManager::getInstance().getBorderCharWidth(); 302 | if ( border_char_width + first_len + info_len < right_boundary_ ) { 303 | line_info_.col = core_top_left_.col - 1 + right_boundary_; 304 | spaces = std::string(right_boundary_ - (first_len - 2) - info.length(), ' '); 305 | } 306 | else { 307 | line_info_.col = core_top_left_.col + first_len + info_len - 1; 308 | spaces = std::string(info_len - info.length() + 2, ' '); 309 | } 310 | 311 | uint32_t flag_len = 0; 312 | if ( show_flag_.load(std::memory_order_relaxed) ) { 313 | flag_col_.store(line_info_.col + 2, std::memory_order_relaxed); 314 | flag_len = 3; 315 | if ( line_info_.col + 1 < core_top_left_.col + core_width_ ) { 316 | tty.addString(line_info_.line, line_info_.col + 1, " "); 317 | } 318 | } 319 | 320 | int32_t info_visual_len = core_top_left_.col + core_width_ - (line_info_.col - info.length() + 1); 321 | if ( info_visual_len > 0 ) { 322 | tty.addString(line_info_.line, line_info_.col - info.length() + 1, 323 | info.substr(0, info_visual_len), 324 | cs_.getColor(HighlightGroup::LineInfo)); 325 | } 326 | int32_t space_len = core_top_left_.col + core_width_ - (line_info_.col + flag_len + 1); 327 | if ( space_len > 0 ) { 328 | tty.addString(line_info_.line, line_info_.col + flag_len + 1, std::string(space_len, ' ')); 329 | } 330 | 331 | int32_t visual_len = core_width_ - prompt_.length(); 332 | tty.addString(cmdline_y_, core_top_left_.col + prompt_.length(), 333 | (pattern + spaces).substr(0, visual_len)); 334 | tty.addStringAndSave(cmdline_y_, core_top_left_.col + prompt_.length(), 335 | pattern.substr(0, cursor_pos).substr(0, visual_len - 1)); 336 | } 337 | else { 338 | uint32_t available_width = core_width_ - prompt_.length() - 1; 339 | if ( static_cast(available_width) <= 0 ) { 340 | return; 341 | } 342 | if ( available_width <= pattern.length() ) { 343 | if ( cursor_pos >= available_width ) { 344 | tty.addStringAndSave(cmdline_y_, core_top_left_.col + prompt_.length(), 345 | pattern.substr(cursor_pos - available_width, available_width)); 346 | } 347 | else { 348 | tty.addString(cmdline_y_, core_top_left_.col + prompt_.length(), 349 | pattern.substr(0, available_width)); 350 | tty.addStringAndSave(cmdline_y_, core_top_left_.col + prompt_.length(), 351 | pattern.substr(0, cursor_pos)); 352 | } 353 | } 354 | else { 355 | tty.addString(cmdline_y_, core_top_left_.col + prompt_.length(), 356 | pattern + std::string(available_width - pattern.length(), ' ')); 357 | tty.addStringAndSave(cmdline_y_, core_top_left_.col + prompt_.length(), 358 | pattern.substr(0, cursor_pos)); 359 | } 360 | } 361 | // if another thread restore cursor to previous position before saveCursorPosition, 362 | // this line can ensure the cursor is at the correct position 363 | tty.restoreCursorPosition(); 364 | } 365 | 366 | /** 367 | * top, right, bottom, left, topleft, topright, botright, botleft corner 368 | * Example: ['─', '│', '─', '│', '╭', '╮', '╯', '╰'] 369 | * ['─', '│', '─', '│', '┌', '┐', '┘', '└'] 370 | * ['━', '┃', '━', '┃', '┏', '┓', '┛', '┗'] 371 | * ['═', '║', '═', '║', '╔', '╗', '╝', '╚'] 372 | */ 373 | void MainWindow::drawBorder() const { 374 | auto& border = ConfigManager::getInstance().getConfigValue(); 375 | if ( border.empty() ) { 376 | return; 377 | } 378 | 379 | auto& border_chars = ConfigManager::getInstance().getConfigValue(); 380 | auto border_char_width = ConfigManager::getInstance().getBorderCharWidth(); 381 | uint32_t count = width_ / border_char_width; 382 | 383 | // top 384 | if ( (border_mask_ & 0b0001) == 0b0001 ) { 385 | Cleanup::getInstance().setTopBorder(); 386 | std::string border_str; 387 | border_str.reserve(border_chars[0].length() * count); 388 | for ( uint32_t i = 0; i < count; ++i ) { 389 | border_str.append(border_chars[0]); 390 | } 391 | 392 | Tty::getInstance().addString(top_left_.line, top_left_.col, border_str, 393 | cs_.getColor(HighlightGroup::Border)); 394 | } 395 | 396 | // right 397 | if ( (border_mask_ & 0b0010) == 0b0010 ) { 398 | for ( uint32_t i = 0; i < height_; ++i ) { 399 | Tty::getInstance().addString(top_left_.line + i, bottom_right_.col - border_char_width + 1, 400 | border_chars[1], cs_.getColor(HighlightGroup::Border)); 401 | } 402 | } 403 | 404 | // bottom 405 | if ( (border_mask_ & 0b0100) == 0b0100 ) { 406 | std::string border_str; 407 | border_str.reserve(border_chars[2].length() * count); 408 | for ( uint32_t i = 0; i < count; ++i ) { 409 | border_str.append(border_chars[2]); 410 | } 411 | 412 | Tty::getInstance().addString(bottom_right_.line, top_left_.col, border_str, 413 | cs_.getColor(HighlightGroup::Border)); 414 | } 415 | 416 | // left 417 | if ( (border_mask_ & 0b1000) == 0b1000 ) { 418 | for ( uint32_t i = 0; i < height_; ++i ) { 419 | Tty::getInstance().addString(top_left_.line + i, top_left_.col, border_chars[3], 420 | cs_.getColor(HighlightGroup::Border)); 421 | } 422 | } 423 | 424 | // top left corner 425 | if ( (border_mask_ & 0b1001) == 0b1001 ) { 426 | Tty::getInstance().addString(top_left_.line, top_left_.col, border_chars[4], 427 | cs_.getColor(HighlightGroup::Border)); 428 | } 429 | 430 | // top right corner 431 | if ( (border_mask_ & 0b0011) == 0b0011 ) { 432 | Tty::getInstance().addString(top_left_.line, bottom_right_.col - border_char_width + 1, 433 | border_chars[5], cs_.getColor(HighlightGroup::Border)); 434 | } 435 | 436 | // bottom right corner 437 | if ( (border_mask_ & 0b0110) == 0b0110 ) { 438 | Tty::getInstance().addString(bottom_right_.line, bottom_right_.col - border_char_width + 1, 439 | border_chars[6], cs_.getColor(HighlightGroup::Border)); 440 | } 441 | 442 | // bottom left corner 443 | if ( (border_mask_ & 0b1100) == 0b1100 ) { 444 | Tty::getInstance().addString(bottom_right_.line, top_left_.col, border_chars[7], 445 | cs_.getColor(HighlightGroup::Border)); 446 | } 447 | } 448 | 449 | void MainWindow::_setCmdline() { 450 | core_height_ -= 1; 451 | if ( is_reverse_ ) { 452 | cmdline_y_ = core_top_left_.line; 453 | if ( core_width_ < right_boundary_ + 3 ) { 454 | core_height_ -= 1; 455 | line_info_.line = cmdline_y_ - 1; 456 | line_info_.col = core_top_left_.col + 2; 457 | flag_col_.store(core_top_left_.col, std::memory_order_relaxed); 458 | } 459 | else { 460 | line_info_.line = cmdline_y_; 461 | line_info_.col = 0; 462 | flag_col_.store(core_top_left_.col - 1 + right_boundary_ + 2, std::memory_order_relaxed); 463 | } 464 | core_top_left_.line = line_info_.line - 1; 465 | } 466 | else { 467 | cmdline_y_ = core_top_left_.line; 468 | if ( core_width_ < right_boundary_ + 3 ) { 469 | core_height_ -= 1; 470 | line_info_.line = cmdline_y_ + 1; 471 | line_info_.col = core_top_left_.col + 2; 472 | flag_col_.store(core_top_left_.col, std::memory_order_relaxed); 473 | } 474 | else { 475 | line_info_.line = cmdline_y_; 476 | line_info_.col = 0; 477 | flag_col_.store(core_top_left_.col - 1 + right_boundary_ + 2, std::memory_order_relaxed); 478 | } 479 | core_top_left_.line = line_info_.line + 1; 480 | } 481 | } 482 | 483 | Tui::Tui() 484 | :tty_(Tty::getInstance()), 485 | cfg_(ConfigManager::getInstance()), 486 | cleanup_(Cleanup::getInstance()) 487 | { 488 | init(false); 489 | } 490 | 491 | void Tui::init(bool resume) { 492 | uint32_t win_height = 0; 493 | uint32_t win_width = 0; 494 | if ( !resume ) { 495 | tty_.getWindowSize(win_height, win_width); 496 | } 497 | else { 498 | tty_.getWindowSize2(win_height, win_width); 499 | } 500 | 501 | uint32_t line = 0; 502 | uint32_t col = 0; 503 | if ( !resume ) { 504 | tty_.getCursorPosition(line, col); 505 | auto& border = ConfigManager::getInstance().getConfigValue(); 506 | if ( !border.empty() ) { 507 | auto& border_chars = ConfigManager::getInstance().getConfigValue(); 508 | tty_.addString(line, col, border_chars[0]); 509 | tty_.flush(); 510 | uint32_t new_line = 0; 511 | uint32_t new_col = 0; 512 | tty_.getCursorPosition(new_line, new_col); 513 | ConfigManager::getInstance().setBorderCharWidth(new_col - col); 514 | tty_.clear(EraseMode::ToLineBegin); 515 | } 516 | } 517 | else { 518 | tty_.getCursorPosition2(line, col); 519 | } 520 | 521 | Point top_left(1, 1); 522 | Point bottom_right(win_height, win_width); 523 | 524 | auto height = cfg_.getConfigValue(); 525 | if ( height == 0 || height >= win_height ) { 526 | height = win_height; 527 | Point orig_cursor_pos; 528 | orig_cursor_pos.line = line; 529 | orig_cursor_pos.col = col; 530 | cleanup_.saveCursorPosition(orig_cursor_pos); 531 | cleanup_.setFullScreen(); 532 | tty_.enableAlternativeBuffer(); 533 | tty_.enableMouse(); 534 | tty_.disableAutoWrap(); 535 | tty_.flush(); 536 | } 537 | else { 538 | uint32_t min_height = 3; // minimum height is 3 539 | auto& border = ConfigManager::getInstance().getConfigValue(); 540 | if ( border.find("T") != std::string::npos ) { 541 | ++min_height; 542 | } 543 | if ( border.find("B") != std::string::npos ) { 544 | ++min_height; 545 | } 546 | 547 | height = std::max(height, min_height); 548 | tty_.enableMouse(); 549 | tty_.disableAutoWrap(); 550 | if ( win_height - line < height ) { 551 | auto delta_height = height - (win_height - line); 552 | tty_.scrollUp(delta_height - 1); 553 | tty_.moveCursor(CursorDirection::Up, delta_height - 1); 554 | tty_.flush(); 555 | line = win_height - height + 1; 556 | } 557 | else { 558 | tty_.flush(); 559 | bottom_right.line = line + height - 1; 560 | } 561 | 562 | top_left.line = line; 563 | top_left.col = col; 564 | } 565 | 566 | setMargin(top_left, bottom_right, height); 567 | win_width = bottom_right.col - top_left.col + 1; 568 | auto& border = ConfigManager::getInstance().getConfigValue(); 569 | if ( border.find("T") != std::string::npos || border.find("B") != std::string::npos ) { 570 | bottom_right.col -= win_width % ConfigManager::getInstance().getBorderCharWidth(); 571 | } 572 | 573 | if ( !resume ) { 574 | setMainWindow(top_left, bottom_right, cs_); 575 | cleanup_.saveCoreHeight(p_main_win_->getCoreHeight()); 576 | p_main_win_->display(); 577 | } 578 | else { 579 | p_main_win_->reset(top_left, bottom_right); 580 | } 581 | } 582 | 583 | void Tui::setMargin(Point& tl, Point& br, uint32_t height) { 584 | auto& margin = ConfigManager::getInstance().getConfigValue(); 585 | uint32_t min_height = 5; 586 | if ( height <= min_height ) { 587 | margin[0] = 0; 588 | margin[2] = 0; 589 | } 590 | else if ( margin[0] + margin[2] + min_height > height ) { 591 | margin[0] = (height - min_height) >> 1; 592 | margin[2] = height - min_height - margin[0]; 593 | } 594 | 595 | auto width = br.col - tl.col + 1; 596 | uint32_t min_width = 16; 597 | if ( width <= min_width ) { 598 | margin[1] = 0; 599 | margin[3] = 0; 600 | } 601 | else if ( margin[1] + margin[3] + min_width > width ) { 602 | margin[1] = (width - min_width) >> 1; 603 | margin[3] = width - min_width - margin[1]; 604 | } 605 | 606 | tl.line += margin[0]; 607 | tl.col += margin[3]; 608 | br.line -= margin[2]; 609 | br.col -= margin[1]; 610 | } 611 | 612 | Tui::~Tui() { 613 | cleanup_.doWork(true); 614 | 615 | if ( accept_ ) { 616 | p_main_win_->printAcceptedStrings(); 617 | } 618 | } 619 | 620 | Cleanup::~Cleanup() { 621 | if ( !done_ ) { 622 | // reduce the possibility that cmdline thread is still running 623 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 624 | doWork(true); 625 | } 626 | } 627 | 628 | void Cleanup::doWork(bool once) { 629 | if ( once && !done_ ) { 630 | done_ = true; 631 | } 632 | 633 | auto& cfg = ConfigManager::getInstance(); 634 | auto& tty = Tty::getInstance(); 635 | tty.enableAutoWrap(); 636 | tty.disableMouse(); 637 | tty.showCursor(); 638 | if ( is_full_screen_ ) { 639 | tty.clear(EraseMode::EntireScreen); 640 | tty.disableAlternativeBuffer(); 641 | tty.moveCursorTo(orig_cursor_pos_.line, orig_cursor_pos_.col); 642 | tty.flush(); 643 | } 644 | else { 645 | if ( cfg.getConfigValue() && core_height_ > 0 ) { 646 | tty.moveCursor(CursorDirection::Up, core_height_); 647 | } 648 | if ( top_border_ ) { 649 | tty.moveCursor(CursorDirection::Up, 1); 650 | } 651 | auto top_margin = cfg.getConfigValue()[0]; 652 | if ( top_margin ) { 653 | tty.moveCursor(CursorDirection::Up, top_margin); 654 | } 655 | tty.moveCursor(CursorDirection::Left, 1024); // move to column 1 656 | tty.clear(EraseMode::ToScreenEnd); 657 | tty.flush(); 658 | } 659 | 660 | tty.restoreOrigTerminal(); 661 | } 662 | 663 | } // end namespace leaf 664 | -------------------------------------------------------------------------------- /src/tty.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "tty.h" 9 | #include "utils.h" 10 | #include "error.h" 11 | 12 | namespace leaf 13 | { 14 | 15 | Tty::Tty(): color_names_ { 16 | "Black", "Maroon", "Green", "Olive", 17 | "Navy", "Purple", "Teal", "Silver", 18 | "Grey", "Red", "Lime", "Yellow", 19 | "Blue", "Fuchsia", "Aqua", "White", 20 | "Grey0", "NavyBlue", "DarkBlue", "Blue3", 21 | "Blue3", "Blue1", "DarkGreen", "DeepSkyBlue4", 22 | "DeepSkyBlue4", "DeepSkyBlue4", "DodgerBlue3", "DodgerBlue2", 23 | "Green4", "SpringGreen4", "Turquoise4", "DeepSkyBlue3", 24 | "DeepSkyBlue3", "DodgerBlue1", "Green3", "SpringGreen3", 25 | "DarkCyan", "LightSeaGreen", "DeepSkyBlue2", "DeepSkyBlue1", 26 | "Green3", "SpringGreen3", "SpringGreen2", "Cyan3", 27 | "DarkTurquoise", "Turquoise2", "Green1", "SpringGreen2", 28 | "SpringGreen1", "MediumSpringGreen", "Cyan2", "Cyan1", 29 | "DarkRed", "DeepPink4", "Purple4", "Purple4", 30 | "Purple3", "BlueViolet", "Orange4", "Grey37", 31 | "MediumPurple4", "SlateBlue3", "SlateBlue3", "RoyalBlue1", 32 | "Chartreuse4", "DarkSeaGreen4", "PaleTurquoise4", "SteelBlue", 33 | "SteelBlue3", "CornflowerBlue", "Chartreuse3", "DarkSeaGreen4", 34 | "CadetBlue", "CadetBlue", "SkyBlue3", "SteelBlue1", 35 | "Chartreuse3", "PaleGreen3", "SeaGreen3", "Aquamarine3", 36 | "MediumTurquoise", "SteelBlue1", "Chartreuse2", "SeaGreen2", 37 | "SeaGreen1", "SeaGreen1", "Aquamarine1", "DarkSlateGray2", 38 | "DarkRed", "DeepPink4", "DarkMagenta", "DarkMagenta", 39 | "DarkViolet", "Purple", "Orange4", "LightPink4", 40 | "Plum4", "MediumPurple3", "MediumPurple3", "SlateBlue1", 41 | "Yellow4", "Wheat4", "Grey53", "LightSlateGrey", 42 | "MediumPurple", "LightSlateBlue", "Yellow4", "DarkOliveGreen3", 43 | "DarkSeaGreen", "LightSkyBlue3", "LightSkyBlue3", "SkyBlue2", 44 | "Chartreuse2", "DarkOliveGreen3", "PaleGreen3", "DarkSeaGreen3", 45 | "DarkSlateGray3", "SkyBlue1", "Chartreuse1", "LightGreen", 46 | "LightGreen", "PaleGreen1", "Aquamarine1", "DarkSlateGray1", 47 | "Red3", "DeepPink4", "MediumVioletRed", "Magenta3", 48 | "DarkViolet", "Purple", "DarkOrange3", "IndianRed", 49 | "HotPink3", "MediumOrchid3", "MediumOrchid", "MediumPurple2", 50 | "DarkGoldenrod", "LightSalmon3", "RosyBrown", "Grey63", 51 | "MediumPurple2", "MediumPurple1", "Gold3", "DarkKhaki", 52 | "NavajoWhite3", "Grey69", "LightSteelBlue3", "LightSteelBlue", 53 | "Yellow3", "DarkOliveGreen3", "DarkSeaGreen3", "DarkSeaGreen2", 54 | "LightCyan3", "LightSkyBlue1", "GreenYellow", "DarkOliveGreen2", 55 | "PaleGreen1", "DarkSeaGreen2", "DarkSeaGreen1", "PaleTurquoise1", 56 | "Red3", "DeepPink3", "DeepPink3", "Magenta3", 57 | "Magenta3", "Magenta2", "DarkOrange3", "IndianRed", 58 | "HotPink3", "HotPink2", "Orchid", "MediumOrchid1", 59 | "Orange3", "LightSalmon3", "LightPink3", "Pink3", 60 | "Plum3", "Violet", "Gold3", "LightGoldenrod3", 61 | "Tan", "MistyRose3", "Thistle3", "Plum2", 62 | "Yellow3", "Khaki3", "LightGoldenrod2", "LightYellow3", 63 | "Grey84", "LightSteelBlue1", "Yellow2", "DarkOliveGreen1", 64 | "DarkOliveGreen1", "DarkSeaGreen1", "Honeydew2", "LightCyan1", 65 | "Red1", "DeepPink2", "DeepPink1", "DeepPink1", 66 | "Magenta2", "Magenta1", "OrangeRed1", "IndianRed1", 67 | "IndianRed1", "HotPink", "HotPink", "MediumOrchid1", 68 | "DarkOrange", "Salmon1", "LightCoral", "PaleVioletRed1", 69 | "Orchid2", "Orchid1", "Orange1", "SandyBrown", 70 | "LightSalmon1", "LightPink1", "Pink1", "Plum1", 71 | "Gold1", "LightGoldenrod2", "LightGoldenrod2", "NavajoWhite1", 72 | "MistyRose1", "Thistle1", "Yellow1", "LightGoldenrod1", 73 | "Khaki1", "Wheat1", "Cornsilk1", "Grey100", 74 | "Grey3", "Grey7", "Grey11", "Grey15", 75 | "Grey19", "Grey23", "Grey27", "Grey30", 76 | "Grey35", "Grey39", "Grey42", "Grey46", 77 | "Grey50", "Grey54", "Grey58", "Grey62", 78 | "Grey66", "Grey70", "Grey74", "Grey78", 79 | "Grey82", "Grey85", "Grey89", "Grey93" 80 | } 81 | { 82 | _init(); 83 | } 84 | 85 | Tty::~Tty() { 86 | restoreOrigTerminal(); 87 | fclose(stdin_); 88 | fclose(stdout_); 89 | } 90 | 91 | void Tty::_init() { 92 | stdin_ = fopen("/dev/tty", "r"); 93 | if ( stdin_ == nullptr ) { 94 | Error::getInstance().appendError(ErrorMessage); 95 | std::exit(EXIT_FAILURE); 96 | } 97 | term_stdin_ = fileno(stdin_); 98 | 99 | stdout_ = fopen("/dev/tty", "w"); 100 | if ( stdout_ == nullptr ) { 101 | Error::getInstance().appendError(ErrorMessage); 102 | std::exit(EXIT_FAILURE); 103 | } 104 | term_stdout_ = fileno(stdout_); 105 | 106 | if ( tcgetattr(term_stdin_, &orig_term_) == -1 ) { 107 | Error::getInstance().appendError(ErrorMessage); 108 | std::exit(EXIT_FAILURE); 109 | } 110 | 111 | new_term_ = orig_term_; 112 | // ICRNL separate Ctrl-M from Ctrl-J 113 | // IXON disable Ctrl-S and Ctrl-Q 114 | new_term_.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 115 | new_term_.c_oflag &= ~(OPOST); 116 | // IEXTEN disable Ctrl-V 117 | new_term_.c_lflag &= ~(ECHO | ICANON | IEXTEN); 118 | new_term_.c_cflag |= (CS8); 119 | new_term_.c_cc[VMIN] = 0; 120 | new_term_.c_cc[VTIME] = 1; 121 | 122 | //new_term_.c_iflag |= (IUTF8); 123 | 124 | setNewTerminal(); 125 | 126 | #if 0 127 | // shell command 128 | for i in `echo "vt100 vt102 vt220 vt320 vt400 \ 129 | xterm xterm-r5 xterm-r6 xterm-new \ 130 | xterm-16color xterm-256color linux \ 131 | scoansi ansi rxvt dtterm nsterm screen"`; \ 132 | do { { infocmp -1 -x $i | grep kcu | sed 's/^\s*//';}; \ 133 | echo $i;} | paste -s ; done | sort -u 134 | #endif 135 | esc_codes_ = { 136 | // -------------------------- kcu -------------------------- 137 | // nsterm screen vt100 vt102 vt320 vt400 138 | // xterm xterm-16color xterm-256color xterm-new xterm-r5 xterm-r6 139 | { "\033OA", Key::Up }, 140 | { "\033OB", Key::Down }, 141 | { "\033OC", Key::Right }, 142 | { "\033OD", Key::Left }, 143 | // ansi dtterm linux rxvt scoansi vt220 144 | { "\033[A", Key::Up }, 145 | { "\033[B", Key::Down }, 146 | { "\033[C", Key::Right }, 147 | { "\033[D", Key::Left }, 148 | 149 | // -------------------------- kUP5\|kDN5\|kLFT5\|kRIT5 -------------------------- 150 | // xterm xterm-16color xterm-256color xterm-new 151 | { "\033[1;5A", Key::Ctrl_Up }, 152 | { "\033[1;5B", Key::Ctrl_Down }, 153 | { "\033[1;5C", Key::Ctrl_Right }, 154 | { "\033[1;5D", Key::Ctrl_Left }, 155 | // rxvt 156 | { "\033Oa", Key::Ctrl_Up }, 157 | { "\033Ob", Key::Ctrl_Down }, 158 | { "\033Oc", Key::Ctrl_Right }, 159 | { "\033Od", Key::Ctrl_Left }, 160 | 161 | // -------------------------- kUP\|kDN\|kLFT\|kRIT -------------------------- 162 | // xterm xterm-16color xterm-256color xterm-new 163 | { "\033[1;2A", Key::Shift_Up }, 164 | { "\033[1;2B", Key::Shift_Down }, 165 | { "\033[1;2C", Key::Shift_Right }, 166 | { "\033[1;2D", Key::Shift_Left }, 167 | // rxvt 168 | { "\033[a", Key::Shift_Up }, 169 | { "\033[b", Key::Shift_Down }, 170 | { "\033[c", Key::Shift_Right }, 171 | { "\033[d", Key::Shift_Left }, 172 | 173 | // -------------------------- khome -------------------------- 174 | // nsterm xterm xterm-16color xterm-256color xterm-new 175 | { "\033OH", Key::Home }, 176 | // ansi scoansi 177 | { "\033[H", Key::Home }, 178 | // linux screen vt320 xterm-r5 179 | { "\033[1~", Key::Home }, 180 | // rxvt 181 | { "\033[7~", Key::Home }, 182 | 183 | // -------------------------- kend -------------------------- 184 | // nsterm xterm xterm-16color xterm-256color xterm-new 185 | { "\033OF", Key::End }, 186 | // scoansi 187 | { "\033[F", Key::End }, 188 | // linux screen xterm-r5 189 | { "\033[4~", Key::End }, 190 | // rxvt 191 | { "\033[8~", Key::End }, 192 | 193 | // -------------------------- kich1 -------------------------- 194 | // dtterm linux rxvt screen vt220 vt320 xterm xterm-16color 195 | // xterm-256color xterm-new xterm-r5 xterm-r6 196 | { "\0332~", Key::Insert }, 197 | // ansi scoansi 198 | { "\033[L", Key::Insert }, 199 | 200 | // -------------------------- kdch1 -------------------------- 201 | // dtterm linux nsterm rxvt screen vt220 vt320 xterm 202 | // xterm-16color xterm-256color xterm-new xterm-r5 xterm-r6 203 | { "\033[3~", Key::Delete }, 204 | 205 | // -------------------------- kpp\|knp -------------------------- 206 | // dtterm linux nsterm rxvt screen vt220 vt320 xterm 207 | // xterm-16color xterm-256color xterm-new xterm-r5 xterm-r6 208 | { "\033[5~", Key::Page_Up }, 209 | { "\033[6~", Key::Page_Down }, 210 | // scoansi 211 | { "\033[I", Key::Page_Up }, 212 | { "\033[G", Key::Page_Down }, 213 | 214 | // -------------------------- kf1-kf12 -------------------------- 215 | // nsterm screen vt100 vt102 vt220 vt320 vt400 xterm 216 | // xterm-16color xterm-256color xterm-new 217 | { "\033OP", Key::F1 }, 218 | { "\033OQ", Key::F2 }, 219 | { "\033OR", Key::F3 }, 220 | { "\033OS", Key::F4 }, 221 | // vt100 vt102 222 | { "\033Ot", Key::F5 }, 223 | { "\033Ou", Key::F6 }, 224 | { "\033Ov", Key::F7 }, 225 | { "\033Ol", Key::F8 }, 226 | { "\033Ow", Key::F9 }, 227 | { "\033Ox", Key::F10 }, 228 | // dtterm rxvt xterm-r5 xterm-r6 229 | { "\033[11~", Key::F1 }, 230 | { "\033[12~", Key::F2 }, 231 | { "\033[13~", Key::F3 }, 232 | { "\033[14~", Key::F4 }, 233 | { "\033[15~", Key::F5 }, 234 | { "\033[17~", Key::F6 }, 235 | { "\033[18~", Key::F7 }, 236 | { "\033[19~", Key::F8 }, 237 | { "\033[20~", Key::F9 }, 238 | { "\033[21~", Key::F10 }, 239 | { "\033[23~", Key::F11 }, 240 | { "\033[24~", Key::F12 }, 241 | // linux 242 | { "\033[[A", Key::F1 }, 243 | { "\033[[B", Key::F2 }, 244 | { "\033[[C", Key::F3 }, 245 | { "\033[[D", Key::F4 }, 246 | { "\033[[E", Key::F5 }, 247 | // scoansi 248 | { "\033[M", Key::F1 }, 249 | { "\033[N", Key::F2 }, 250 | { "\033[O", Key::F3 }, 251 | { "\033[P", Key::F4 }, 252 | { "\033[Q", Key::F5 }, 253 | { "\033[R", Key::F6 }, 254 | { "\033[S", Key::F7 }, 255 | { "\033[T", Key::F8 }, 256 | { "\033[U", Key::F9 }, 257 | { "\033[V", Key::F10 }, 258 | { "\033[W", Key::F11 }, 259 | { "\033[X", Key::F12 }, 260 | 261 | // -------------------------- kf13-kf24 -------------------------- 262 | // xterm xterm-16color xterm-256color xterm-new 263 | { "\033[1;2P", Key::Shift_F1 }, 264 | { "\033[1;2Q", Key::Shift_F2 }, 265 | { "\033[1;2R", Key::Shift_F3 }, 266 | { "\033[1;2S", Key::Shift_F4 }, 267 | { "\033[15;2~", Key::Shift_F5 }, 268 | { "\033[17;2~", Key::Shift_F6 }, 269 | { "\033[18;2~", Key::Shift_F7 }, 270 | { "\033[19;2~", Key::Shift_F8 }, 271 | { "\033[20;2~", Key::Shift_F9 }, 272 | { "\033[21;2~", Key::Shift_F10 }, 273 | { "\033[23;2~", Key::Shift_F11 }, 274 | { "\033[24;2~", Key::Shift_F12 }, 275 | 276 | // dtterm linux nsterm rxvt vt320 xterm-r6 vt220 277 | { "\033[25~", Key::Shift_F1 }, 278 | { "\033[26~", Key::Shift_F2 }, 279 | { "\033[28~", Key::Shift_F3 }, 280 | { "\033[29~", Key::Shift_F4 }, 281 | { "\033[31~", Key::Shift_F5 }, 282 | { "\033[32~", Key::Shift_F6 }, 283 | { "\033[33~", Key::Shift_F7 }, 284 | { "\033[34~", Key::Shift_F8 }, 285 | { "\033[23$", Key::Shift_F9 }, 286 | { "\033[24$", Key::Shift_F10 }, 287 | //{ "\033[11\^", Key::Shift_F11 }, 288 | //{ "\033[12\^", Key::Shift_F12 }, 289 | 290 | // scoansi 291 | { "\033[Y", Key::Shift_F1 }, 292 | // { "\033[a", Key::Shift_F3 }, 293 | // { "\033[b", Key::Shift_F4 }, 294 | // { "\033[c", Key::Shift_F5 }, 295 | // { "\033[d", Key::Shift_F6 }, 296 | { "\033[e", Key::Shift_F7 }, 297 | { "\033[f", Key::Shift_F8 }, 298 | { "\033[g", Key::Shift_F9 }, 299 | { "\033[h", Key::Shift_F10 }, 300 | { "\033[i", Key::Shift_F11 }, 301 | { "\033[j", Key::Shift_F12 }, 302 | 303 | // -------------------------- kf25-kf36 -------------------------- 304 | // xterm xterm-16color xterm-256color xterm-new 305 | { "\033[1;5P", Key::Ctrl_F1 }, 306 | { "\033[1;5Q", Key::Ctrl_F2 }, 307 | { "\033[1;5R", Key::Ctrl_F3 }, 308 | { "\033[1;5S", Key::Ctrl_F4 }, 309 | { "\033[15;5~", Key::Ctrl_F5 }, 310 | { "\033[17;5~", Key::Ctrl_F6 }, 311 | { "\033[18;5~", Key::Ctrl_F7 }, 312 | { "\033[19;5~", Key::Ctrl_F8 }, 313 | { "\033[20;5~", Key::Ctrl_F9 }, 314 | { "\033[21;5~", Key::Ctrl_F10 }, 315 | { "\033[23;5~", Key::Ctrl_F11 }, 316 | { "\033[24;5~", Key::Ctrl_F12 }, 317 | 318 | // rxvt 319 | //{ "\033[13\^", Key::Ctrl_F1 }, 320 | //{ "\033[14\^", Key::Ctrl_F2 }, 321 | //{ "\033[15\^", Key::Ctrl_F3 }, 322 | //{ "\033[17\^", Key::Ctrl_F4 }, 323 | //{ "\033[18\^", Key::Ctrl_F5 }, 324 | //{ "\033[19\^", Key::Ctrl_F6 }, 325 | //{ "\033[20\^", Key::Ctrl_F7 }, 326 | //{ "\033[21\^", Key::Ctrl_F8 }, 327 | //{ "\033[23\^", Key::Ctrl_F9 }, 328 | //{ "\033[24\^", Key::Ctrl_F10 }, 329 | //{ "\033[25\^", Key::Ctrl_F11 }, 330 | //{ "\033[26\^", Key::Ctrl_F12 }, 331 | 332 | // scoansi 333 | { "\033[k", Key::Ctrl_F1 }, 334 | { "\033[l", Key::Ctrl_F2 }, 335 | { "\033[m", Key::Ctrl_F3 }, 336 | { "\033[n", Key::Ctrl_F4 }, 337 | { "\033[o", Key::Ctrl_F5 }, 338 | { "\033[p", Key::Ctrl_F6 }, 339 | { "\033[q", Key::Ctrl_F7 }, 340 | { "\033[r", Key::Ctrl_F8 }, 341 | { "\033[s", Key::Ctrl_F9 }, 342 | { "\033[t", Key::Ctrl_F10 }, 343 | { "\033[u", Key::Ctrl_F11 }, 344 | { "\033[v", Key::Ctrl_F12 }, 345 | // -------------------------- kcbt -------------------------- 346 | // ansi scoansi screen xterm xterm-16color xterm-256color xterm-new linux nsterm rxvt 347 | { "\033[Z", Key::Shift_Tab }, 348 | 349 | // Alt + character 350 | { "\033a", Key::Alt_a }, 351 | { "\033b", Key::Alt_b }, 352 | { "\033c", Key::Alt_c }, 353 | { "\033d", Key::Alt_d }, 354 | { "\033e", Key::Alt_e }, 355 | { "\033f", Key::Alt_f }, 356 | { "\033g", Key::Alt_g }, 357 | { "\033h", Key::Alt_h }, 358 | { "\033i", Key::Alt_i }, 359 | { "\033j", Key::Alt_j }, 360 | { "\033k", Key::Alt_k }, 361 | { "\033l", Key::Alt_l }, 362 | { "\033m", Key::Alt_m }, 363 | { "\033n", Key::Alt_n }, 364 | { "\033o", Key::Alt_o }, 365 | { "\033p", Key::Alt_p }, 366 | { "\033q", Key::Alt_q }, 367 | { "\033r", Key::Alt_r }, 368 | { "\033s", Key::Alt_s }, 369 | { "\033t", Key::Alt_t }, 370 | { "\033u", Key::Alt_u }, 371 | { "\033v", Key::Alt_v }, 372 | { "\033w", Key::Alt_w }, 373 | { "\033x", Key::Alt_x }, 374 | { "\033y", Key::Alt_y }, 375 | { "\033z", Key::Alt_z }, 376 | 377 | { "\033A", Key::Alt_A }, 378 | { "\033B", Key::Alt_B }, 379 | { "\033C", Key::Alt_C }, 380 | { "\033D", Key::Alt_D }, 381 | { "\033E", Key::Alt_E }, 382 | { "\033F", Key::Alt_F }, 383 | { "\033G", Key::Alt_G }, 384 | { "\033H", Key::Alt_H }, 385 | { "\033I", Key::Alt_I }, 386 | { "\033J", Key::Alt_J }, 387 | { "\033K", Key::Alt_K }, 388 | { "\033L", Key::Alt_L }, 389 | { "\033M", Key::Alt_M }, 390 | { "\033N", Key::Alt_N }, 391 | { "\033O", Key::Alt_O }, 392 | { "\033P", Key::Alt_P }, 393 | { "\033Q", Key::Alt_Q }, 394 | { "\033R", Key::Alt_R }, 395 | { "\033S", Key::Alt_S }, 396 | { "\033T", Key::Alt_T }, 397 | { "\033U", Key::Alt_U }, 398 | { "\033V", Key::Alt_V }, 399 | { "\033W", Key::Alt_W }, 400 | { "\033X", Key::Alt_X }, 401 | { "\033Y", Key::Alt_Y }, 402 | { "\033Z", Key::Alt_Z }, 403 | }; 404 | } 405 | 406 | void Tty::restoreOrigTerminal() { 407 | if ( tcsetattr(term_stdin_, TCSANOW, &orig_term_) == -1 ) { 408 | Error::getInstance().appendError(ErrorMessage); 409 | std::exit(EXIT_FAILURE); 410 | } 411 | } 412 | 413 | void Tty::restoreOrigTerminal_s() { 414 | if ( tcsetattr(term_stdin_, TCSANOW, &orig_term_) == -1 ) { 415 | const char* msg = "tcsetattr error in restoreOrigTerminal_s()"; 416 | write(STDERR_FILENO, msg, strlen(msg)); 417 | std::_Exit(EXIT_FAILURE); 418 | } 419 | } 420 | 421 | void Tty::setNewTerminal() { 422 | if ( tcsetattr(term_stdin_, TCSANOW, &new_term_) == -1 ) { 423 | Error::getInstance().appendError(ErrorMessage); 424 | std::exit(EXIT_FAILURE); 425 | } 426 | } 427 | 428 | enum class KeyType 429 | { 430 | Empty, 431 | Char, 432 | Control, 433 | EscCode 434 | }; 435 | 436 | std::tuple Tty::getchar() { 437 | static char left_char = -1; 438 | static char last_char = -1; 439 | static KeyType last_type = KeyType::Empty; 440 | 441 | std::string esc_code; 442 | if ( left_char == '\033' ) { 443 | esc_code.push_back(left_char); 444 | } 445 | else if ( std::iscntrl(left_char) ) { 446 | auto key = left_char; 447 | left_char = -1; 448 | last_type = KeyType::Control; 449 | return std::make_tuple(static_cast(key), std::string()); 450 | } 451 | 452 | bool has_esc = left_char == '\033'; 453 | while ( running_.load(std::memory_order_relaxed) ) { 454 | char ch; 455 | auto n = read(term_stdin_, &ch, 1); 456 | if ( n > 0 ) { 457 | bool is_same = ch == last_char; 458 | bool valid = last_char != -1; 459 | last_char = ch; 460 | left_char = -1; 461 | if ( !has_esc ) { 462 | if ( std::iscntrl(ch) ) { 463 | if ( last_type == KeyType::Char || ( valid && !is_same ) ) { 464 | last_type = KeyType::Empty; 465 | left_char = ch; 466 | return std::make_tuple(Key::Timeout, std::string(1, ch)); 467 | } 468 | 469 | if ( ch == '\033' ) { 470 | has_esc = true; 471 | esc_code.push_back(ch); 472 | } 473 | else { 474 | last_type = KeyType::Control; 475 | return std::make_tuple(static_cast(ch), std::string()); 476 | } 477 | } 478 | else { 479 | last_type = KeyType::Char; 480 | return std::make_tuple(Key::Char, std::string(1, ch)); 481 | } 482 | } 483 | else { 484 | if ( std::iscntrl(ch) ) { 485 | left_char = ch; 486 | if ( esc_code.length() == 1 ) { // e.g. \033\033 487 | last_type = KeyType::Control; 488 | return std::make_tuple(Key::ESC, std::string()); 489 | } 490 | else { // e.g. \033[A\033[B 491 | last_type = KeyType::EscCode; 492 | auto iter = esc_codes_.find(esc_code); 493 | if ( iter != esc_codes_.end() ) { 494 | return std::make_tuple(iter->second, std::move(esc_code)); 495 | } 496 | else { 497 | auto k = Key::Unknown; 498 | auto xy = _mouseTracking(esc_code, k); 499 | if ( k != Key::Unknown ) { 500 | return std::make_tuple(k, std::move(xy)); 501 | } 502 | else { 503 | _getCursorPos(esc_code); 504 | return std::make_tuple(Key::Unknown, std::string()); 505 | } 506 | } 507 | } 508 | } 509 | else { 510 | esc_code.push_back(ch); 511 | auto k = Key::Unknown; 512 | auto xy = _mouseTracking(esc_code, k); 513 | if ( k != Key::Unknown ) { 514 | return std::make_tuple(k, std::move(xy)); 515 | } 516 | if ( _getCursorPos(esc_code) ) { 517 | return std::make_tuple(Key::Unknown, std::string()); 518 | } 519 | } 520 | } 521 | } 522 | else if ( n == 0 ) { // time out 523 | last_char = -1; 524 | left_char = -1; 525 | if ( !esc_code.empty() ) { 526 | if ( esc_code.length() == 1 ) { 527 | last_type = KeyType::Control; 528 | return std::make_tuple(Key::ESC, std::string()); 529 | } 530 | else { // e.g. \033[A 531 | last_type = KeyType::EscCode; 532 | auto iter = esc_codes_.find(esc_code); 533 | if ( iter != esc_codes_.end() ) { 534 | return std::make_tuple(iter->second, std::move(esc_code)); 535 | } 536 | else { 537 | auto k = Key::Unknown; 538 | auto xy = _mouseTracking(esc_code, k); 539 | if ( k != Key::Unknown ) { 540 | return std::make_tuple(k, std::move(xy)); 541 | } 542 | else { 543 | _getCursorPos(esc_code); 544 | return std::make_tuple(Key::Unknown, std::string()); 545 | } 546 | } 547 | } 548 | } 549 | else if ( last_type != KeyType::Empty ) { 550 | last_type = KeyType::Empty; 551 | return std::make_tuple(Key::Timeout, std::string()); 552 | } 553 | } 554 | else { // n < 0 555 | Error::getInstance().appendError(ErrorMessage); 556 | std::exit(EXIT_FAILURE); 557 | } 558 | } 559 | 560 | return std::make_tuple(Key::Exit, std::string()); 561 | 562 | } 563 | 564 | // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking 565 | std::string Tty::_mouseTracking(const std::string& esc_code, Key& key) const { 566 | using namespace std::chrono; 567 | 568 | static std::string orig_xy; 569 | static auto start_time = steady_clock::now(); 570 | 571 | std::smatch sm; 572 | // mouse enabled by \033[?1000h 573 | if ( esc_code.length() == 6 && esc_code.compare(0, 3, "\033[M") == 0 ) { 574 | std::string xy; 575 | xy.append(std::to_string(esc_code[4] - 32)); 576 | xy.append(";"); 577 | xy.append(std::to_string(esc_code[5] - 32)); 578 | switch ( esc_code[3] ) 579 | { 580 | case 32: 581 | { 582 | auto cur_time = steady_clock::now(); 583 | if ( orig_xy != xy || duration_cast(cur_time - start_time).count() > 500 ) { 584 | orig_xy = xy; 585 | key = Key::Single_Click; 586 | } 587 | else { 588 | key = Key::Double_Click; 589 | } 590 | start_time = cur_time; 591 | } 592 | return xy; 593 | case 33: 594 | key = Key::Right_Click; 595 | return xy; 596 | case 34: 597 | key = Key::Middle_Click; 598 | return xy; 599 | case 35: // button release 600 | break; 601 | case 36: 602 | key = Key::Shift_LeftMouse; 603 | return xy; 604 | case 48: 605 | key = Key::Ctrl_LeftMouse; 606 | return xy; 607 | case 49: // ctrl + right click 608 | break; 609 | case 96: 610 | key = Key::Wheel_Up; 611 | return xy; 612 | case 97: 613 | key = Key::Wheel_Down; 614 | return xy; 615 | default: 616 | break; 617 | } 618 | } 619 | // mouse enabled by \033[?1006h 620 | else if ( std::regex_match(esc_code, sm, std::regex("\033\\[<(\\d+);(\\d+;\\d+)M")) ) { 621 | auto xy = sm.str(2); 622 | switch ( std::stol(sm.str(1)) ) 623 | { 624 | case 0: 625 | { 626 | auto cur_time = steady_clock::now(); 627 | if ( orig_xy != xy || duration_cast(cur_time - start_time).count() > 500 ) { 628 | orig_xy = xy; 629 | key = Key::Single_Click; 630 | } 631 | else { 632 | key = Key::Double_Click; 633 | } 634 | start_time = cur_time; 635 | } 636 | return xy; 637 | case 1: 638 | key = Key::Right_Click; 639 | return xy; 640 | case 2: 641 | key = Key::Middle_Click; 642 | return xy; 643 | case 4: 644 | key = Key::Shift_LeftMouse; 645 | return xy; 646 | case 16: 647 | key = Key::Ctrl_LeftMouse; 648 | return xy; 649 | case 17: // ctrl + right click 650 | break; 651 | case 64: 652 | key = Key::Wheel_Up; 653 | return xy; 654 | case 65: 655 | key = Key::Wheel_Down; 656 | return xy; 657 | default: 658 | break; 659 | } 660 | } 661 | 662 | return esc_code; 663 | } 664 | 665 | bool Tty::_getCursorPos(const std::string& esc_code) { 666 | std::smatch sm; 667 | if ( cursor_pos_ == true && std::regex_match(esc_code, sm, std::regex("\033\\[(\\d+);(\\d+)R")) ) { 668 | cursor_line_ = stoi(sm.str(1)); 669 | cursor_col_ = stoi(sm.str(2)); 670 | std::lock_guard lock(mutex_); 671 | cursor_pos_ = false; 672 | cursor_cond_.notify_one(); 673 | return true; 674 | } 675 | return false; 676 | } 677 | 678 | } // end namespace leaf 679 | --------------------------------------------------------------------------------