Skip to content

Commit 7d80113

Browse files
committed
[lldb/Driver] Support terminal resizing
Summary: The comment in the Editine.h header made it sound like editline was just unable to handle terminal resizing. We were not ever telling editline that the terminal had changed size, which might explain why it wasn't working. This patch threads a `TerminalSizeChanged()` callback through the IOHandler and invokes it from the SIGWINCH handler in the driver. Our `Editline` class already had a `TerminalSizeChanged()` method which was invoked only when editline was configured. This patch also changes `Editline` to not apply the changes right away in `TerminalSizeChanged()`, but instead defer that to the next character read. During my testing, it happened once that the signal was received while our `ConnectionFileDescriptor::Read` was allocating memory. As `el_resize` seems to allocate memory too, this crashed. Reviewers: labath, teemperor Subscribers: lldb-commits Tags: #lldb Differential Revision: https://reviews.llvm.org/D79654 (cherry picked from commit d9166ad)
1 parent fc78685 commit 7d80113

File tree

6 files changed

+86
-31
lines changed

6 files changed

+86
-31
lines changed

lldb/include/lldb/Core/IOHandler.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ class IOHandler {
9595

9696
virtual void Deactivate() { m_active = false; }
9797

98+
virtual void TerminalSizeChanged() {}
99+
98100
virtual const char *GetPrompt() {
99101
// Prompt support isn't mandatory
100102
return nullptr;
@@ -369,6 +371,8 @@ class IOHandlerEditline : public IOHandler {
369371

370372
void Deactivate() override;
371373

374+
void TerminalSizeChanged() override;
375+
372376
ConstString GetControlSequence(char ch) override {
373377
return m_delegate.IOHandlerGetControlSequence(ch);
374378
}

lldb/include/lldb/Host/Editline.h

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,10 @@
1919
// good amount of the text will
2020
// disappear. It's still in the buffer, just invisible.
2121
// b) The prompt printing logic for dealing with ANSI formatting characters is
22-
// broken, which is why we're
23-
// working around it here.
24-
// c) When resizing the terminal window, if the cursor moves between rows
25-
// libedit will get confused. d) The incremental search uses escape to cancel
26-
// input, so it's confused by
22+
// broken, which is why we're working around it here.
23+
// c) The incremental search uses escape to cancel input, so it's confused by
2724
// ANSI sequences starting with escape.
28-
// e) Emoji support is fairly terrible, presumably it doesn't understand
25+
// d) Emoji support is fairly terrible, presumably it doesn't understand
2926
// composed characters?
3027

3128
#ifndef liblldb_Editline_h_
@@ -50,6 +47,7 @@
5047
#include <histedit.h>
5148
#endif
5249

50+
#include <csignal>
5351
#include <mutex>
5452
#include <string>
5553
#include <vector>
@@ -171,9 +169,7 @@ class Editline {
171169
/// editing scenarios.
172170
void SetContinuationPrompt(const char *continuation_prompt);
173171

174-
/// Required to update the width of the terminal registered for I/O. It is
175-
/// critical that this
176-
/// be correct at all times.
172+
/// Call when the terminal size changes
177173
void TerminalSizeChanged();
178174

179175
/// Returns the prompt established by SetPrompt()
@@ -328,6 +324,8 @@ class Editline {
328324

329325
bool CompleteCharacter(char ch, EditLineGetCharType &out);
330326

327+
void ApplyTerminalSizeChange();
328+
331329
private:
332330
#if LLDB_EDITLINE_USE_WCHAR
333331
std::wstring_convert<std::codecvt_utf8<wchar_t>> m_utf8conv;
@@ -350,6 +348,7 @@ class Editline {
350348
std::string m_set_continuation_prompt;
351349
std::string m_current_prompt;
352350
bool m_needs_prompt_repaint = false;
351+
volatile std::sig_atomic_t m_terminal_size_has_changed = 0;
353352
std::string m_editor_name;
354353
FILE *m_input_file;
355354
FILE *m_output_file;

lldb/source/Core/Debugger.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ uint32_t Debugger::GetTerminalWidth() const {
388388
}
389389

390390
bool Debugger::SetTerminalWidth(uint32_t term_width) {
391+
if (auto handler_sp = m_io_handler_stack.Top())
392+
handler_sp->TerminalSizeChanged();
393+
391394
const uint32_t idx = ePropertyTerminalWidth;
392395
return m_collection_sp->SetPropertyAtIndexAsSInt64(nullptr, idx, term_width);
393396
}

lldb/source/Core/IOHandler.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,12 @@ void IOHandlerEditline::Deactivate() {
287287
m_delegate.IOHandlerDeactivated(*this);
288288
}
289289

290+
void IOHandlerEditline::TerminalSizeChanged() {
291+
#if LLDB_ENABLE_LIBEDIT
292+
m_editline_up->TerminalSizeChanged();
293+
#endif
294+
}
295+
290296
// Split out a line from the buffer, if there is a full one to get.
291297
static Optional<std::string> SplitLine(std::string &line_buffer) {
292298
size_t pos = line_buffer.find('\n');

lldb/source/Host/common/Editline.cpp

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,9 @@ int Editline::GetCharacter(EditLineGetCharType *c) {
563563
lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess;
564564
char ch = 0;
565565

566+
if (m_terminal_size_has_changed)
567+
ApplyTerminalSizeChange();
568+
566569
// This mutex is locked by our caller (GetLine). Unlock it while we read a
567570
// character (blocking operation), so we do not hold the mutex
568571
// indefinitely. This gives a chance for someone to interrupt us. After
@@ -1055,7 +1058,7 @@ void Editline::ConfigureEditor(bool multiline) {
10551058

10561059
m_editline =
10571060
el_init(m_editor_name.c_str(), m_input_file, m_output_file, m_error_file);
1058-
TerminalSizeChanged();
1061+
ApplyTerminalSizeChange();
10591062

10601063
if (m_history_sp && m_history_sp->IsValid()) {
10611064
if (!m_history_sp->Load()) {
@@ -1308,28 +1311,32 @@ void Editline::SetContinuationPrompt(const char *continuation_prompt) {
13081311
continuation_prompt == nullptr ? "" : continuation_prompt;
13091312
}
13101313

1311-
void Editline::TerminalSizeChanged() {
1312-
if (m_editline != nullptr) {
1313-
el_resize(m_editline);
1314-
int columns;
1315-
// This function is documenting as taking (const char *, void *) for the
1316-
// vararg part, but in reality in was consuming arguments until the first
1317-
// null pointer. This was fixed in libedit in April 2019
1318-
// <http://mail-index.netbsd.org/source-changes/2019/04/26/msg105454.html>,
1319-
// but we're keeping the workaround until a version with that fix is more
1320-
// widely available.
1321-
if (el_get(m_editline, EL_GETTC, "co", &columns, nullptr) == 0) {
1322-
m_terminal_width = columns;
1323-
if (m_current_line_rows != -1) {
1324-
const LineInfoW *info = el_wline(m_editline);
1325-
int lineLength =
1326-
(int)((info->lastchar - info->buffer) + GetPromptWidth());
1327-
m_current_line_rows = (lineLength / columns) + 1;
1328-
}
1329-
} else {
1330-
m_terminal_width = INT_MAX;
1331-
m_current_line_rows = 1;
1314+
void Editline::TerminalSizeChanged() { m_terminal_size_has_changed = 1; }
1315+
1316+
void Editline::ApplyTerminalSizeChange() {
1317+
if (!m_editline)
1318+
return;
1319+
1320+
m_terminal_size_has_changed = 0;
1321+
el_resize(m_editline);
1322+
int columns;
1323+
// This function is documenting as taking (const char *, void *) for the
1324+
// vararg part, but in reality in was consuming arguments until the first
1325+
// null pointer. This was fixed in libedit in April 2019
1326+
// <http://mail-index.netbsd.org/source-changes/2019/04/26/msg105454.html>,
1327+
// but we're keeping the workaround until a version with that fix is more
1328+
// widely available.
1329+
if (el_get(m_editline, EL_GETTC, "co", &columns, nullptr) == 0) {
1330+
m_terminal_width = columns;
1331+
if (m_current_line_rows != -1) {
1332+
const LineInfoW *info = el_wline(m_editline);
1333+
int lineLength =
1334+
(int)((info->lastchar - info->buffer) + GetPromptWidth());
1335+
m_current_line_rows = (lineLength / columns) + 1;
13321336
}
1337+
} else {
1338+
m_terminal_width = INT_MAX;
1339+
m_current_line_rows = 1;
13331340
}
13341341
}
13351342

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Test resizing in our IOHandlers.
3+
"""
4+
5+
import os
6+
7+
import lldb
8+
from lldbsuite.test.decorators import *
9+
from lldbsuite.test.lldbtest import *
10+
from lldbsuite.test.lldbpexpect import PExpectTest
11+
12+
class IOHandlerCompletionTest(PExpectTest):
13+
14+
mydir = TestBase.compute_mydir(__file__)
15+
16+
# PExpect uses many timeouts internally and doesn't play well
17+
# under ASAN on a loaded machine..
18+
@skipIfAsan
19+
@skipIfEditlineSupportMissing
20+
def test_resize(self):
21+
22+
# Start with a small window
23+
self.launch(dimensions=(10,10))
24+
25+
self.child.send("his is a long sentence missing its first letter.")
26+
27+
# Now resize to something bigger
28+
self.child.setwinsize(100,500)
29+
30+
# Hit "left" 60 times (to go to the beginning of the line) and insert
31+
# a character.
32+
self.child.send(60 * "\033[D")
33+
self.child.send("T")
34+
35+
self.child.expect_exact("(lldb) This is a long sentence missing its first letter.")
36+
self.quit()

0 commit comments

Comments
 (0)