xaizek / pipedial (License: GPLv3 only) (since 2019-01-08)
One more tool for selecting something in console.
Commit 125650b8d7fa068955ec29a198a86474f0862f33

Add `e` key (edit and accept)
Author: xaizek
Author date (UTC): 2019-01-18 15:57
Committer name: xaizek
Committer date (UTC): 2019-01-24 13:53
Parent(s): 70ce3e4d1b6c3c9c688161d6092dee5e59513f86
Signing key: 99DC5E4DB05F6BE2
Tree: 2cec0200ee5d3ea691d773125796651f895630cd
File Lines added Lines deleted
Makefile 1 1
README.md 2 0
docs/04-shortcuts.md 2 0
docs/pipedial.1 3 1
src/PromptRequest.cpp 210 0
src/PromptRequest.hpp 104 0
src/main.cpp 22 0
File Makefile changed (mode: 100644) (index cd222e5..621b1ad)
1 1 CFLAGS += -Wall -Wextra -Werror -MMD -Isrc/ -Ilibs/ CFLAGS += -Wall -Wextra -Werror -MMD -Isrc/ -Ilibs/
2 2 CXXFLAGS += -std=c++11 $(CFLAGS) CXXFLAGS += -std=c++11 $(CFLAGS)
3 LDFLAGS += -lcursesw
3 LDFLAGS += -lcursesw -lreadline
4 4
5 5 INSTALL := install -D INSTALL := install -D
6 6 GZIP := gzip GZIP := gzip
File README.md changed (mode: 100644) (index 3b894a8..48a899b)
... ... Expected to work in \*nix like environments.
32 32 * [GNU Make][make] * [GNU Make][make]
33 33 * C++11 compatible compiler (e.g. GCC 4.9.3) * C++11 compatible compiler (e.g. GCC 4.9.3)
34 34 * [curses library][curses] with support of wide characters * [curses library][curses] with support of wide characters
35 * [GNU Readline library][readline]
35 36 * (optional) [pandoc][pandoc] for regenerating man page * (optional) [pandoc][pandoc] for regenerating man page
36 37
37 38 ### License ### ### License ###
 
... ... GNU General Public License, version 3 or later.
41 42 [sentaku]: https://github.com/rcmdnk/sentaku [sentaku]: https://github.com/rcmdnk/sentaku
42 43 [make]: https://www.gnu.org/software/make/ [make]: https://www.gnu.org/software/make/
43 44 [curses]: https://en.wikipedia.org/wiki/Curses_(programming_library) [curses]: https://en.wikipedia.org/wiki/Curses_(programming_library)
45 [readline]: https://tiswww.case.edu/php/chet/readline/rltop.html
44 46 [pandoc]: https://pandoc.org/ [pandoc]: https://pandoc.org/
File docs/04-shortcuts.md changed (mode: 100644) (index 21112a2..2b0df43)
... ... Normal Mode
12 12
13 13 **{Enter}** -- select current item and quit. **{Enter}** -- select current item and quit.
14 14
15 **e** -- edit current item and accept the result.
16
15 17 **[count]gg** -- put cursor on the first or **[count]**-th element. **[count]gg** -- put cursor on the first or **[count]**-th element.
16 18
17 19 **[count]G** -- put cursor on the last or **[count]**-th element. **[count]G** -- put cursor on the last or **[count]**-th element.
File docs/pipedial.1 changed (mode: 100644) (index 2f6b017..2226281)
1 1 .\" Automatically generated by Pandoc 1.17.0.3 .\" Automatically generated by Pandoc 1.17.0.3
2 2 .\" .\"
3 .TH "pipedial" "1" "January 13, 2019" "" ""
3 .TH "pipedial" "1" "January 18, 2019" "" ""
4 4 .hy .hy
5 5 .SH NAME .SH NAME
6 6 .PP .PP
 
