1
+ from contextlib import contextmanager
1
2
from logging import getLogger
2
3
from typing import Iterable
3
4
10
11
from snowflake .cli ._app .printing import print_result
11
12
from snowflake .cli ._plugins .sql .lexer import CliLexer , cli_completer
12
13
from snowflake .cli ._plugins .sql .manager import SqlManager
14
+ from snowflake .cli ._plugins .sql .repl_commands import detect_command
13
15
from snowflake .cli .api .cli_global_context import get_cli_context_manager
14
16
from snowflake .cli .api .console import cli_console
15
17
from snowflake .cli .api .output .types import MultipleResults , QueryResult
28
30
log .debug ("setting history file to: %s" , HISTORY_FILE .as_posix ())
29
31
30
32
33
+ @contextmanager
34
+ def repl_context (repl_instance ):
35
+ """Context manager for REPL execution that handles CLI context registration."""
36
+ context_manager = get_cli_context_manager ()
37
+ context_manager .is_repl = True
38
+ context_manager .repl_instance = repl_instance
39
+
40
+ try :
41
+ yield
42
+ finally :
43
+ # Clean up REPL context
44
+ context_manager .is_repl = False
45
+ context_manager .repl_instance = None
46
+
47
+
31
48
class Repl :
32
49
"""Basic REPL implementation for the Snowflake CLI."""
33
50
@@ -45,7 +62,6 @@ def __init__(
45
62
`retain_comments` how to handle comments in queries
46
63
"""
47
64
super ().__init__ ()
48
- setattr (get_cli_context_manager (), "is_repl" , True )
49
65
self ._data = data or {}
50
66
self ._retain_comments = retain_comments
51
67
self ._template_syntax_config = template_syntax_config
@@ -56,6 +72,7 @@ def __init__(
56
72
self ._yes_no_keybindings = self ._setup_yn_key_bindings ()
57
73
self ._sql_manager = sql_manager
58
74
self .session = PromptSession (history = self ._history )
75
+ self ._next_input : str | None = None
59
76
60
77
def _setup_key_bindings (self ) -> KeyBindings :
61
78
"""Key bindings for repl. Helps detecting ; at end of buffer."""
@@ -67,19 +84,31 @@ def not_searching():
67
84
68
85
@kb .add (Keys .Enter , filter = not_searching )
69
86
def _ (event ):
70
- """Handle Enter key press."""
87
+ """Handle Enter key press with intelligent execution logic.
88
+
89
+ Execution priority:
90
+ 1. Exit keywords (exit, quit) - execute immediately
91
+ 2. REPL commands (starting with !) - execute immediately
92
+ 3. SQL with trailing semicolon - execute immediately
93
+ 4. All other input - add new line for multi-line editing
94
+ """
71
95
buffer = event .app .current_buffer
72
96
stripped_buffer = buffer .text .strip ()
73
97
74
98
if stripped_buffer :
75
99
log .debug ("evaluating repl input" )
76
100
cursor_position = buffer .cursor_position
77
101
ends_with_semicolon = buffer .text .endswith (";" )
102
+ is_command = detect_command (stripped_buffer ) is not None
78
103
79
104
if stripped_buffer .lower () in EXIT_KEYWORDS :
80
105
log .debug ("exit keyword detected %r" , stripped_buffer )
81
106
buffer .validate_and_handle ()
82
107
108
+ elif is_command :
109
+ log .debug ("command detected, submitting input" )
110
+ buffer .validate_and_handle ()
111
+
83
112
elif ends_with_semicolon and cursor_position >= len (stripped_buffer ):
84
113
log .debug ("semicolon detected, submitting input" )
85
114
buffer .validate_and_handle ()
@@ -118,16 +147,27 @@ def _(event):
118
147
119
148
return kb
120
149
121
- def repl_propmpt (self , msg : str = " > " ) -> str :
122
- """Regular repl prompt."""
123
- return self .session .prompt (
124
- msg ,
125
- lexer = self ._lexer ,
126
- completer = self ._completer ,
127
- multiline = True ,
128
- wrap_lines = True ,
129
- key_bindings = self ._repl_key_bindings ,
130
- )
150
+ def repl_prompt (self , msg : str = " > " ) -> str :
151
+ """Regular repl prompt with support for pre-filled input.
152
+
153
+ Checks for queued input from commands like !edit and uses it as
154
+ default text in the prompt. The queued input is cleared after use.
155
+ """
156
+ default_text = self ._next_input
157
+
158
+ try :
159
+ return self .session .prompt (
160
+ msg ,
161
+ lexer = self ._lexer ,
162
+ completer = self ._completer ,
163
+ multiline = True ,
164
+ wrap_lines = True ,
165
+ key_bindings = self ._repl_key_bindings ,
166
+ default = default_text or "" ,
167
+ )
168
+ finally :
169
+ if self ._next_input == default_text :
170
+ self ._next_input = None
131
171
132
172
def yn_prompt (self , msg : str ) -> str :
133
173
"""Yes/No prompt."""
@@ -142,7 +182,7 @@ def yn_prompt(self, msg: str) -> str:
142
182
143
183
@property
144
184
def _welcome_banner (self ) -> str :
145
- return f "Welcome to Snowflake-CLI REPL\n Type 'exit' or 'quit' to leave"
185
+ return "Welcome to Snowflake-CLI REPL\n Type 'exit' or 'quit' to leave"
146
186
147
187
def _initialize_connection (self ):
148
188
"""Early connection for possible fast fail."""
@@ -163,12 +203,13 @@ def _execute(self, user_input: str) -> Iterable[SnowflakeCursor]:
163
203
return cursors
164
204
165
205
def run (self ):
166
- try :
167
- cli_console .panel (self ._welcome_banner )
168
- self ._initialize_connection ()
169
- self ._repl_loop ()
170
- except (KeyboardInterrupt , EOFError ):
171
- cli_console .message ("\n [bold orange_red1]Leaving REPL, bye ..." )
206
+ with repl_context (self ):
207
+ try :
208
+ cli_console .panel (self ._welcome_banner )
209
+ self ._initialize_connection ()
210
+ self ._repl_loop ()
211
+ except (KeyboardInterrupt , EOFError ):
212
+ cli_console .message ("\n [bold orange_red1]Leaving REPL, bye ..." )
172
213
173
214
def _repl_loop (self ):
174
215
"""Main REPL loop. Handles input and query execution.
@@ -178,7 +219,7 @@ def _repl_loop(self):
178
219
"""
179
220
while True :
180
221
try :
181
- user_input = self .repl_propmpt ().strip ()
222
+ user_input = self .repl_prompt ().strip ()
182
223
183
224
if not user_input :
184
225
continue
@@ -210,6 +251,21 @@ def _repl_loop(self):
210
251
except Exception as e :
211
252
cli_console .warning (f"\n Error occurred: { e } " )
212
253
254
+ def set_next_input (self , text : str ) -> None :
255
+ """Set the text that will be used as the next REPL input."""
256
+ self ._next_input = text
257
+ log .debug ("Next input has been set" )
258
+
259
+ @property
260
+ def next_input (self ) -> str | None :
261
+ """Get the next input text that will be used in the prompt."""
262
+ return self ._next_input
263
+
264
+ @property
265
+ def history (self ) -> FileHistory :
266
+ """Get the FileHistory instance used by the REPL."""
267
+ return self ._history
268
+
213
269
def ask_yn (self , question : str ) -> bool :
214
270
"""Asks user a Yes/No question."""
215
271
try :
0 commit comments