mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
@@ -21,6 +21,7 @@ use crate::bottom_pane::SettingsSection;
|
||||
use crate::chatwidget::ChatWidget;
|
||||
use crate::cloud_tasks_service;
|
||||
use crate::exec_command::strip_bash_lc_and_escape;
|
||||
use crate::external_editor;
|
||||
use crate::get_git_diff::get_git_diff;
|
||||
use crate::history_cell;
|
||||
use crate::slash_command::SlashCommand;
|
||||
@@ -575,6 +576,37 @@ impl App<'_> {
|
||||
AppEvent::Paste(text) => {
|
||||
self.dispatch_paste_event(text);
|
||||
}
|
||||
AppEvent::OpenExternalEditor { initial } => {
|
||||
let was_alt_screen = self.alt_screen_active;
|
||||
self.input_suspended.store(true, Ordering::Release);
|
||||
let editor_result = match tui::restore() {
|
||||
Ok(()) => external_editor::run_editor(&initial),
|
||||
Err(err) => Err(external_editor::ExternalEditorError::LaunchFailed(format!(
|
||||
"Failed to reset terminal: {err}",
|
||||
))),
|
||||
};
|
||||
let (new_terminal, new_terminal_info) = tui::init(&self.config)?;
|
||||
*terminal = new_terminal;
|
||||
self.terminal_info = new_terminal_info;
|
||||
terminal.clear()?;
|
||||
if was_alt_screen {
|
||||
self.alt_screen_active = true;
|
||||
} else {
|
||||
let _ = tui::leave_alt_screen_only();
|
||||
self.alt_screen_active = false;
|
||||
}
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.set_standard_terminal_mode(!self.alt_screen_active);
|
||||
}
|
||||
self.input_suspended.store(false, Ordering::Release);
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
match editor_result {
|
||||
Ok(text) => widget.set_composer_text(text),
|
||||
Err(err) => widget.debug_notice(err.to_string()),
|
||||
}
|
||||
}
|
||||
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||
}
|
||||
AppEvent::RegisterPastedImage { placeholder, path } => {
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.register_pasted_image(placeholder, path);
|
||||
|
||||
@@ -106,8 +106,9 @@ impl App<'_> {
|
||||
|
||||
// Spawn a dedicated thread for reading the crossterm event loop and
|
||||
// re-publishing the events as AppEvents, as appropriate.
|
||||
// Create the input thread stop flag up front so we can store it on `Self`.
|
||||
// Create the input thread flags up front so we can store them on `Self`.
|
||||
let input_running = Arc::new(AtomicBool::new(true));
|
||||
let input_suspended = Arc::new(AtomicBool::new(false));
|
||||
#[cfg(unix)]
|
||||
let mut sigterm_guard = None;
|
||||
#[cfg(unix)]
|
||||
@@ -115,6 +116,7 @@ impl App<'_> {
|
||||
{
|
||||
let app_event_tx = app_event_tx.clone();
|
||||
let input_running_thread = input_running.clone();
|
||||
let input_suspended_thread = input_suspended.clone();
|
||||
let drop_release_events = enhanced_keys_supported;
|
||||
if let Err(err) = std::thread::Builder::new()
|
||||
.name("tui-input-loop".to_string())
|
||||
@@ -123,6 +125,10 @@ impl App<'_> {
|
||||
let mut last_key_time = Instant::now();
|
||||
loop {
|
||||
if !input_running_thread.load(Ordering::Relaxed) { break; }
|
||||
if input_suspended_thread.load(Ordering::Relaxed) {
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
continue;
|
||||
}
|
||||
// This timeout is necessary to avoid holding the event lock
|
||||
// that crossterm::event::read() acquires. In particular,
|
||||
// reading the cursor position (crossterm::cursor::position())
|
||||
@@ -315,6 +321,7 @@ impl App<'_> {
|
||||
stdout_backpressure_skips: 0,
|
||||
frame_timer,
|
||||
input_running,
|
||||
input_suspended,
|
||||
enhanced_keys_supported,
|
||||
non_enhanced_pressed_keys: HashSet::new(),
|
||||
_debug: debug,
|
||||
|
||||
@@ -220,6 +220,8 @@ pub(crate) struct App<'a> {
|
||||
pub(super) frame_timer: Arc<FrameTimer>,
|
||||
/// Controls the input reader thread spawned at startup.
|
||||
pub(super) input_running: Arc<AtomicBool>,
|
||||
/// Temporarily pause input reader while external editor owns the terminal.
|
||||
pub(super) input_suspended: Arc<AtomicBool>,
|
||||
|
||||
pub(super) enhanced_keys_supported: bool,
|
||||
/// Tracks keys seen as pressed when keyboard enhancements are unavailable
|
||||
|
||||
@@ -169,6 +169,9 @@ pub(crate) enum AppEvent {
|
||||
/// Text pasted from the terminal clipboard.
|
||||
Paste(String),
|
||||
|
||||
/// Open the external editor with the current composer text.
|
||||
OpenExternalEditor { initial: String },
|
||||
|
||||
/// Request to exit the application gracefully.
|
||||
ExitRequest,
|
||||
|
||||
|
||||
@@ -934,6 +934,8 @@ impl ChatComposer {
|
||||
if !text.is_empty() {
|
||||
self.typed_anything = true;
|
||||
}
|
||||
self.sync_command_popup();
|
||||
self.sync_file_search_popup();
|
||||
}
|
||||
|
||||
pub(crate) fn insert_str(&mut self, text: &str) {
|
||||
@@ -2447,18 +2449,18 @@ impl ChatComposer {
|
||||
let mut include_tokens = !token_spans_full.is_empty() || !token_spans_compact.is_empty();
|
||||
let mut token_use_compact = false;
|
||||
|
||||
// Guide hint (priority 4) — only when not auto-drive and not showing quit hint
|
||||
let guide_spans: Vec<Span<'static>> = if !self.auto_drive_active && !self.ctrl_c_quit_hint {
|
||||
// Editor hint (priority 4) — only when not auto-drive and not showing quit hint
|
||||
let editor_spans: Vec<Span<'static>> = if !self.auto_drive_active && !self.ctrl_c_quit_hint {
|
||||
vec![
|
||||
Span::from("Ctrl+G").style(key_hint_style),
|
||||
Span::from(" guide").style(label_style),
|
||||
Span::from(" editor").style(label_style),
|
||||
]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let guide_present = !guide_spans.is_empty();
|
||||
if guide_present {
|
||||
right_sections.push((4, guide_spans, true));
|
||||
let editor_present = !editor_spans.is_empty();
|
||||
if editor_present {
|
||||
right_sections.push((4, editor_spans, true));
|
||||
}
|
||||
|
||||
// Tokens placeholder (actual spans chosen later)
|
||||
@@ -2489,7 +2491,7 @@ impl ChatComposer {
|
||||
right_sections.iter().any(|(p, _, inc)| *p == 3 && *inc);
|
||||
let mut include_left_misc = true;
|
||||
let mut include_ctrl_c = ctrl_c_present;
|
||||
let mut include_guide = guide_present;
|
||||
let mut include_editor = editor_present;
|
||||
let mut include_right_other = true; // covers priority 7 sections
|
||||
|
||||
// helper closures to rebuild assembled spans based on current flags
|
||||
@@ -2559,7 +2561,7 @@ impl ChatComposer {
|
||||
include_tokens: bool,
|
||||
use_compact_tokens: bool,
|
||||
include_auto_review_agent_hint: bool,
|
||||
include_guide: bool,
|
||||
include_editor: bool,
|
||||
include_right_other: bool,
|
||||
| -> (Vec<Span<'static>>, usize) {
|
||||
let mut assembled: Vec<Span<'static>> = Vec::new();
|
||||
@@ -2569,7 +2571,7 @@ impl ChatComposer {
|
||||
let include = match *priority {
|
||||
1 => include_tokens && *included,
|
||||
3 => include_auto_review_agent_hint && *included,
|
||||
4 => include_guide && *included,
|
||||
4 => include_editor && *included,
|
||||
7 => include_right_other && *included,
|
||||
_ => *included,
|
||||
};
|
||||
@@ -2618,7 +2620,7 @@ impl ChatComposer {
|
||||
include_tokens,
|
||||
token_use_compact,
|
||||
include_auto_review_agent_hint,
|
||||
include_guide,
|
||||
include_editor,
|
||||
include_right_other,
|
||||
);
|
||||
|
||||
@@ -2656,7 +2658,7 @@ impl ChatComposer {
|
||||
}
|
||||
|
||||
// Removal order: 7 (right other) -> 6 (left misc) -> 5 (Ctrl+A hint)
|
||||
// -> 4 (guide) -> 3 (auto review status) -> 2 (Ctrl+C) -> 1 (tokens)
|
||||
// -> 4 (editor) -> 3 (auto review status) -> 2 (Ctrl+C) -> 1 (tokens)
|
||||
match removal_stage {
|
||||
0 => {
|
||||
include_right_other = false;
|
||||
@@ -2668,7 +2670,7 @@ impl ChatComposer {
|
||||
include_auto_review_agent_hint = false;
|
||||
}
|
||||
3 => {
|
||||
include_guide = false;
|
||||
include_editor = false;
|
||||
}
|
||||
4 => {
|
||||
include_auto_review_status = false;
|
||||
|
||||
@@ -517,6 +517,11 @@ impl BottomPane<'_> {
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
pub(crate) fn set_composer_text(&mut self, text: String) {
|
||||
self.composer.set_text_content(text);
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Clear the composer text and reset transient composer state.
|
||||
pub(crate) fn clear_composer(&mut self) {
|
||||
self.composer.clear_text();
|
||||
|
||||
@@ -5933,6 +5933,16 @@ impl ChatWidget<'_> {
|
||||
self.bottom_pane.insert_str(s);
|
||||
}
|
||||
|
||||
pub(crate) fn set_composer_text(&mut self, text: String) {
|
||||
if self.auto_state.should_show_goal_entry()
|
||||
&& matches!(self.auto_goal_escape_state, AutoGoalEscState::Inactive)
|
||||
&& !text.trim().is_empty()
|
||||
{
|
||||
self.auto_goal_escape_state = AutoGoalEscState::NeedsEnableEditing;
|
||||
}
|
||||
self.bottom_pane.set_composer_text(text);
|
||||
}
|
||||
|
||||
// Removed: pending insert sequencing is not used under strict ordering.
|
||||
|
||||
pub(crate) fn register_pasted_image(&mut self, placeholder: String, path: std::path::PathBuf) {
|
||||
@@ -8027,6 +8037,21 @@ impl ChatWidget<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
if let KeyEvent {
|
||||
code: crossterm::event::KeyCode::Char('g'),
|
||||
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
} = key_event
|
||||
{
|
||||
if !self.bottom_pane.has_active_modal_view() {
|
||||
let initial = self.bottom_pane.composer_text();
|
||||
self.app_event_tx
|
||||
.send(AppEvent::OpenExternalEditor { initial });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast-path PageUp/PageDown to scroll the transcript by a viewport at a time.
|
||||
if let crossterm::event::KeyEvent {
|
||||
code: crossterm::event::KeyCode::PageUp,
|
||||
@@ -21833,7 +21858,8 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
));
|
||||
|
||||
// Global
|
||||
lines.push(kv("Ctrl+G", "Guide overlay"));
|
||||
lines.push(kv("F1", "Help overlay"));
|
||||
lines.push(kv("Ctrl+G", "Open external editor"));
|
||||
lines.push(kv("Ctrl+R", "Toggle reasoning"));
|
||||
lines.push(kv("Ctrl+T", "Toggle screen"));
|
||||
lines.push(kv("Ctrl+D", "Diff viewer"));
|
||||
@@ -29470,6 +29496,7 @@ impl Drop for AutoReviewStubGuard {
|
||||
CAPTURE_AUTO_TURN_COMMIT_STUB,
|
||||
GIT_DIFF_NAME_ONLY_BETWEEN_STUB,
|
||||
};
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::bottom_pane::AutoCoordinatorViewModel;
|
||||
use crate::chatwidget::message::UserMessage;
|
||||
use crate::chatwidget::smoke_helpers::{enter_test_runtime_guard, ChatWidgetHarness};
|
||||
@@ -31247,6 +31274,20 @@ use code_core::protocol::OrderMeta;
|
||||
assert_eq!(chat.bottom_pane.composer_text(), "x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_g_dispatches_external_editor_event() {
|
||||
let mut harness = ChatWidgetHarness::new();
|
||||
let key_event = KeyEvent::new(KeyCode::Char('g'), KeyModifiers::CONTROL);
|
||||
harness.chat().handle_key_event(key_event);
|
||||
let events = harness.drain_events();
|
||||
assert!(
|
||||
events
|
||||
.iter()
|
||||
.any(|event| matches!(event, AppEvent::OpenExternalEditor { .. })),
|
||||
"expected external editor request on Ctrl+G",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goal_entry_esc_exits_immediately_without_suggestion() {
|
||||
let mut harness = ChatWidgetHarness::new();
|
||||
|
||||
@@ -5,9 +5,9 @@ use crossterm::event::{KeyCode, KeyEvent};
|
||||
|
||||
// Returns true if the key was handled by the guide overlay (or toggled it closed).
|
||||
pub(super) fn handle_help_key(chat: &mut ChatWidget<'_>, key_event: KeyEvent) -> bool {
|
||||
// If no guide overlay, only intercept Ctrl+G to open it.
|
||||
// If no guide overlay, only intercept F1 to open it.
|
||||
if chat.help.overlay.is_none() {
|
||||
if let KeyEvent { code: KeyCode::Char('g'), modifiers: crossterm::event::KeyModifiers::CONTROL, .. } = key_event {
|
||||
if let KeyEvent { code: KeyCode::F(1), .. } = key_event {
|
||||
chat.toggle_help_popup();
|
||||
return true;
|
||||
}
|
||||
@@ -56,8 +56,8 @@ pub(super) fn handle_help_key(chat: &mut ChatWidget<'_>, key_event: KeyEvent) ->
|
||||
chat.request_redraw();
|
||||
true
|
||||
}
|
||||
KeyCode::Esc | KeyCode::Char('g') => {
|
||||
// Close on Esc or Ctrl+G
|
||||
KeyCode::Esc | KeyCode::F(1) => {
|
||||
// Close on Esc or F1
|
||||
chat.help.overlay = None;
|
||||
chat.request_redraw();
|
||||
true
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
|
||||
use tempfile::Builder;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum ExternalEditorError {
|
||||
#[error("Set $VISUAL or $EDITOR to use the external editor.")]
|
||||
MissingEditor,
|
||||
#[error("Failed to parse editor command.")]
|
||||
ParseFailed,
|
||||
#[error("Editor command is empty.")]
|
||||
EmptyCommand,
|
||||
#[error("Failed to launch editor: {0}")]
|
||||
LaunchFailed(String),
|
||||
#[error("Editor exited with status {0}.")]
|
||||
NonZeroExit(String),
|
||||
#[error("Failed to read edited content: {0}")]
|
||||
ReadFailed(String),
|
||||
}
|
||||
|
||||
pub(crate) fn run_editor(initial: &str) -> Result<String, ExternalEditorError> {
|
||||
let command = resolve_editor_command()?;
|
||||
let (program, args) = command
|
||||
.split_first()
|
||||
.ok_or(ExternalEditorError::EmptyCommand)?;
|
||||
|
||||
let mut temp = Builder::new()
|
||||
.prefix("code-edit-")
|
||||
.suffix(".md")
|
||||
.tempfile()
|
||||
.map_err(|e| ExternalEditorError::ReadFailed(e.to_string()))?;
|
||||
if !initial.is_empty() {
|
||||
std::io::Write::write_all(&mut temp, initial.as_bytes())
|
||||
.map_err(|e| ExternalEditorError::ReadFailed(e.to_string()))?;
|
||||
}
|
||||
temp.flush()
|
||||
.map_err(|e| ExternalEditorError::ReadFailed(e.to_string()))?;
|
||||
|
||||
let temp_path = temp.into_temp_path();
|
||||
let path = temp_path.to_path_buf();
|
||||
let path_str = path.to_string_lossy().to_string();
|
||||
|
||||
let mut args = args.to_vec();
|
||||
let mut replaced = false;
|
||||
for arg in &mut args {
|
||||
if arg.contains("{file}") {
|
||||
*arg = arg.replace("{file}", &path_str);
|
||||
replaced = true;
|
||||
} else if arg == "{}" {
|
||||
*arg = path_str.clone();
|
||||
replaced = true;
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
args.push(path_str.clone());
|
||||
}
|
||||
|
||||
let status = Command::new(program)
|
||||
.args(&args)
|
||||
.status()
|
||||
.map_err(|e| ExternalEditorError::LaunchFailed(e.to_string()))?;
|
||||
if !status.success() {
|
||||
return Err(ExternalEditorError::NonZeroExit(format!("{status:?}")));
|
||||
}
|
||||
|
||||
fs::read_to_string(&path)
|
||||
.map_err(|e| ExternalEditorError::ReadFailed(e.to_string()))
|
||||
}
|
||||
|
||||
fn resolve_editor_command() -> Result<Vec<String>, ExternalEditorError> {
|
||||
let visual = env::var("VISUAL").ok().filter(|val| !val.trim().is_empty());
|
||||
let editor = env::var("EDITOR").ok().filter(|val| !val.trim().is_empty());
|
||||
let raw = visual.or(editor).ok_or(ExternalEditorError::MissingEditor)?;
|
||||
parse_editor_command(&raw)
|
||||
}
|
||||
|
||||
fn parse_editor_command(raw: &str) -> Result<Vec<String>, ExternalEditorError> {
|
||||
let trimmed = raw.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err(ExternalEditorError::EmptyCommand);
|
||||
}
|
||||
let parsed = shlex::split(trimmed).ok_or(ExternalEditorError::ParseFailed)?;
|
||||
if parsed.is_empty() {
|
||||
return Err(ExternalEditorError::EmptyCommand);
|
||||
}
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::parse_editor_command;
|
||||
|
||||
#[test]
|
||||
fn parse_editor_command_splits_args() {
|
||||
let parsed = parse_editor_command("code --wait --reuse-window").expect("parse");
|
||||
assert_eq!(parsed, vec!["code", "--wait", "--reuse-window"]);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ mod colors;
|
||||
pub mod card_theme;
|
||||
mod diff_render;
|
||||
mod exec_command;
|
||||
mod external_editor;
|
||||
mod file_search;
|
||||
pub mod gradient_background;
|
||||
mod get_git_diff;
|
||||
|
||||
@@ -189,7 +189,7 @@ fn assert_viewport_invariants(output: &str, expected_rows: u16) {
|
||||
lines.len()
|
||||
);
|
||||
assert!(
|
||||
output.contains("Ctrl+G guide"),
|
||||
output.contains("Ctrl+G editor"),
|
||||
"composer footer is expected to remain visible"
|
||||
);
|
||||
if let Some(last_line) = lines.last() {
|
||||
|
||||
+1
-1
@@ -23,4 +23,4 @@ expression: collapsed
|
||||
|
||||
| What can I code for you today? |
|
||||
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -23,4 +23,4 @@ expression: expanded
|
||||
|
||||
| What can I code for you today? |
|
||||
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -29,4 +29,4 @@ expression: output
|
||||
|
||||
| What can I code for you today? |
|
||||
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -23,4 +23,4 @@ expression: output
|
||||
|
||||
| What can I code for you today? |
|
||||
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -32,4 +32,4 @@ expression: output
|
||||
+----------------------------- ✶ Responding... -----------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -20,4 +20,4 @@ expression: output
|
||||
+----------------------------- ✶ Using tools... -----------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -32,4 +32,4 @@ expression: output
|
||||
+----------------------------- ✶ Browsing... -----------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -32,4 +32,4 @@ expression: output
|
||||
+----------------------------- ✶ Browsing... -----------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -24,4 +24,4 @@ expression: output
|
||||
+----------------------------- ✶ Browsing... -----------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -20,4 +20,4 @@ expression: frame
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -18,4 +18,4 @@ expression: output
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
@@ -18,4 +18,4 @@ expression: output
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -18,4 +18,4 @@ expression: output
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -18,4 +18,4 @@ expression: output
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
@@ -24,4 +24,4 @@ expression: output
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
@@ -24,4 +24,4 @@ expression: output
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -24,4 +24,4 @@ expression: scrolled
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -28,4 +28,4 @@ expression: closed
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -28,4 +28,4 @@ expression: open
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -28,4 +28,4 @@ expression: section_closed
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -28,4 +28,4 @@ expression: section_open
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -28,4 +28,4 @@ expression: output
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -20,4 +20,4 @@ expression: output
|
||||
+--------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+--------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -22,4 +22,4 @@ expression: output
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
@@ -24,4 +24,4 @@ expression: output
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@ expression: output
|
||||
+----------------------------------------------------------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
@@ -40,4 +40,4 @@ expression: output
|
||||
+----------------------------- ✶ Agents... -----------------------------+
|
||||
| What can I code for you today? |
|
||||
+----------------------------------------------------------------------------+
|
||||
Read Only (Shift+Tab change) Ctrl+G guide • API key
|
||||
Read Only (Shift+Tab change) Ctrl+G editor • API key
|
||||
|
||||
Reference in New Issue
Block a user