Skip to content

Commit 80fd656

Browse files
authored
👌 IMPROVE: Allow copying empty lines (#127)
1 parent fa0845b commit 80fd656

File tree

6 files changed

+60
-14
lines changed

6 files changed

+60
-14
lines changed

‎.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,5 @@ _build/
112112
node_modules/
113113

114114
.DS_Store
115+
116+
.idea

‎doc/index.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,18 @@ strip the prompts), use the following configuration in ``conf.py``:
268268
269269
copybutton_remove_prompts = False
270270
271+
Configure whether *empty* lines are copied
272+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
273+
274+
By default, sphinx-copybutton will also copy / pass through empty lines,
275+
determined by ``line.trim() === ''``.
276+
277+
To disable copying empty lines, use the following configuration in ``conf.py``:
278+
279+
.. code-block:: python
280+
281+
copybutton_copy_empty_lines = False
282+
271283
Use a different copy button image
272284
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
273285

‎sphinx_copybutton/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ def add_to_context(app, config):
2727
config.html_context.update(
2828
{"copybutton_remove_prompts": config.copybutton_remove_prompts}
2929
)
30+
config.html_context.update(
31+
{"copybutton_copy_empty_lines": config.copybutton_copy_empty_lines}
32+
)
3033
config.html_context.update({"copybutton_image_path": config.copybutton_image_path})
3134
config.html_context.update({"copybutton_selector": config.copybutton_selector})
3235
config.html_context.update(
@@ -50,6 +53,7 @@ def setup(app):
5053
app.add_config_value("copybutton_prompt_is_regexp", False, "html")
5154
app.add_config_value("copybutton_only_copy_prompt_lines", True, "html")
5255
app.add_config_value("copybutton_remove_prompts", True, "html")
56+
app.add_config_value("copybutton_copy_empty_lines", True, "html")
5357
app.add_config_value("copybutton_image_path", "copy-button.svg", "html")
5458
app.add_config_value("copybutton_selector", "div.highlight pre", "html")
5559

‎sphinx_copybutton/_static/copybutton.js_t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const addCopyButtonToCodeCells = () => {
9191

9292
var copyTargetText = (trigger) => {
9393
var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
94-
return formatCopyText(target.innerText, {{ "{!r}".format(copybutton_prompt_text) }}, {{ copybutton_prompt_is_regexp | lower }}, {{ copybutton_only_copy_prompt_lines | lower }}, {{ copybutton_remove_prompts | lower }})
94+
return formatCopyText(target.innerText, {{ "{!r}".format(copybutton_prompt_text) }}, {{ copybutton_prompt_is_regexp | lower }}, {{ copybutton_only_copy_prompt_lines | lower }}, {{ copybutton_remove_prompts | lower }}, {{ copybutton_copy_empty_lines | lower }})
9595
}
9696

9797
// Initialize with a callback so we can modify the text before copy

‎sphinx_copybutton/_static/copybutton_funcs.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ function escapeRegExp(string) {
44

55
// Callback when a copy button is clicked. Will be passed the node that was clicked
66
// should then grab the text and replace pieces of text that shouldn't be used in output
7-
export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true) {
7+
export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true) {
88

99
var regexp;
1010
var match;
@@ -27,10 +27,10 @@ export function formatCopyText(textContent, copybuttonPromptText, isRegexp = fal
2727
} else {
2828
outputLines.push(line)
2929
}
30-
} else {
31-
if (!onlyCopyPromptLines) {
32-
outputLines.push(line)
33-
}
30+
} else if (!onlyCopyPromptLines) {
31+
outputLines.push(line)
32+
} else if (copyEmptyLines && line.trim() === '') {
33+
outputLines.push(line)
3434
}
3535
}
3636

‎sphinx_copybutton/_static/test.js

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ output
3030
isRegexp: false,
3131
onlyCopyPromptLines: true,
3232
removePrompts: true,
33-
expected: 'first\nsecond'
33+
expected: '\nfirst\nsecond'
3434
},
3535
{
3636
description: 'with non-regexp console prompt',
@@ -42,7 +42,7 @@ $ second`,
4242
isRegexp: false,
4343
onlyCopyPromptLines: true,
4444
removePrompts: true,
45-
expected: 'first\nsecond'
45+
expected: '\nfirst\nsecond'
4646
},
4747
{
4848
description: 'with non-regexp prompt, keep prompt',
@@ -54,7 +54,7 @@ output
5454
isRegexp: false,
5555
onlyCopyPromptLines: true,
5656
removePrompts: false,
57-
expected: '>>> first\n>>> second'
57+
expected: '\n>>> first\n>>> second'
5858
},
5959
{
6060
description: 'with non-regexp prompt, keep lines',
@@ -90,6 +90,34 @@ output
9090
isRegexp: true,
9191
onlyCopyPromptLines: true,
9292
removePrompts: true,
93+
expected: '\nfirst\nsecond'
94+
},
95+
{
96+
description: 'with regexp python prompt and empty lines',
97+
text: `
98+
>>> first
99+
output
100+
101+
>>> second`,
102+
prompt: '>>> ',
103+
isRegexp: true,
104+
onlyCopyPromptLines: true,
105+
removePrompts: true,
106+
copyEmptyLines: true,
107+
expected: '\nfirst\n\nsecond'
108+
},
109+
{
110+
description: 'with regexp python prompt and empty lines, ignore empty lines',
111+
text: `
112+
>>> first
113+
output
114+
115+
>>> second`,
116+
prompt: '>>> ',
117+
isRegexp: true,
118+
onlyCopyPromptLines: true,
119+
removePrompts: true,
120+
copyEmptyLines: false,
93121
expected: 'first\nsecond'
94122
},
95123
{
@@ -102,7 +130,7 @@ $ second`,
102130
isRegexp: true,
103131
onlyCopyPromptLines: true,
104132
removePrompts: true,
105-
expected: 'first\nsecond'
133+
expected: '\nfirst\nsecond'
106134
},
107135
{
108136
description: 'with ipython prompt (old and new style) regexp',
@@ -116,7 +144,7 @@ In [3]: jupyter`,
116144
isRegexp: true,
117145
onlyCopyPromptLines: true,
118146
removePrompts: true,
119-
expected: 'first\ncontinuation\nsecond\njupyter',
147+
expected: '\nfirst\ncontinuation\nsecond\njupyter',
120148
},
121149
{
122150
description: 'with ipython prompt (old and new style) regexp, keep prompts',
@@ -130,7 +158,7 @@ In [3]: jupyter`,
130158
isRegexp: true,
131159
onlyCopyPromptLines: true,
132160
removePrompts: false,
133-
expected: '[1]: first\n...: continuation\n[2]: second\nIn [3]: jupyter',
161+
expected: '\n[1]: first\n...: continuation\n[2]: second\nIn [3]: jupyter',
134162
},
135163
{
136164
/* The following is included to demonstrate an example of a false positive regex test.
@@ -147,13 +175,13 @@ output
147175
isRegexp: true,
148176
onlyCopyPromptLines: true,
149177
removePrompts: true,
150-
expected: 'first\ncontinuation\nsecond'
178+
expected: '\nfirst\ncontinuation\nsecond'
151179
}
152180
]
153181

154182
parameters.forEach((p) => {
155183
test(p.description, t => {
156-
const text = formatCopyText(p.text, p.prompt, p.isRegexp, p.onlyCopyPromptLines, p.removePrompts);
184+
const text = formatCopyText(p.text, p.prompt, p.isRegexp, p.onlyCopyPromptLines, p.removePrompts, p.copyEmptyLines);
157185
t.is(text, p.expected)
158186
});
159187
})

0 commit comments

Comments
 (0)