Skip to main content

vtcode_core/terminal_setup/features/
multiline.rs

1//! Multiline input feature - Shift+Enter keybinding configuration.
2//!
3//! Generates terminal-specific configuration for binding Shift+Enter to insert a newline
4//! character, enabling multiline input in VT Code.
5
6use crate::terminal_setup::detector::TerminalType;
7use anyhow::Result;
8
9/// Generate Shift+Enter multiline configuration for a terminal
10pub fn generate_config(terminal_type: TerminalType) -> Result<String> {
11    let config = match terminal_type {
12        TerminalType::Ghostty => generate_ghostty_config(),
13        TerminalType::Kitty => generate_kitty_config(),
14        TerminalType::Alacritty => generate_alacritty_config(),
15        TerminalType::WezTerm => generate_wezterm_config(),
16        TerminalType::TerminalApp => generate_terminal_app_instructions(),
17        TerminalType::Xterm => generate_xterm_instructions(),
18        TerminalType::Zed => generate_zed_config(),
19        TerminalType::Warp => {
20            // Warp has built-in multiline support, no config needed
21            String::new()
22        }
23        TerminalType::ITerm2 => generate_iterm2_instructions(),
24        TerminalType::VSCode => generate_vscode_instructions(),
25        TerminalType::WindowsTerminal => generate_windows_terminal_config(),
26        TerminalType::Hyper => generate_hyper_config(),
27        TerminalType::Tabby => generate_tabby_config(),
28        TerminalType::Unknown => {
29            anyhow::bail!("Cannot generate multiline config for unknown terminal")
30        }
31    };
32
33    Ok(config)
34}
35
36/// WezTerm: Lua keybinding example.
37fn generate_wezterm_config() -> String {
38    r#"keys = {
39  { key = "Enter", mods = "SHIFT", action = wezterm.action.SendString("\n") },
40}
41"#
42    .to_string()
43}
44
45/// Terminal.app: manual key mapping guidance.
46fn generate_terminal_app_instructions() -> String {
47    r#"Terminal.app uses profile key mappings.
48Add Shift+Enter mapping to send \n in your active profile."#
49        .to_string()
50}
51
52/// xterm: baseline guidance.
53fn generate_xterm_instructions() -> String {
54    r#"xterm multiline can be configured through X resources or window manager key mapping.
55Ensure Shift+Enter sends a newline sequence."#
56        .to_string()
57}
58
59/// Ghostty: keybind = shift+enter=text:\n
60fn generate_ghostty_config() -> String {
61    "keybind = shift+enter=text:\\n".to_string()
62}
63
64/// Kitty: map shift+enter send_text all \n
65fn generate_kitty_config() -> String {
66    "map shift+enter send_text all \\n".to_string()
67}
68
69/// Alacritty: TOML keyboard binding
70fn generate_alacritty_config() -> String {
71    r#"[[keyboard.bindings]]
72key = "Return"
73mods = "Shift"
74chars = "\n"
75"#
76    .to_string()
77}
78
79/// Zed: JSON keybinding configuration
80fn generate_zed_config() -> String {
81    r#"{
82  "bindings": {
83    "shift-enter": "editor::Newline"
84  }
85}
86"#
87    .to_string()
88}
89
90/// Windows Terminal: JSON action binding
91fn generate_windows_terminal_config() -> String {
92    r#"{
93  "actions": [
94    {
95      "command": {
96        "action": "sendInput",
97        "input": "\n"
98      },
99      "keys": "shift+enter"
100    }
101  ]
102}
103"#
104    .to_string()
105}
106
107/// Hyper: JavaScript plugin configuration
108fn generate_hyper_config() -> String {
109    r#"// In your .hyper.js config:
110module.exports = {
111  config: {
112    // ... other config
113  },
114  keymaps: {
115    'window:devtools': 'cmd+alt+i',
116    'window:reload': 'cmd+shift+r',
117    'tab:new': 'cmd+t',
118    'shift+enter': 'editor:newline'
119  }
120}
121"#
122    .to_string()
123}
124
125/// Tabby: YAML keybinding configuration
126fn generate_tabby_config() -> String {
127    r#"hotkeys:
128  multiline-input:
129    - Shift-Enter
130terminal:
131  sendInputOnEnter: true
132"#
133    .to_string()
134}
135
136/// iTerm2: Manual setup instructions (plist modification is complex)
137fn generate_iterm2_instructions() -> String {
138    r#"iTerm2 Manual Setup Instructions:
139
1401. Open iTerm2 Preferences (Cmd+,)
1412. Go to Profiles → Keys
1423. Click the "+" button to add a new key mapping
1434. Press Shift+Enter when prompted
1445. Set Action to "Send Text"
1456. Enter "\n" (without quotes) in the text field
1467. Click OK to save
147
148This will bind Shift+Enter to insert a newline character.
149"#
150    .to_string()
151}
152
153/// VS Code: JSON settings configuration
154fn generate_vscode_instructions() -> String {
155    r#"VS Code Terminal Manual Setup:
156
157Add this to your keybindings.json (Cmd+K Cmd+S to open):
158
159{
160  "key": "shift+enter",
161  "command": "workbench.action.terminal.sendSequence",
162  "when": "terminalFocus",
163  "args": { "text": "\n" }
164}
165
166Or use the UI:
1671. Open Command Palette (Cmd+Shift+P)
1682. Search for "Preferences: Open Keyboard Shortcuts (JSON)"
1693. Add the keybinding above to the array
170"#
171    .to_string()
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_generate_ghostty_config() {
180        let config = generate_config(TerminalType::Ghostty).unwrap();
181        assert!(config.contains("keybind"));
182        assert!(config.contains("shift+enter"));
183    }
184
185    #[test]
186    fn test_generate_kitty_config() {
187        let config = generate_config(TerminalType::Kitty).unwrap();
188        assert!(config.contains("map shift+enter"));
189        assert!(config.contains("send_text"));
190    }
191
192    #[test]
193    fn test_generate_alacritty_config() {
194        let config = generate_config(TerminalType::Alacritty).unwrap();
195        assert!(config.contains("keyboard.bindings"));
196        assert!(config.contains("Return"));
197        assert!(config.contains("Shift"));
198    }
199
200    #[test]
201    fn test_generate_windows_terminal_config() {
202        let config = generate_config(TerminalType::WindowsTerminal).unwrap();
203        assert!(config.contains("sendInput"));
204        assert!(config.contains("shift+enter"));
205    }
206
207    #[test]
208    fn test_warp_no_config_needed() {
209        let config = generate_config(TerminalType::Warp).unwrap();
210        assert!(config.is_empty());
211    }
212
213    #[test]
214    fn test_iterm2_instructions() {
215        let config = generate_config(TerminalType::ITerm2).unwrap();
216        assert!(config.contains("Manual Setup"));
217        assert!(config.contains("Preferences"));
218    }
219
220    #[test]
221    fn test_unknown_terminal_error() {
222        let result = generate_config(TerminalType::Unknown);
223        result.unwrap_err();
224    }
225}