• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / 1459

03 Mar 2026 05:42AM UTC coverage: 76.679% (-0.1%) from 76.816%
1459

push

buildkite

web-flow
REPL: add OSC 52 clipboard fallback and graceful degradation (#61151)

When pressing Ctrl-S → Clipboard in REPL history search on a system
without clipboard tools (xsel, xclip, wl-clipboard), a
TaskFailedException stack trace was thrown. This adds:

- `OncePerId{T}`: a new callable struct that caches `initializer(key)`
per mutable key object via WeakKeyDict, for general-purpose per-object
caching
- `has_system_clipboard()` in InteractiveUtils to detect native
clipboard availability per platform
- OSC 52 terminal clipboard support as a fallback, with proper DA1
(Primary Device Attributes) query to detect terminal capability
- Graceful degradation: when neither clipboard mechanism is available,
the Clipboard option is skipped and a friendly message is shown instead
of a stack trace

Fixes #60145

Co-authored-by: Keno Fischer <Keno@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

0 of 30 new or added lines in 3 files covered. (0.0%)

950 existing lines in 17 files now uncovered.

63347 of 82613 relevant lines covered (76.68%)

23390622.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.54
/stdlib/InteractiveUtils/src/clipboard.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
# clipboard copy and paste
4

5
"""
6
    has_system_clipboard() -> Bool
7

8
Return `true` if the operating system has a clipboard mechanism available.
9
On macOS and Windows this is always `true`. On Linux and FreeBSD, it checks
10
for the presence of `xclip`, `xsel`, or `wl-copy` (from `wl-clipboard`).
11
On other systems, returns `false`.
12

13
!!! compat "Julia 1.14"
14
    This function requires Julia 1.14 or later.
15

16
See also [`clipboard`](@ref).
17
"""
18
function has_system_clipboard end
19

20
if Sys.isapple()
NEW
21
    has_system_clipboard() = true
×
22
    function clipboard(x)
1✔
23
        pbcopy_cmd = `pbcopy`
4✔
24

25
        # OSX shells, especially when run within `tmux` or `screen`, can be
26
        # disconnected from the global shell namespace, which causes problems
27
        # with clipboards.  Luckily, the `reattach-to-user-namespace` utility
28
        # dodges these issues quite nicely, so we automatically utilize it if
29
        # it is installed.
30
        if Sys.which("reattach-to-user-namespace") !== nothing
4✔
31
            pbcopy_cmd = `reattach-to-user-namespace pbcopy`
×
32
        end
33

34
        open(pipeline(pbcopy_cmd, stderr=stderr), "w") do io
4✔
35
            print(io, x)
4✔
36
        end
37
        nothing
4✔
38
    end
39
    function clipboard()
4✔
40
        pbpaste_cmd = `pbpaste`
4✔
41

42
        # See above comment in `clipboard(x)`
43
        if Sys.which("reattach-to-user-namespace") !== nothing
4✔
44
            pbpaste_cmd = `reattach-to-user-namespace pbpaste`
×
45
        end
46
        return read(pbpaste_cmd, String)
4✔
47
    end
48

49
elseif Sys.islinux() || Sys.KERNEL === :FreeBSD
NEW
50
    function has_system_clipboard()
×
NEW
51
        try
×
NEW
52
            clipboardcmd()
×
NEW
53
            return true
×
54
        catch
NEW
55
            return false
×
56
        end
57
    end
58
    _clipboardcmd = nothing
59
    const _clipboard_copy = Dict(
60
            :xsel  => Sys.islinux() ?
61
                `xsel --input --clipboard` :
62
                `xsel -c`,
63
            :xclip => `xclip -silent -in -selection clipboard`,
64
            :wlclipboard => `wl-copy`
65
        )
66
    const _clipboard_paste = Dict(
67
            :xsel  => Sys.islinux() ?
68
                `xsel --nodetach --output --clipboard` :
69
                `xsel -p`,
70
            :xclip => `xclip -quiet -out -selection clipboard`,
71
            :wlclipboard => `wl-paste`
72
        )
73
    function clipboardcmd()
×
74
        global _clipboardcmd
×
75
        _clipboardcmd !== nothing && return _clipboardcmd
×
76
        for cmd in (:xclip, :xsel, :wlclipboard)
×
77
            # wl-clipboard ships wl-copy/paste individually
78
            c = cmd === :wlclipboard ? Symbol("wl-copy") : cmd
×
79
            success(pipeline(`which $c`, devnull)) && return _clipboardcmd = cmd
×
80
        end
×
81
        pkgs = @static if Sys.KERNEL === :FreeBSD
×
82
            "x11/xsel or x11/xclip"
×
83
        else
84
            "xsel or xclip or wl-clipboard"
×
85
        end
86
        error("no clipboard command found, please install $pkgs")
×
87
    end
88
    function clipboard(x)
×
89
        c = clipboardcmd()
×
90
        cmd = _clipboard_copy[c]
×
91
        open(pipeline(cmd, stderr=stderr), "w") do io
×
92
            print(io, x)
×
93
        end
94
        nothing
×
95
    end
96
    function clipboard()
×
97
        c = clipboardcmd()
×
98
        cmd = _clipboard_paste[c]
×
99
        return read(pipeline(cmd, stderr=stderr), String)
×
100
    end
101

102
elseif Sys.iswindows()
NEW
103
    has_system_clipboard() = true
×
104
    function clipboard(x::AbstractString)
4✔
105
        if Base.containsnul(x)
4✔
106
            throw(ArgumentError("Windows clipboard strings cannot contain NUL character"))
1✔
107
        end
108
        x_u16 = Base.cwstring(x)
3✔
109
        pdata = Ref(Ptr{UInt16}(C_NULL))
3✔
110
        function cleanup(cause)
3✔
111
            errno = cause === :success ? UInt32(0) : Libc.GetLastError()
3✔
112
            if cause !== :OpenClipboard
3✔
113
                if cause !== :success && pdata[] != C_NULL
3✔
114
                    ccall((:GlobalFree, "kernel32"), stdcall, Cint, (Ptr{UInt16},), pdata[])
×
115
                end
116
                ccall((:CloseClipboard, "user32"), stdcall, Cint, ()) == 0 && Base.windowserror(:CloseClipboard) # this should never fail
3✔
117
            end
118
            cause === :success || Base.windowserror(cause, errno)
3✔
119
            nothing
3✔
120
        end
121
        ccall((:OpenClipboard, "user32"), stdcall, Cint, (Ptr{Cvoid},), C_NULL) == 0 && return Base.windowserror(:OpenClipboard)
3✔
122
        ccall((:EmptyClipboard, "user32"), stdcall, Cint, ()) == 0 && return cleanup(:EmptyClipboard)
3✔
123
        # copy data to locked, allocated space
124
        pdata[] = ccall((:GlobalAlloc, "kernel32"), stdcall, Ptr{UInt16}, (Cuint, Csize_t), 2 #=GMEM_MOVEABLE=#, sizeof(x_u16))
3✔
125
        pdata[] == C_NULL && return cleanup(:GlobalAlloc)
3✔
126
        plock = ccall((:GlobalLock, "kernel32"), stdcall, Ptr{UInt16}, (Ptr{UInt16},), pdata[])
3✔
127
        plock == C_NULL && return cleanup(:GlobalLock)
3✔
128
        GC.@preserve x_u16 memcpy(plock, Base.unsafe_convert(Ptr{UInt16}, Base.cconvert(Ptr{UInt16}, x_u16)), sizeof(x_u16))
3✔
129
        unlock = ccall((:GlobalUnlock, "kernel32"), stdcall, Cint, (Ptr{UInt16},), pdata[])
3✔
130
        (unlock == 0 && Libc.GetLastError() == 0) || return cleanup(:GlobalUnlock) # this should never fail
3✔
131
        pset = ccall((:SetClipboardData, "user32"), stdcall, Ptr{UInt16}, (Cuint, Ptr{UInt16}), 13, pdata[]) # CF_UNICODETEXT
3✔
132
        pdata[] != pset && return cleanup(:SetClipboardData)
3✔
133
        cleanup(:success)
3✔
134
    end
135
    clipboard(x) = clipboard(sprint(print, x)::String)
×
136
    function clipboard()
4✔
137
        function cleanup(cause)
4✔
138
            errno = cause === :success ? UInt32(0) : Libc.GetLastError()
4✔
139
            if cause !== :OpenClipboard
4✔
140
                ccall((:CloseClipboard, "user32"), stdcall, Cint, ()) == 0 && Base.windowserror(:CloseClipboard) # this should never fail
4✔
141
            end
142
            if cause !== :success && !(cause === :GetClipboardData && (errno == 0x8004006A || errno == 0x800401D3)) # ignore DV_E_CLIPFORMAT and CLIPBRD_E_BAD_DATA from GetClipboardData
4✔
143
                Base.windowserror(cause, errno)
×
144
            end
145
            ""
146
        end
147
        ccall((:OpenClipboard, "user32"), stdcall, Cint, (Ptr{Cvoid},), C_NULL) == 0 && return Base.windowserror(:OpenClipboard)
4✔
148
        ccall(:SetLastError, stdcall, Cvoid, (UInt32,), 0) # allow distinguishing if the clipboard simply didn't have text
4✔
149
        pdata = ccall((:GetClipboardData, "user32"), stdcall, Ptr{UInt16}, (Cuint,), 13) # CF_UNICODETEXT
4✔
150
        pdata == C_NULL && return cleanup(:GetClipboardData)
4✔
151
        plock = ccall((:GlobalLock, "kernel32"), stdcall, Ptr{UInt16}, (Ptr{UInt16},), pdata)
3✔
152
        plock == C_NULL && return cleanup(:GlobalLock)
3✔
153
        s = try
3✔
154
            # find NUL terminator (0x0000 16-bit code unit)
155
            len = 0
3✔
156
            while unsafe_load(plock, len + 1) != 0
23✔
157
                len += 1
20✔
158
            end
20✔
159
            # get Vector{UInt16}, transcode data to UTF-8, make a String of it
160
            transcode(String, unsafe_wrap(Array, plock, len))
5✔
161
        finally
162
           unlock = ccall((:GlobalUnlock, "kernel32"), stdcall, Cint, (Ptr{UInt16},), pdata)
3✔
163
           (unlock != 0 || Libc.GetLastError() == 0) || return cleanup(:GlobalUnlock) # this should never fail
3✔
164
           cleanup(:success)
3✔
165
        end
166
        return s
3✔
167
    end
168

169
else
NEW
170
    has_system_clipboard() = false
×
UNCOV
171
    clipboard(x="") = error("`clipboard` function not implemented for $(Sys.KERNEL)")
×
172
end
173

174

175
"""
176
    clipboard(x)
177

178
Send a printed form of `x` to the operating system clipboard ("copy").
179
"""
180
clipboard(x)
181

182
"""
183
    clipboard()::String
184

185
Return a string with the contents of the operating system clipboard ("paste").
186
"""
187
clipboard()
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc