Editing multi-line content in a terminal environment involves a lot of

trade-offs. When LLDB's multi-line editing support was first introduced
for expressions / REPL contexts the behavior was as follows:

* The Return key is treated as a line-break except at the end of the input
  buffer, where a completeness test is applied

This worked well enough when writing code, and makes it trivial to insert
new lines above code you've already typed. Just use cursor navigation to
move up and type freely. Where it was awkward is that the gesture to insert
a line break and end editing is conflated for most people. Sometimes you
want Return to end the editing session and other times you want to insert
a line break.

This commit changes the behavior as follows:

* The Return key is treated as the end of editing except at the end of the
  input buffer, where a completeness test is applied

* The Meta+Return sequence is always treated as a line break. This is
  consistent with conventions in Facebook and elsewhere since
  Alt/Option+Return is often mapped to Meta+Return. The unfortunate
  exception is on macOS where this *can* be the case, but isn't by
  default. Sigh.

Note that by design both before and after the patch pasting a Return
character always introduces a line break.

<rdar://problem/26886287>

llvm-svn: 275482
This commit is contained in:
Kate Stone 2016-07-14 22:00:04 +00:00
parent 6c35b2dea4
commit 240414dc95
2 changed files with 97 additions and 41 deletions

View File

@ -279,11 +279,15 @@ namespace lldb_private {
/// Prompt implementation for EditLine.
const char *
Prompt();
/// Line break command used when return is pressed in multi-line mode.
/// Line break command used when meta+return is pressed in multi-line mode.
unsigned char
BreakLineCommand (int ch);
/// Command used when return is pressed in multi-line mode.
unsigned char
EndOrAddLineCommand(int ch);
/// Delete command used when delete is pressed in multi-line mode.
unsigned char
DeleteNextCharCommand (int ch);
@ -299,7 +303,15 @@ namespace lldb_private {
/// Line navigation command used when ^N or down arrow are pressed in multi-line mode.
unsigned char
NextLineCommand (int ch);
/// History navigation command used when Alt + up arrow is pressed in multi-line mode.
unsigned char
PreviousHistoryCommand(int ch);
/// History navigation command used when Alt + down arrow is pressed in multi-line mode.
unsigned char
NextHistoryCommand(int ch);
/// Buffer start command used when Esc < is typed in multi-line emacs mode.
unsigned char
BufferStartCommand (int ch);

View File

@ -657,41 +657,9 @@ Editline::BreakLineCommand (int ch)
// Establish the new cursor position at the start of a line when inserting a line break
m_revert_cursor_index = 0;
// Don't perform end of input detection or automatic formatting when pasting
// Don't perform automatic formatting when pasting
if (!IsInputPending (m_input_file))
{
// If this is the end of the last line, treat this as a potential exit
if (m_current_line_index == m_input_lines.size() - 1 && new_line_fragment.length() == 0)
{
bool end_of_input = true;
if (m_is_input_complete_callback)
{
SaveEditedLine();
auto lines = GetInputAsStringList();
end_of_input = m_is_input_complete_callback (this, lines, m_is_input_complete_callback_baton);
// The completion test is allowed to change the input lines when complete
if (end_of_input)
{
m_input_lines.clear();
for (unsigned index = 0; index < lines.GetSize(); index++)
{
#if LLDB_EDITLINE_USE_WCHAR
m_input_lines.insert (m_input_lines.end(), m_utf8conv.from_bytes (lines[index]));
#else
m_input_lines.insert (m_input_lines.end(), lines[index]);
#endif
}
}
}
if (end_of_input)
{
fprintf (m_output_file, "\n");
m_editor_status = EditorStatus::Complete;
return CC_NEWLINE;
}
}
// Apply smart indentation
if (m_fix_indentation_callback)
{
@ -720,7 +688,49 @@ Editline::BreakLineCommand (int ch)
}
unsigned char
Editline::DeleteNextCharCommand (int ch)
Editline::EndOrAddLineCommand(int ch)
{
// Don't perform end of input detection when pasting, always treat this as a line break
if (IsInputPending(m_input_file))
{
return BreakLineCommand(ch);
}
// Save any edits to this line
SaveEditedLine();
// If this is the end of the last line, consider whether to add a line instead
const LineInfoW *info = el_wline(m_editline);
if (m_current_line_index == m_input_lines.size() - 1 && info->cursor == info->lastchar)
{
if (m_is_input_complete_callback)
{
auto lines = GetInputAsStringList();
if (!m_is_input_complete_callback(this, lines, m_is_input_complete_callback_baton))
{
return BreakLineCommand(ch);
}
// The completion test is allowed to change the input lines when complete
m_input_lines.clear();
for (unsigned index = 0; index < lines.GetSize(); index++)
{
#if LLDB_EDITLINE_USE_WCHAR
m_input_lines.insert(m_input_lines.end(), m_utf8conv.from_bytes(lines[index]));
#else
m_input_lines.insert(m_input_lines.end(), lines[index]);
#endif
}
}
}
MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockEnd);
fprintf(m_output_file, "\n");
m_editor_status = EditorStatus::Complete;
return CC_NEWLINE;
}
unsigned char
Editline::DeleteNextCharCommand(int ch)
{
LineInfoW * info = const_cast<LineInfoW *>(el_wline (m_editline));
@ -860,7 +870,23 @@ Editline::NextLineCommand (int ch)
}
unsigned char
Editline::FixIndentationCommand (int ch)
Editline::PreviousHistoryCommand(int ch)
{
SaveEditedLine();
return RecallHistory(true);
}
unsigned char
Editline::NextHistoryCommand(int ch)
{
SaveEditedLine();
return RecallHistory(false);
}
unsigned char
Editline::FixIndentationCommand(int ch)
{
if (!m_fix_indentation_callback)
return CC_NORM;
@ -1077,6 +1103,10 @@ Editline::ConfigureEditor (bool multiline)
el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-break-line"), EditLineConstString("Insert a line break"),
(EditlineCommandCallbackType)(
[](EditLine *editline, int ch) { return Editline::InstanceFor(editline)->BreakLineCommand(ch); }));
el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-end-or-add-line"),
EditLineConstString("End editing or continue when incomplete"),
(EditlineCommandCallbackType)(
[](EditLine *editline, int ch) { return Editline::InstanceFor(editline)->EndOrAddLineCommand(ch); }));
el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-delete-next-char"),
EditLineConstString("Delete next character"), (EditlineCommandCallbackType)([](EditLine *editline, int ch) {
return Editline::InstanceFor(editline)->DeleteNextCharCommand(ch);
@ -1093,6 +1123,14 @@ Editline::ConfigureEditor (bool multiline)
el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-next-line"), EditLineConstString("Move to next line"),
(EditlineCommandCallbackType)(
[](EditLine *editline, int ch) { return Editline::InstanceFor(editline)->NextLineCommand(ch); }));
el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-previous-history"),
EditLineConstString("Move to previous history"),
(EditlineCommandCallbackType)([](EditLine *editline, int ch) {
return Editline::InstanceFor(editline)->PreviousHistoryCommand(ch);
}));
el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-next-history"), EditLineConstString("Move to next history"),
(EditlineCommandCallbackType)(
[](EditLine *editline, int ch) { return Editline::InstanceFor(editline)->NextHistoryCommand(ch); }));
el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-buffer-start"),
EditLineConstString("Move to start of buffer"),
(EditlineCommandCallbackType)(
@ -1149,8 +1187,10 @@ Editline::ConfigureEditor (bool multiline)
// Multi-line editor bindings
if (multiline)
{
el_set (m_editline, EL_BIND, "\n", "lldb-break-line", NULL);
el_set (m_editline, EL_BIND, "\r", "lldb-break-line", NULL);
el_set(m_editline, EL_BIND, "\n", "lldb-end-or-add-line", NULL);
el_set(m_editline, EL_BIND, "\r", "lldb-end-or-add-line", NULL);
el_set(m_editline, EL_BIND, ESCAPE "\n", "lldb-break-line", NULL);
el_set(m_editline, EL_BIND, ESCAPE "\r", "lldb-break-line", NULL);
el_set (m_editline, EL_BIND, "^p", "lldb-previous-line", NULL);
el_set (m_editline, EL_BIND, "^n", "lldb-next-line", NULL);
el_set (m_editline, EL_BIND, "^?", "lldb-delete-previous-char", NULL);
@ -1165,6 +1205,10 @@ Editline::ConfigureEditor (bool multiline)
el_set (m_editline, EL_BIND, ESCAPE ">", "lldb-buffer-end", NULL);
el_set (m_editline, EL_BIND, ESCAPE "[A", "lldb-previous-line", NULL);
el_set (m_editline, EL_BIND, ESCAPE "[B", "lldb-next-line", NULL);
el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[A", "lldb-previous-history", NULL);
el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[B", "lldb-next-history", NULL);
el_set(m_editline, EL_BIND, ESCAPE "[1;3A", "lldb-previous-history", NULL);
el_set(m_editline, EL_BIND, ESCAPE "[1;3B", "lldb-next-history", NULL);
}
else
{