... ... Display version information.
34 34 .PP .PP
35 35 \f[B]{Enter}\f[] \-\- select current item and quit. \f[B]{Enter}\f[] \-\- select current item and quit.
36 36 .PP .PP
37 \f[B]e\f[] \-\- edit current item and accept the result.
38 .PP
37 39 \f[B][count]gg\f[] \-\- put cursor on the first or \f[B][count]\f[]\-th \f[B][count]gg\f[] \-\- put cursor on the first or \f[B][count]\f[]\-th
38 40 element. element.
39 41 .PP .PP
File src/PromptRequest.cpp added (mode: 100644) (index 0000000..4758e2d)
1 // pipedial -- terminal element picker
2 // Copyright (C) 2019 xaizek <xaizek@posteo.net>
3 //
4 // This file is part of pipedial.
5 //
6 // pipedial is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // pipedial is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with pipedial. If not, see <https://www.gnu.org/licenses/>.
18
19 #include "PromptRequest.hpp"
20
21 #include <stdio.h>
22
23 #include <csignal>
24 #include <cstdlib>
25 #include <cstring>
26
27 #include <memory>
28 #include <stdexcept>
29 #include <string>
30 #include <utility>
31
32 #include <readline/readline.h>
33
34 #include "cursed/Input.hpp"
35 #include "cursed/Prompt.hpp"
36 #include "cursed/Screen.hpp"
37
38 #include "utils.hpp"
39
40 namespace {
41
42 // Saves and restores signal handling information.
43 class SignalSaver
44 {
45 public:
46 // Captures state of the signal.
47 explicit SignalSaver(int sigNum) : sigNum(sigNum)
48 {
49 if (sigaction(sigNum, nullptr, &sa) == -1) {
50 throw std::runtime_error("Failed wit save signal");
51 }
52 }
53
54 // Restores state of the signal.
55 ~SignalSaver()
56 {
57 (void)sigaction(sigNum, &sa, nullptr);
58 }
59
60 SignalSaver(const SignalSaver &rhs) = delete;
61 SignalSaver(SignalSaver &&rhs) = delete;
62 SignalSaver & operator=(const SignalSaver &rhs) = delete;
63 SignalSaver & operator=(SignalSaver &&rhs) = delete;
64
65 private:
66 int sigNum; // Signal number.
67 struct sigaction sa; // Saved signal handling information.
68 };
69
70 }
71
72 PromptResult::PromptResult() : hasInput(false)
73 { }
74
75 PromptResult::PromptResult(std::wstring result)
76 : result(std::move(result)), hasInput(true)
77 { }
78
79 PromptRequest::PromptRequest(cursed::Input &input, cursed::Screen &screen,
80 cursed::Prompt &promptArea)
81 : input(input), screen(screen), promptArea(promptArea)
82 {
83 screen.showCursor();
84 }
85
86 PromptRequest::~PromptRequest()
87 {
88 screen.hideCursor();
89 }
90
91 PromptResult
92 PromptRequest::prompt(const std::wstring &invitation,
93 const std::wstring &initial)
94 {
95 initialValue = toNarrow(initial);
96
97 static PromptRequest *instance;
98 instance = this;
99
100 // Prevent displaying completion menu, which could mess up output.
101 rl_completion_display_matches_hook = [](char *[], int, int) {};
102
103 // Disable filename completion.
104 rl_completion_entry_function = [](const char *, int) -> char * {
105 return nullptr;
106 };
107
108 rl_startup_hook = []() { instance->rlStartup(); return 0; };
109 rl_redisplay_function = []() { instance->rlRedisplay(); };
110 rl_input_available_hook = []() { return instance->rlInputAvailable(); };
111 rl_getc_function = [](FILE *){ return instance->rlGetcFunction(); };
112
113 rl_catch_sigwinch = 0;
114 rl_change_environment = 0;
115
116 SignalSaver sigwinchSaver(SIGWINCH);
117 std::unique_ptr<char, decltype(&std::free)> line {
118 readline(toNarrow(invitation).c_str()), &std::free
119 };
120
121 if (line == nullptr) {
122 return {};
123 }
124 return toWide(line.get());
125 }
126
127 void
128 PromptRequest::rlStartup()
129 {
130 rl_extend_line_buffer(initialValue.length() + 1U);
131 std::strcpy(rl_line_buffer, initialValue.c_str());
132 rl_point = initialValue.length();
133 rl_end = rl_point;
134 }
135
136 void
137 PromptRequest::rlRedisplay()
138 {
139 std::wstring prompt = toWide(rl_display_prompt);
140 std::wstring beforeCursor = toWide({ rl_line_buffer,
141 rl_line_buffer + rl_point });
142 std::wstring afterCursor = toWide({ rl_line_buffer + rl_point,
143 rl_line_buffer + rl_end });
144
145 int promptWidth = wcswidth(prompt.c_str(), prompt.length());
146 int cursorCol = promptWidth
147 + wcswidth(beforeCursor.c_str(), beforeCursor.length());
148
149 promptArea.setText(prompt + beforeCursor + afterCursor, cursorCol);
150 screen.draw();
151 }
152
153 int
154 PromptRequest::rlInputAvailable()
155 {
156 if (!inputBuf.empty()) {
157 return true;
158 }
159
160 while (cursed::InputElement ie = input.peek()) {
161 if (!ie.isTerminalResize()) {
162 return true;
163 }
164
165 updateScreen();
166 }
167
168 return false;
169 }
170
171 int
172 PromptRequest::rlGetcFunction()
173 {
174 fetchInput();
175 int c = inputBuf.front();
176 inputBuf.erase(inputBuf.cbegin());
177 return c;
178 }
179
180 void
181 PromptRequest::fetchInput()
182 {
183 if (!inputBuf.empty()) {
184 return;
185 }
186
187 while (true) {
188 cursed::InputElement ie = input.read();
189 if (ie.isEndOfInput()) {
190 inputBuf.push_back(EOF);
191 break;
192 } else if (ie.isTerminalResize()) {
193 updateScreen();
194 } else {
195 // Turn `wchar_t` into `char[]` to be able to return one byte at a
196 // time.
197 std::string narrow = toNarrow(std::wstring(1, ie));
198 inputBuf.insert(inputBuf.cend(), narrow.cbegin(), narrow.cend());
199 break;
200 }
201 }
202 }
203
204 void
205 PromptRequest::updateScreen()
206 {
207 rl_resize_terminal();
208 screen.resize();
209 screen.draw();
210 }
File src/PromptRequest.hpp added (mode: 100644) (index 0000000..ad27139)
1 // pipedial -- terminal element picker
2 // Copyright (C) 2019 xaizek <xaizek@posteo.net>
3 //
4 // This file is part of pipedial.
5 //
6 // pipedial is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // pipedial is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with pipedial. If not, see <https://www.gnu.org/licenses/>.
18
19 #ifndef PIPEDIAL__PROMPTREQUEST_HPP__
20 #define PIPEDIAL__PROMPTREQUEST_HPP__
21
22 #include <string>
23 #include <vector>
24
25 namespace cursed {
26
27 class Input;
28 class Prompt;
29 class Screen;
30
31 }
32
33 // Describes result of a prompt request.
34 class PromptResult
35 {
36 friend class PromptRequest;
37
38 private:
39 // No input was provided (request was declined/cancelled).
40 PromptResult();
41 // An input was provided.
42 PromptResult(std::wstring result);
43
44 public:
45 // Retrieves input.
46 std::wstring getResult() const
47 { return result; }
48
49 // Checks if result contains input.
50 operator bool() const
51 { return hasInput; }
52
53 private:
54 std::wstring result; // Input.
55 bool hasInput; // Whether input makes sense.
56 };
57
58 // Provides text editing facility via prompt.
59 class PromptRequest
60 {
61 public:
62 // Remembers arguments and makes cursor visible.
63 PromptRequest(cursed::Input &input, cursed::Screen &screen,
64 cursed::Prompt &promptArea);
65 // Cleans up and hides cursor.
66 ~PromptRequest();
67
68 PromptRequest(const PromptRequest &rhs) = delete;
69 PromptRequest(PromptRequest &&rhs) = delete;
70 PromptRequest & operator=(const PromptRequest &rhs) = delete;
71 PromptRequest & operator=(PromptRequest &&rhs) = delete;
72
73 public:
74 // Performs the prompting. Blocks until it's done. Might throw
75 // `std::runtime_error`.
76 PromptResult prompt(const std::wstring &invitation,
77 const std::wstring &initial);
78
79 private:
80 // Readline's callback for initial preparations.
81 void rlStartup();
82 // Readline's callback for drawing prompt.
83 void rlRedisplay();
84 // Readline's callback for checking for more input.
85 int rlInputAvailable();
86 // Readline's callback for getting input.
87 int rlGetcFunction();
88
89 // Reads more input if `inputBuf` is empty populating it as a result. After
90 // the function is invoked `inputBuf` is never empty.
91 void fetchInput();
92
93 // Resizes and redraws screen.
94 void updateScreen();
95
96 private:
97 cursed::Input &input; // Source of input events.
98 cursed::Screen &screen; // Screen manager.
99 cursed::Prompt &promptArea; // Widget that displays the prompt.
100 std::string initialValue; // Initial value of the prompt.
101 std::vector<int> inputBuf; // Input queue.
102 };
103
104 #endif // PIPEDIAL__PROMPTREQUEST_HPP__
File src/main.cpp changed (mode: 100644) (index 1480160..d681106)
31 31 #include "cursed/Label.hpp" #include "cursed/Label.hpp"
32 32 #include "cursed/List.hpp" #include "cursed/List.hpp"
33 33 #include "cursed/Pos.hpp" #include "cursed/Pos.hpp"
34 #include "cursed/Prompt.hpp"
34 35 #include "cursed/Screen.hpp" #include "cursed/Screen.hpp"
35 36 #include "cursed/Size.hpp" #include "cursed/Size.hpp"
36 37 #include "cursed/Text.hpp" #include "cursed/Text.hpp"
 
