@@ -2101,6 +2101,142 @@ const escape_defaults = merge!(
21012101 AnyDict (" \e [$(c) l" => nothing for c in 1 : 20 )
21022102 )
21032103
2104+
2105+ # Keymap for automatic bracket/quote insertion and completion
2106+ const bracket_insert_keymap = AnyDict ()
2107+ let
2108+ # Determine when we should not close a bracket/quote
2109+ function should_skip_closing_bracket (left_peek, v)
2110+ # Don't close if we already have an open quote immediately before (triple quote case)
2111+ # For quotes, also check for transpose expressions: issue JuliaLang/OhMyREPL.jl#200
2112+ left_peek == v && return true
2113+ if v == ' \' '
2114+ tr_expr = isletter (left_peek) || isnumeric (left_peek) || left_peek == ' _' || left_peek == ' ]'
2115+ return tr_expr
2116+ end
2117+ return false
2118+ end
2119+
2120+ function peek_char_left (b:: IOBuffer )
2121+ p = position (b)
2122+ c = char_move_left (b)
2123+ seek (b, p)
2124+ return c
2125+ end
2126+
2127+ # Check if there's an unmatched opening quote before the cursor
2128+ function has_unmatched_quote (buf:: IOBuffer , quote_char:: Char )
2129+ pos = position (buf)
2130+ content = String (buf. data[1 : pos])
2131+ isempty (content) && return false
2132+
2133+ # Count unescaped quotes before cursor position
2134+ count = 0
2135+ i = 1
2136+ while i <= length (content)
2137+ if content[i] == quote_char
2138+ # Check if escaped by counting preceding backslashes
2139+ num_backslashes = 0
2140+ j = i - 1
2141+ while j >= 1 && content[j] == ' \\ '
2142+ num_backslashes += 1
2143+ j -= 1
2144+ end
2145+ # If even number of backslashes (including zero), the quote is not escaped
2146+ if num_backslashes % 2 == 0
2147+ count += 1
2148+ end
2149+ end
2150+ i = nextind (content, i)
2151+ end
2152+ return isodd (count)
2153+ end
2154+
2155+ # Left/right bracket pairs
2156+ bracket_pairs = ((' (' , ' )' ), (' {' , ' }' ), (' [' , ' ]' ))
2157+ right_brackets_ws = (' )' , ' }' , ' ]' , ' ' , ' \t ' , ' \n ' )
2158+
2159+ for (left, right) in bracket_pairs
2160+ # Left bracket: insert both and move cursor between them
2161+ bracket_insert_keymap[left] = (s:: MIState , o... ) -> begin
2162+ buf = buffer (s)
2163+ edit_insert (buf, left)
2164+ if eof (buf) || peek (buf, Char) in right_brackets_ws
2165+ edit_insert (buf, right)
2166+ edit_move_left (buf)
2167+ end
2168+ refresh_line (s)
2169+ end
2170+
2171+ # Right bracket: skip over if next char matches, otherwise insert
2172+ bracket_insert_keymap[right] = (s:: MIState , o... ) -> begin
2173+ buf = buffer (s)
2174+ if ! eof (buf) && peek (buf, Char) == right
2175+ edit_move_right (buf)
2176+ else
2177+ edit_insert (buf, right)
2178+ end
2179+ refresh_line (s)
2180+ end
2181+ end
2182+
2183+ # Quote characters (need special handling for transpose detection)
2184+ for quote_char in (' "' , ' \' ' , ' `' )
2185+ bracket_insert_keymap[quote_char] = (s:: MIState , o... ) -> begin
2186+ buf = buffer (s)
2187+ if ! eof (buf) && peek (buf, Char) == quote_char
2188+ # Skip over closing quote
2189+ edit_move_right (buf)
2190+ elseif position (buf) > 0 && should_skip_closing_bracket (peek_char_left (buf), quote_char)
2191+ # Don't auto-close (e.g., for transpose or triple quotes)
2192+ edit_insert (buf, quote_char)
2193+ elseif quote_char in (' "' , ' \' ' , ' `' ) && has_unmatched_quote (buf, quote_char)
2194+ # For quotes, check if we're closing an existing string
2195+ edit_insert (buf, quote_char)
2196+ else
2197+ # Insert both quotes
2198+ edit_insert (buf, quote_char)
2199+ edit_insert (buf, quote_char)
2200+ edit_move_left (buf)
2201+ end
2202+ refresh_line (s)
2203+ end
2204+ end
2205+
2206+ # Backspace - also remove matching closing bracket/quote
2207+ bracket_insert_keymap[' \b ' ] = (s:: MIState , o... ) -> begin
2208+ if is_region_active (s)
2209+ return edit_kill_region (s)
2210+ elseif isempty (s) || position (buffer (s)) == 0
2211+ # Handle transitioning to main mode
2212+ repl = Base. active_repl
2213+ mirepl = isdefined (repl, :mi ) ? repl. mi : repl
2214+ main_mode = mirepl. interface. modes[1 ]
2215+ buf = copy (buffer (s))
2216+ transition (s, main_mode) do
2217+ state (s, main_mode). input_buffer = buf
2218+ end
2219+ return
2220+ end
2221+
2222+ buf = buffer (s)
2223+ left_brackets = (' (' , ' {' , ' [' , ' "' , ' \' ' , ' `' )
2224+ right_brackets = (' )' , ' }' , ' ]' , ' "' , ' \' ' , ' `' )
2225+
2226+ if ! eof (buf) && position (buf) > 0
2227+ left_char = peek_char_left (buf)
2228+ i = findfirst (isequal (left_char), left_brackets)
2229+ if i != = nothing && peek (buf, Char) == right_brackets[i]
2230+ # Remove both the left and right bracket/quote
2231+ edit_delete (buf)
2232+ edit_backspace (buf)
2233+ return refresh_line (s)
2234+ end
2235+ end
2236+ return edit_backspace (s)
2237+ end
2238+ end
2239+
21042240mutable struct HistoryPrompt <: TextInterface
21052241 hp:: HistoryProvider
21062242 complete:: CompletionProvider
0 commit comments