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  "context": "Terminal",
83  "bindings": {
84    "shift-enter": ["terminal::SendText", "\n"]
85  }
86}
87"#
88    .to_string()
89}
90
91/// Windows Terminal: JSON action binding
92fn generate_windows_terminal_config() -> String {
93    r#"{
94  "actions": [
95    {
96      "command": {
97        "action": "sendInput",
98        "input": "\n"
99      },
100      "keys": "shift+enter"
101    }
102  ]
103}
104"#
105    .to_string()
106}
107
108/// Hyper: JavaScript plugin configuration
109fn generate_hyper_config() -> String {
110    r#"// In your .hyper.js config:
111module.exports = {
112  config: {
113    // ... other config
114  },
115  keymaps: {
116    'window:devtools': 'cmd+alt+i',
117    'window:reload': 'cmd+shift+r',
118    'tab:new': 'cmd+t',
119    'shift+enter': 'editor:newline'
120  }
121}
122"#
123    .to_string()
124}
125
126/// Tabby: YAML keybinding configuration
127fn generate_tabby_config() -> String {
128    r#"hotkeys:
129  multiline-input:
130    - Shift-Enter
131terminal:
132  sendInputOnEnter: true
133"#
134    .to_string()
135}
136
137/// iTerm2: Manual setup instructions (plist modification is complex)
138fn generate_iterm2_instructions() -> String {
139    r#"iTerm2 Manual Setup Instructions:
140
1411. Open iTerm2 Preferences (Cmd+,)
1422. Go to Profiles → Keys
1433. Click the "+" button to add a new key mapping
1444. Press Shift+Enter when prompted
1455. Set Action to "Send Text"
1466. Enter "\n" (without quotes) in the text field
1477. Click OK to save
148
149This will bind Shift+Enter to insert a newline character.
150"#
151    .to_string()
152}
153
154/// VS Code: JSON settings configuration
155fn generate_vscode_instructions() -> String {
156    r#"VS Code Terminal Manual Setup:
157
158Add this to your keybindings.json (Cmd+K Cmd+S to open):
159
160{
161  "key": "shift+enter",
162  "command": "workbench.action.terminal.sendSequence",
163  "when": "terminalFocus",
164  "args": { "text": "\n" }
165}
166
167Or use the UI:
1681. Open Command Palette (Cmd+Shift+P)
1692. Search for "Preferences: Open Keyboard Shortcuts (JSON)"
1703. Add the keybinding above to the array
171"#
172    .to_string()
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_generate_ghostty_config() {
181        let config = generate_config(TerminalType::Ghostty).unwrap();
182        assert!(config.contains("keybind"));
183        assert!(config.contains("shift+enter"));
184    }
185
186    #[test]
187    fn test_generate_kitty_config() {
188        let config = generate_config(TerminalType::Kitty).unwrap();
189        assert!(config.contains("map shift+enter"));
190        assert!(config.contains("send_text"));
191    }
192
193    #[test]
194    fn test_generate_alacritty_config() {
195        let config = generate_config(TerminalType::Alacritty).unwrap();
196        assert!(config.contains("keyboard.bindings"));
197        assert!(config.contains("Return"));
198        assert!(config.contains("Shift"));
199    }
200
201    #[test]
202    fn test_generate_windows_terminal_config() {
203        let config = generate_config(TerminalType::WindowsTerminal).unwrap();
204        assert!(config.contains("sendInput"));
205        assert!(config.contains("shift+enter"));
206    }
207
208    #[test]
209    fn test_warp_no_config_needed() {
210        let config = generate_config(TerminalType::Warp).unwrap();
211        assert!(config.is_empty());
212    }
213
214    #[test]
215    fn test_iterm2_instructions() {
216        let config = generate_config(TerminalType::ITerm2).unwrap();
217        assert!(config.contains("Manual Setup"));
218        assert!(config.contains("Preferences"));
219    }
220
221    #[test]
222    fn test_unknown_terminal_error() {
223        let result = generate_config(TerminalType::Unknown);
224        result.unwrap_err();
225    }
226    #[test]
227    fn zed_binding_sends_literal_newline_only_in_terminal_context() {
228        let value: serde_json::Value = serde_json::from_str(&generate_config(TerminalType::Zed).unwrap()).unwrap();
229        assert_eq!(value["context"], "Terminal");
230        assert_eq!(value["bindings"]["shift-enter"], serde_json::json!(["terminal::SendText", "\n"]));
231        assert_eq!(value["bindings"].as_object().unwrap().len(), 1);
232    }
233}