/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
stdlib/REPL/src/StylingPasses.jl
165 строк
5 KB
Sam Schweigel
[REPL] Highlight enclosing parens like Emacs (#60992)
01 июл 2026, 02:22
Не верифицирован
01 июл 2026, 02:22
7a691d2
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license # Each pass takes the input string and returns an AnnotatedString with styling annotations module StylingPasses using StyledStrings using StyledStrings: Face using JuliaSyntaxHighlighting import Base: AnnotatedString, annotate!, annotations, JuliaSyntax export StylingPass, StylingContext, SyntaxHighlightPass, RegionHighlightPass, EnclosingParenHighlightPass, apply_styling_passes, merge_annotations # Context information passed to all styling passes struct StylingContext cursor_pos::Int # -1 when cursor is not shown (e.g. after hitting enter) region_start::Int region_stop::Int end StylingContext(cursor_pos::Int) = StylingContext(cursor_pos, 0, 0) abstract type StylingPass end function merge_annotations(annotated_strings::Vector{<:AnnotatedString}) isempty(annotated_strings) && return AnnotatedString("") result = AnnotatedString(annotated_strings[1]) for source in annotated_strings for ann in annotations(source) annotate!(result, ann.region, ann.label, ann.value) end end return result end function apply_style(pass::StylingPass, input::String, ast, context::StylingContext) return pass(input, ast, context)::AnnotatedString{String} end function apply_styling_passes(input::String, passes::Vector{StylingPass}, context::StylingContext) if isempty(passes) return AnnotatedString(input) end # Parse once and share AST across all passes ast = JuliaSyntax.parseall(JuliaSyntax.GreenNode, input; ignore_errors=true) results = [apply_style(pass, input, ast, context) for pass in passes] return merge_annotations(results) end # Applies Julia syntax highlighting struct SyntaxHighlightPass <: StylingPass end function (::SyntaxHighlightPass)(input::String, ast, ::StylingContext) try return JuliaSyntaxHighlighting.highlight(input, ast) catch e @error "Error in SyntaxHighlightPass" exception=(e, catch_backtrace()) maxlog=1 return AnnotatedString(input) end end # Applies inverse video styling to the selected region struct RegionHighlightPass <: StylingPass end function (::RegionHighlightPass)(input::String, ::Any, context::StylingContext) result = AnnotatedString(input) if context.region_start > 0 && context.region_stop >= context.region_start # Add inverse face to the region # Region positions are 1-based byte positions region_range = context.region_start:context.region_stop annotate!(result, region_range, :face, Face(inverse=true)) end return result end # Applies bold styling to parentheses that enclose the cursor position struct EnclosingParenHighlightPass <: StylingPass face::Face end EnclosingParenHighlightPass() = EnclosingParenHighlightPass(Face(weight=:bold, underline=true)) function (pass::EnclosingParenHighlightPass)(input::String, ast, context::StylingContext) result = AnnotatedString(input) if isempty(input) return result end try paren_pairs = find_enclosing_parens(input, ast, context.cursor_pos) for (open_pos, close_pos) in paren_pairs annotate!(result, open_pos:open_pos, :face, pass.face) annotate!(result, close_pos:close_pos, :face, pass.face) end catch e @error "Error in EnclosingParenHighlightPass" exception=(e, catch_backtrace()) maxlog=1 end return result end function paren_type(k) if k == JuliaSyntax.K"("; 1, :paren elseif k == JuliaSyntax.K")"; -1, :paren elseif k == JuliaSyntax.K"["; 1, :bracket elseif k == JuliaSyntax.K"]"; -1, :bracket elseif k == JuliaSyntax.K"{"; 1, :curly elseif k == JuliaSyntax.K"}"; -1, :curly else 0, :none end end function find_enclosing_parens(content::String, ast, cursor_pos::Int) innermost_pairs = Dict{Symbol,Tuple{Int,Int}}() paren_stack = Tuple{Int,Int,Symbol}[] # (open_pos, depth, type) walk_tree(ast, content, UInt32(0)) do node, offset nkind = JuliaSyntax.kind(node) pos = firstindex(content) + offset depthchange, ptype = paren_type(nkind) if ptype != :none if depthchange > 0 # Opening paren - push to stack push!(paren_stack, (pos, length(paren_stack) + 1, ptype)) elseif depthchange < 0 && !isempty(paren_stack) # Closing paren - pop from stack and check if cursor is inside open_pos, depth, open_ptype = pop!(paren_stack) if open_ptype == ptype && open_pos <= cursor_pos <= nextind(content, pos) # Cursor is inside this paren pair - keep only innermost per type # Only update if this is the first pair or if it's smaller (more inner) than existing if !haskey(innermost_pairs, ptype) || (pos - open_pos) < (innermost_pairs[ptype][2] - innermost_pairs[ptype][1]) innermost_pairs[ptype] = (open_pos, pos) end end end end end return collect(values(innermost_pairs)) end function walk_tree(f::Function, node, content::String, offset::UInt32) f(node, offset) if JuliaSyntax.numchildren(node) > 0 for child in JuliaSyntax.children(node) walk_tree(f, child, content, offset) offset += JuliaSyntax.span(child) end end end end # module StylingPasses