42 43 #include "vle/Modes.hpp" #include "vle/Modes.hpp"
43 44
44 45 #include "Args.hpp" #include "Args.hpp"
46 #include "PromptRequest.hpp"
45 47 #include "utils.hpp" #include "utils.hpp"
46 48
47 49 static std::wstring ui(const std::wstring &titleText, static std::wstring ui(const std::wstring &titleText,
 
... ... h -- enter Help mode.
62 64
63 65 {Enter} -- select current item and quit. {Enter} -- select current item and quit.
64 66
67 e -- edit current item and accept the result.
68
65 69 [count]gg -- put cursor on the first or [count]-th element. [count]gg -- put cursor on the first or [count]-th element.
66 70
67 71 [count]G -- put cursor on the last or [count]-th element. [count]G -- put cursor on the last or [count]-th element.
 
... ... ui(const std::wstring &titleText, std::vector<std::wstring> &&lines)
166 170 screen.setMainWidget(&help); screen.setMainWidget(&help);
167 171 }, },
168 172 "display help" }); "display help" });
173 normalMode.addShortcut({ L"e", [&]() {
174 cursed::Prompt prompt;
175 cursed::Track tmpTrack;
176 tmpTrack.addItem(&title);
177 tmpTrack.addItem(&list);
178 tmpTrack.addItem(&prompt);
179
180 PromptRequest request(input, screen, prompt);
181 screen.setMainWidget(&tmpTrack);
182 if (PromptResult r = request.prompt(L"Edit and accept: ",
183 list.getCurrent())) {
184 result = r.getResult();
185 quit = true;
186 } else {
187 screen.setMainWidget(&track);
188 }
189 },
190 "edit and accept" });
169 191
170 192 vle::Mode helpMode("help"); vle::Mode helpMode("help");
171 193 helpMode.addShortcut({ L"h", [&modes, &screen, &track]() { helpMode.addShortcut({ L"h", [&modes, &screen, &track]() {
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/pipedial

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/pipedial

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master