Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions Examples/UICatalog/Scenarios/BracketedPasteDemo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#nullable enable

namespace UICatalog.Scenarios;

[ScenarioMetadata ("Bracketed Paste", "Logs bracketed-paste payloads delivered by the terminal")]
[ScenarioCategory ("Text and Formatting")]
public sealed class BracketedPasteDemo : Scenario
{
public override void Main ()
{
ConfigurationManager.Enable (ConfigLocations.All);
using IApplication app = Application.Create ();
app.Init ();

using Window appWindow = new ()
{
Title = GetQuitKeyAndName ()
};
appWindow.AssignHotKeys = true;

Label hint = CreateHintLabel ();

TextField field = new ()
{
X = 0,
Y = Pos.Bottom (hint) + 1,
Width = Dim.Fill (),
Title = "Paste into the focused TextField — the default Command.Paste handler inserts the text."
};

Label log = new ()
{
X = 0,
Y = Pos.Bottom (field) + 1,
Width = Dim.Fill (),
Height = Dim.Fill (),
Text = "Application.Paste log (only bracketed paste events appear here):\n"
};

int counter = 0;

app.Paste += (_, args) =>
{
counter++;
log.Text += $"{FormatPasteLogEntry (counter, args.Text)}\n";
};

appWindow.Add (hint, field, log);

app.Run (appWindow);
}

public static Label CreateHintLabel ()
{
Label hint = new ()
{
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Auto (DimAutoStyle.Text),
Text = "Paste into the TextField below.\n"
+ "If Terminal.Gui detects bracketed paste, the paste is logged below as a single bracketed-paste event.\n"
+ "If text appears in the field but no new log entry is added, your terminal delivered it as normal input instead."
};

hint.TextFormatter.WordWrap = true;

return hint;
}

public static string FormatPasteLogEntry (int counter, string text)
{
return $"[{counter}] Bracketed paste event: {text.Length} chars: {Truncate (text)}";
}

private static string Truncate (string text)
{
// Replace control chars so the display stays one line per paste.
string flattened = text.Replace ("\r", "\\r").Replace ("\n", "\\n").Replace ("\t", "\\t");

if (flattened.Length <= 60)
{
return flattened;
}

return flattened [..60] + "…";
}
}
3 changes: 3 additions & 0 deletions Terminal.Gui/App/ApplicationImpl.Driver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,14 @@ internal void SubscribeDriverEvents ()
Driver.KeyDown += Driver_KeyDown;
Driver.KeyUp += Driver_KeyUp;
Driver.MouseEvent += Driver_MouseEvent;
Driver.Paste += Driver_Paste;
}

private void Driver_KeyDown (object? sender, Key e) => Keyboard.RaiseKeyDownEvent (e);

private void Driver_KeyUp (object? sender, Key e) => Keyboard.RaiseKeyUpEvent (e);

private void Driver_MouseEvent (object? sender, Mouse e) => Mouse.RaiseMouseEvent (e);

private void Driver_Paste (object? sender, string e) => RaisePasteEvent (e);
}
35 changes: 35 additions & 0 deletions Terminal.Gui/App/ApplicationImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,41 @@ public IMouse Mouse
set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
}

/// <inheritdoc/>
public event EventHandler<PasteEventArgs>? Paste;

/// <inheritdoc/>
public bool RaisePasteEvent (string text)
{
PasteEventArgs args = new (text);

Paste?.Invoke (this, args);

if (args.Handled)
{
return true;
}

// Route only to the focused view — paste data is text destined for a text-input control,
// and silently dispatching to a non-text container would either drop the paste or surprise
// the user. Apps that want to handle pastes without a focused view should subscribe to
// Application.Paste and set Handled.
View? focused = Navigation?.GetFocused ();

if (focused is null)
{
return false;
}

// Dispatch through Command.Paste so bracketed paste shares the same paste handler
// (sanitization, Pasting/Pasted events, insertion) as keyboard-driven paste. Use a
// dedicated payload object so the handler does not mistake unrelated string-valued command
// context entries for pasted text.
CommandContext ctx = new (Command.Paste, new WeakReference<View> (focused), binding: null);

return focused.InvokeCommand (Command.Paste, ctx.WithValue (new PastePayload (args.Text))) is true;
}

#endregion Input (Mouse/Keyboard)

#region Navigation and Popover
Expand Down
28 changes: 28 additions & 0 deletions Terminal.Gui/App/IApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,34 @@ public interface IApplication : IDisposable
/// </remarks>
IMouse Mouse { get; set; }

/// <summary>
/// Raised when the terminal delivers a bracketed paste. Fires before the paste is dispatched
/// to the focused view; set <see cref="System.ComponentModel.HandledEventArgs.Handled"/> on
/// the event arguments to <see langword="true"/> to prevent the focused view from receiving
/// the paste.
/// </summary>
/// <remarks>
/// <para>
/// Bracketed paste mode is enabled automatically by the driver. On terminals that do not
/// support bracketed paste (or have it disabled by user configuration) the pasted text is
/// delivered as ordinary key events and this event does not fire.
/// </para>
/// </remarks>
event EventHandler<PasteEventArgs>? Paste;

/// <summary>
/// Raises the <see cref="Paste"/> event with <paramref name="text"/>, then dispatches the
/// paste to the focused view by invoking <see cref="Command.Paste"/> with a dedicated
/// command-context paste payload if not already handled. The default
/// <see cref="Command.Paste"/> handler on <see cref="View"/> sanitizes the payload, raises
/// <see cref="View.Pasting"/>, and delegates insertion to the focused view's
/// <see cref="View.OnPaste"/> override. If the focused view consumes the paste and reports
/// a final-text segment for the pasted range, the handler raises <see cref="View.Pasted"/>.
/// </summary>
/// <param name="text">The pasted content with bracketing markers stripped.</param>
/// <returns><see langword="true"/> if the paste was handled.</returns>
bool RaisePasteEvent (string text);

#endregion Input (Mouse/Keyboard)

#region Layout and Drawing
Expand Down
3 changes: 3 additions & 0 deletions Terminal.Gui/Drivers/AnsiDriver/AnsiOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public AnsiOutput (AppModel appModel = AppModel.FullScreen)

// TODO: Move Input related CSI sequences to AnsiInput
Write (EscSeqUtils.CSI_EnableMouseEvents);
Write (EscSeqUtils.CSI_EnableBracketedPaste);
}
else
{
Expand All @@ -156,6 +157,7 @@ public AnsiOutput (AppModel appModel = AppModel.FullScreen)

// TODO: Move Input related CSI sequences to AnsiInput
Write (EscSeqUtils.CSI_EnableMouseEvents);
Write (EscSeqUtils.CSI_EnableBracketedPaste);
}

// Flush to ensure all sequences are sent
Expand Down Expand Up @@ -382,6 +384,7 @@ public void Dispose ()
}

// Restore terminal state: disable mouse, reset attributes, show cursor
Write (EscSeqUtils.CSI_DisableBracketedPaste);
Write (EscSeqUtils.CSI_DisableMouseEvents);
Write (EscSeqUtils.CSI_ResetAttributes);

Expand Down
Loading
Loading