spyder-copy-current 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spyder_copy_current-0.1.0/LICENSE +21 -0
- spyder_copy_current-0.1.0/MANIFEST.in +3 -0
- spyder_copy_current-0.1.0/PKG-INFO +94 -0
- spyder_copy_current-0.1.0/README-pypi.md +60 -0
- spyder_copy_current-0.1.0/README.md +68 -0
- spyder_copy_current-0.1.0/assets/spyder_copy_current.png +0 -0
- spyder_copy_current-0.1.0/pyproject.toml +3 -0
- spyder_copy_current-0.1.0/setup.cfg +52 -0
- spyder_copy_current-0.1.0/setup.py +4 -0
- spyder_copy_current-0.1.0/spyder_copy_current/__init__.py +1 -0
- spyder_copy_current-0.1.0/spyder_copy_current/plugin.py +592 -0
- spyder_copy_current-0.1.0/spyder_copy_current/spyder/__init__.py +0 -0
- spyder_copy_current-0.1.0/spyder_copy_current/spyder/plugin.py +4 -0
- spyder_copy_current-0.1.0/spyder_copy_current.egg-info/PKG-INFO +94 -0
- spyder_copy_current-0.1.0/spyder_copy_current.egg-info/SOURCES.txt +18 -0
- spyder_copy_current-0.1.0/spyder_copy_current.egg-info/dependency_links.txt +1 -0
- spyder_copy_current-0.1.0/spyder_copy_current.egg-info/entry_points.txt +2 -0
- spyder_copy_current-0.1.0/spyder_copy_current.egg-info/requires.txt +3 -0
- spyder_copy_current-0.1.0/spyder_copy_current.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Miroslav Hruska
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spyder-copy-current
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Spyder plugin to copy the current file path and kernel connection info
|
|
5
|
+
Home-page: https://github.com/hruskamiro/spyder-copy-current
|
|
6
|
+
Author: Miroslav Hruska
|
|
7
|
+
Author-email: hruska.miro@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Source, https://github.com/hruskamiro/spyder-copy-current
|
|
10
|
+
Project-URL: Issues, https://github.com/hruskamiro/spyder-copy-current/issues
|
|
11
|
+
Keywords: spyder,plugin,ipython,jupyter,clipboard,kernel
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: IPython
|
|
14
|
+
Classifier: Framework :: Jupyter
|
|
15
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Topic :: Text Editors :: Integrated Development Environments (IDE)
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: jupyter_client<9,>=7.4.9
|
|
31
|
+
Requires-Dist: spyder<7,>=5.4
|
|
32
|
+
Requires-Dist: qtpy
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# spyder-copy-current
|
|
36
|
+
|
|
37
|
+
Small Spyder plugin that copies the current editor file path and the active IPython kernel connection info to the clipboard.
|
|
38
|
+
|
|
39
|
+
Default shortcuts:
|
|
40
|
+
|
|
41
|
+
- **`Ctrl+Alt+C`**: copy the current file path; press twice for `{"path": "<path>", "cell": "<cell>"}`; press three times for `{"path": "<path>", "cell": "<cell>", "line": <line>}`
|
|
42
|
+
- **`Ctrl+Alt+K`**: copy the active IPython kernel connection info
|
|
43
|
+
|
|
44
|
+
## Supported Versions
|
|
45
|
+
|
|
46
|
+
- Spyder `5.4.x` through `6.x`
|
|
47
|
+
- Python `3.8+`
|
|
48
|
+
|
|
49
|
+
## Behavior
|
|
50
|
+
|
|
51
|
+
- Copies the current file path to the system clipboard.
|
|
52
|
+
- Copies `{"path": "<path>", "cell": "<cell>"}` on the second quick press, and `{"path": "<path>", "cell": "<cell>", "line": <line>}` on the third quick press.
|
|
53
|
+
- Copies the current IPython kernel connection info to the system clipboard.
|
|
54
|
+
- Works from the active editor.
|
|
55
|
+
- Works from the active IPython console for kernel connection info.
|
|
56
|
+
- If the current tab has not been saved yet, it shows a message instead of copying anything.
|
|
57
|
+
- If the current IPython console does not expose kernel connection info, it shows a message instead of copying anything.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
Inside the same environment where Spyder runs:
|
|
62
|
+
|
|
63
|
+
Install the latest released version from PyPI:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install spyder-copy-current
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or install directly from GitHub:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install "git+https://github.com/hruskamiro/spyder-copy-current.git@main"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then restart Spyder.
|
|
76
|
+
|
|
77
|
+
## Compatibility Notes
|
|
78
|
+
|
|
79
|
+
- The plugin is intended for Spyder `5.4.x` through `6.x`.
|
|
80
|
+
- Existing user-defined Spyder shortcuts are preserved.
|
|
81
|
+
- The default shortcuts are only assigned if the corresponding Spyder shortcut entry is still empty.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
- Press `Ctrl+Alt+C` in the editor to copy the current file path. Press it twice in quick succession to copy `{"path": "<path>", "cell": "<cell>"}`; press it three times to include the cursor line number.
|
|
86
|
+
- Press `Ctrl+Alt+K` in the IPython console to copy the current kernel connection info.
|
|
87
|
+
|
|
88
|
+
The kernel connection info is copied in a `%connect_info`-style format so it can be reused with Jupyter clients when available.
|
|
89
|
+
|
|
90
|
+
## Shortcut
|
|
91
|
+
|
|
92
|
+
The plugin registers a keyboard shortcut in Spyder's shortcuts preferences under the editor context.
|
|
93
|
+
If that shortcut is still empty, it assigns `Ctrl+Alt+C` by default. Repeating it quickly adds the nearest cell marker, then the cursor line number.
|
|
94
|
+
It also registers a keyboard shortcut in the IPython console context and assigns `Ctrl+Alt+K` by default if that shortcut is still empty.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# spyder-copy-current
|
|
2
|
+
|
|
3
|
+
Small Spyder plugin that copies the current editor file path and the active IPython kernel connection info to the clipboard.
|
|
4
|
+
|
|
5
|
+
Default shortcuts:
|
|
6
|
+
|
|
7
|
+
- **`Ctrl+Alt+C`**: copy the current file path; press twice for `{"path": "<path>", "cell": "<cell>"}`; press three times for `{"path": "<path>", "cell": "<cell>", "line": <line>}`
|
|
8
|
+
- **`Ctrl+Alt+K`**: copy the active IPython kernel connection info
|
|
9
|
+
|
|
10
|
+
## Supported Versions
|
|
11
|
+
|
|
12
|
+
- Spyder `5.4.x` through `6.x`
|
|
13
|
+
- Python `3.8+`
|
|
14
|
+
|
|
15
|
+
## Behavior
|
|
16
|
+
|
|
17
|
+
- Copies the current file path to the system clipboard.
|
|
18
|
+
- Copies `{"path": "<path>", "cell": "<cell>"}` on the second quick press, and `{"path": "<path>", "cell": "<cell>", "line": <line>}` on the third quick press.
|
|
19
|
+
- Copies the current IPython kernel connection info to the system clipboard.
|
|
20
|
+
- Works from the active editor.
|
|
21
|
+
- Works from the active IPython console for kernel connection info.
|
|
22
|
+
- If the current tab has not been saved yet, it shows a message instead of copying anything.
|
|
23
|
+
- If the current IPython console does not expose kernel connection info, it shows a message instead of copying anything.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
Inside the same environment where Spyder runs:
|
|
28
|
+
|
|
29
|
+
Install the latest released version from PyPI:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install spyder-copy-current
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or install directly from GitHub:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install "git+https://github.com/hruskamiro/spyder-copy-current.git@main"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then restart Spyder.
|
|
42
|
+
|
|
43
|
+
## Compatibility Notes
|
|
44
|
+
|
|
45
|
+
- The plugin is intended for Spyder `5.4.x` through `6.x`.
|
|
46
|
+
- Existing user-defined Spyder shortcuts are preserved.
|
|
47
|
+
- The default shortcuts are only assigned if the corresponding Spyder shortcut entry is still empty.
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
- Press `Ctrl+Alt+C` in the editor to copy the current file path. Press it twice in quick succession to copy `{"path": "<path>", "cell": "<cell>"}`; press it three times to include the cursor line number.
|
|
52
|
+
- Press `Ctrl+Alt+K` in the IPython console to copy the current kernel connection info.
|
|
53
|
+
|
|
54
|
+
The kernel connection info is copied in a `%connect_info`-style format so it can be reused with Jupyter clients when available.
|
|
55
|
+
|
|
56
|
+
## Shortcut
|
|
57
|
+
|
|
58
|
+
The plugin registers a keyboard shortcut in Spyder's shortcuts preferences under the editor context.
|
|
59
|
+
If that shortcut is still empty, it assigns `Ctrl+Alt+C` by default. Repeating it quickly adds the nearest cell marker, then the cursor line number.
|
|
60
|
+
It also registers a keyboard shortcut in the IPython console context and assigns `Ctrl+Alt+K` by default if that shortcut is still empty.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# spyder-copy-current
|
|
2
|
+
|
|
3
|
+
Small Spyder plugin that copies the current editor file path and the active IPython kernel connection info to the clipboard.
|
|
4
|
+
|
|
5
|
+
Default shortcuts:
|
|
6
|
+
|
|
7
|
+
- **`Ctrl+Alt+C`**: copy the current file path; press twice for `{"path": "<path>", "cell": "<cell>"}`; press three times for `{"path": "<path>", "cell": "<cell>", "line": <line>}`
|
|
8
|
+
- **`Ctrl+Alt+K`**: copy the active IPython kernel connection info
|
|
9
|
+
|
|
10
|
+
<!-- pypi-remove-start -->
|
|
11
|
+
## Screenshot
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
<!-- pypi-remove-end -->
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Supported Versions
|
|
19
|
+
|
|
20
|
+
- Spyder `5.4.x` through `6.x`
|
|
21
|
+
- Python `3.8+`
|
|
22
|
+
|
|
23
|
+
## Behavior
|
|
24
|
+
|
|
25
|
+
- Copies the current file path to the system clipboard.
|
|
26
|
+
- Copies `{"path": "<path>", "cell": "<cell>"}` on the second quick press, and `{"path": "<path>", "cell": "<cell>", "line": <line>}` on the third quick press.
|
|
27
|
+
- Copies the current IPython kernel connection info to the system clipboard.
|
|
28
|
+
- Works from the active editor.
|
|
29
|
+
- Works from the active IPython console for kernel connection info.
|
|
30
|
+
- If the current tab has not been saved yet, it shows a message instead of copying anything.
|
|
31
|
+
- If the current IPython console does not expose kernel connection info, it shows a message instead of copying anything.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
Inside the same environment where Spyder runs:
|
|
36
|
+
|
|
37
|
+
Install the latest released version from PyPI:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install spyder-copy-current
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or install directly from GitHub:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install "git+https://github.com/hruskamiro/spyder-copy-current.git@main"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then restart Spyder.
|
|
50
|
+
|
|
51
|
+
## Compatibility Notes
|
|
52
|
+
|
|
53
|
+
- The plugin is intended for Spyder `5.4.x` through `6.x`.
|
|
54
|
+
- Existing user-defined Spyder shortcuts are preserved.
|
|
55
|
+
- The default shortcuts are only assigned if the corresponding Spyder shortcut entry is still empty.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
- Press `Ctrl+Alt+C` in the editor to copy the current file path. Press it twice in quick succession to copy `{"path": "<path>", "cell": "<cell>"}`; press it three times to include the cursor line number.
|
|
60
|
+
- Press `Ctrl+Alt+K` in the IPython console to copy the current kernel connection info.
|
|
61
|
+
|
|
62
|
+
The kernel connection info is copied in a `%connect_info`-style format so it can be reused with Jupyter clients when available.
|
|
63
|
+
|
|
64
|
+
## Shortcut
|
|
65
|
+
|
|
66
|
+
The plugin registers a keyboard shortcut in Spyder's shortcuts preferences under the editor context.
|
|
67
|
+
If that shortcut is still empty, it assigns `Ctrl+Alt+C` by default. Repeating it quickly adds the nearest cell marker, then the cursor line number.
|
|
68
|
+
It also registers a keyboard shortcut in the IPython console context and assigns `Ctrl+Alt+K` by default if that shortcut is still empty.
|
|
Binary file
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = spyder-copy-current
|
|
3
|
+
version = 0.1.0
|
|
4
|
+
description = Spyder plugin to copy the current file path and kernel connection info
|
|
5
|
+
long_description = file: README-pypi.md
|
|
6
|
+
long_description_content_type = text/markdown
|
|
7
|
+
author = Miroslav Hruska
|
|
8
|
+
author_email = hruska.miro@gmail.com
|
|
9
|
+
license = MIT
|
|
10
|
+
license_files = LICENSE
|
|
11
|
+
url = https://github.com/hruskamiro/spyder-copy-current
|
|
12
|
+
project_urls =
|
|
13
|
+
Source = https://github.com/hruskamiro/spyder-copy-current
|
|
14
|
+
Issues = https://github.com/hruskamiro/spyder-copy-current/issues
|
|
15
|
+
keywords = spyder, plugin, ipython, jupyter, clipboard, kernel
|
|
16
|
+
classifiers =
|
|
17
|
+
Development Status :: 3 - Alpha
|
|
18
|
+
Framework :: IPython
|
|
19
|
+
Framework :: Jupyter
|
|
20
|
+
Environment :: X11 Applications :: Qt
|
|
21
|
+
Intended Audience :: Developers
|
|
22
|
+
Operating System :: OS Independent
|
|
23
|
+
Programming Language :: Python
|
|
24
|
+
Programming Language :: Python :: 3
|
|
25
|
+
Programming Language :: Python :: 3.8
|
|
26
|
+
Programming Language :: Python :: 3.9
|
|
27
|
+
Programming Language :: Python :: 3.10
|
|
28
|
+
Programming Language :: Python :: 3.11
|
|
29
|
+
Programming Language :: Python :: 3.12
|
|
30
|
+
Topic :: Software Development :: Libraries :: Python Modules
|
|
31
|
+
Topic :: Text Editors :: Integrated Development Environments (IDE)
|
|
32
|
+
|
|
33
|
+
[options]
|
|
34
|
+
packages = find:
|
|
35
|
+
python_requires = >=3.8
|
|
36
|
+
install_requires =
|
|
37
|
+
jupyter_client>=7.4.9,<9
|
|
38
|
+
spyder>=5.4,<7
|
|
39
|
+
qtpy
|
|
40
|
+
include_package_data = True
|
|
41
|
+
|
|
42
|
+
[options.packages.find]
|
|
43
|
+
include = spyder_copy_current*
|
|
44
|
+
|
|
45
|
+
[options.entry_points]
|
|
46
|
+
spyder.plugins =
|
|
47
|
+
spyder_copy_current = spyder_copy_current.spyder.plugin:CopyCurrentPlugin
|
|
48
|
+
|
|
49
|
+
[egg_info]
|
|
50
|
+
tag_build =
|
|
51
|
+
tag_date = 0
|
|
52
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Spyder plugin to copy the current editor file path."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import inspect
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from jupyter_client.connect import find_connection_file
|
|
11
|
+
from qtpy.QtGui import QGuiApplication, QIcon
|
|
12
|
+
from qtpy.QtWidgets import QMessageBox
|
|
13
|
+
|
|
14
|
+
from spyder.api.plugins import Plugins, SpyderPluginV2
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CopyCurrentPlugin(SpyderPluginV2):
|
|
18
|
+
"""Plugin that copies current editor and kernel info to the clipboard."""
|
|
19
|
+
|
|
20
|
+
NAME = "spyder_copy_current"
|
|
21
|
+
CONF_SECTION = NAME
|
|
22
|
+
CONTEXT_NAME = NAME
|
|
23
|
+
|
|
24
|
+
REQUIRES = [Plugins.Editor]
|
|
25
|
+
OPTIONAL = [plugin for plugin in (Plugins.Shortcuts, getattr(Plugins, "IPythonConsole", None)) if plugin is not None]
|
|
26
|
+
|
|
27
|
+
REPEAT_PRESS_SECONDS = 2.0
|
|
28
|
+
CELL_MARKER_RE = re.compile(r"^\s*#\s*%%\s*(.*)$")
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def get_name():
|
|
32
|
+
return "Copy Current"
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def get_description():
|
|
36
|
+
return "Copy the current editor file path or kernel info to the clipboard."
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def get_icon():
|
|
40
|
+
return QIcon()
|
|
41
|
+
|
|
42
|
+
def on_initialize(self):
|
|
43
|
+
self._file_path_copy_state = (None, 0.0, 0)
|
|
44
|
+
|
|
45
|
+
self.create_action(
|
|
46
|
+
name="copy_current_file_path",
|
|
47
|
+
text="Copy current file path",
|
|
48
|
+
tip="Copy the current editor file path to the clipboard",
|
|
49
|
+
triggered=self.copy_current_file_path,
|
|
50
|
+
shortcut_context="editor",
|
|
51
|
+
register_shortcut=True,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self.create_action(
|
|
55
|
+
name="copy_current_kernel_connection_info",
|
|
56
|
+
text="Copy current kernel connection info",
|
|
57
|
+
tip="Copy the current IPython kernel connection info to the clipboard",
|
|
58
|
+
triggered=self.copy_current_kernel_connection_info,
|
|
59
|
+
shortcut_context="ipython_console",
|
|
60
|
+
register_shortcut=True,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
self.main.addAction(self.get_action("copy_current_file_path"))
|
|
64
|
+
self.main.addAction(self.get_action("copy_current_kernel_connection_info"))
|
|
65
|
+
|
|
66
|
+
def on_mainwindow_visible(self):
|
|
67
|
+
shortcuts = self.get_plugin(Plugins.Shortcuts)
|
|
68
|
+
if shortcuts is None:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
def _uses_spyder5_shortcuts_api():
|
|
72
|
+
try:
|
|
73
|
+
params = list(inspect.signature(shortcuts.set_shortcut).parameters)
|
|
74
|
+
return bool(params) and params[0] == "context"
|
|
75
|
+
except Exception:
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
def _get_shortcut(context, name):
|
|
79
|
+
try:
|
|
80
|
+
if _uses_spyder5_shortcuts_api():
|
|
81
|
+
return shortcuts.get_shortcut(context, name)
|
|
82
|
+
return shortcuts.get_shortcut(name, context)
|
|
83
|
+
except Exception:
|
|
84
|
+
return ""
|
|
85
|
+
|
|
86
|
+
def _set_shortcut(context, name, value):
|
|
87
|
+
try:
|
|
88
|
+
if _uses_spyder5_shortcuts_api():
|
|
89
|
+
shortcuts.set_shortcut(context, name, value)
|
|
90
|
+
else:
|
|
91
|
+
shortcuts.set_shortcut(value, name, context)
|
|
92
|
+
shortcuts.apply_shortcuts()
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
if not _get_shortcut("editor", "copy_current_file_path"):
|
|
97
|
+
_set_shortcut("editor", "copy_current_file_path", "Ctrl+Alt+C")
|
|
98
|
+
|
|
99
|
+
if not _get_shortcut(
|
|
100
|
+
"ipython_console",
|
|
101
|
+
"copy_current_kernel_connection_info",
|
|
102
|
+
):
|
|
103
|
+
_set_shortcut(
|
|
104
|
+
"ipython_console",
|
|
105
|
+
"copy_current_kernel_connection_info",
|
|
106
|
+
"Ctrl+Alt+K",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def _get_editorstack(self):
|
|
110
|
+
editor_plugin = self.get_plugin(Plugins.Editor)
|
|
111
|
+
if editor_plugin is None:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
return editor_plugin.get_current_editorstack()
|
|
116
|
+
except Exception:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def _get_current_editor(stack):
|
|
121
|
+
if stack is None:
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
getter = getattr(stack, "get_current_editor", None)
|
|
125
|
+
if callable(getter):
|
|
126
|
+
try:
|
|
127
|
+
editor = getter()
|
|
128
|
+
except Exception:
|
|
129
|
+
editor = None
|
|
130
|
+
if editor is not None:
|
|
131
|
+
return editor
|
|
132
|
+
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def _get_editor_text(editor):
|
|
137
|
+
if editor is None:
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
for method_name in ("toPlainText", "get_text"):
|
|
141
|
+
method = getattr(editor, method_name, None)
|
|
142
|
+
if callable(method):
|
|
143
|
+
try:
|
|
144
|
+
value = method()
|
|
145
|
+
except TypeError:
|
|
146
|
+
continue
|
|
147
|
+
except Exception:
|
|
148
|
+
value = None
|
|
149
|
+
if isinstance(value, str):
|
|
150
|
+
return value
|
|
151
|
+
|
|
152
|
+
value = getattr(editor, "text", None)
|
|
153
|
+
if isinstance(value, str):
|
|
154
|
+
return value
|
|
155
|
+
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _get_cursor_line_number(editor):
|
|
160
|
+
if editor is None:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
cursor_getter = getattr(editor, "textCursor", None)
|
|
164
|
+
if callable(cursor_getter):
|
|
165
|
+
try:
|
|
166
|
+
cursor = cursor_getter()
|
|
167
|
+
return cursor.blockNumber() + 1
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
for method_name in ("get_cursor_line_number", "get_cursor_line"):
|
|
172
|
+
method = getattr(editor, method_name, None)
|
|
173
|
+
if callable(method):
|
|
174
|
+
try:
|
|
175
|
+
value = method()
|
|
176
|
+
except Exception:
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
if isinstance(value, tuple):
|
|
180
|
+
value = value[0] if value else None
|
|
181
|
+
|
|
182
|
+
if isinstance(value, int):
|
|
183
|
+
return value if value >= 1 else value + 1
|
|
184
|
+
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
@classmethod
|
|
188
|
+
def _get_current_cell_marker_line(cls, editor, line_number):
|
|
189
|
+
text = cls._get_editor_text(editor)
|
|
190
|
+
if not text or not line_number:
|
|
191
|
+
return "(unknown)"
|
|
192
|
+
|
|
193
|
+
lines = text.splitlines()
|
|
194
|
+
index = min(max(line_number, 1), len(lines)) - 1
|
|
195
|
+
for line in reversed(lines[: index + 1]):
|
|
196
|
+
if cls.CELL_MARKER_RE.match(line):
|
|
197
|
+
return line.strip() or "# %%"
|
|
198
|
+
|
|
199
|
+
return "(no cell)"
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def _get_current_location_parts(cls, stack, path):
|
|
203
|
+
editor = cls._get_current_editor(stack)
|
|
204
|
+
line_number = cls._get_cursor_line_number(editor)
|
|
205
|
+
cell = cls._get_current_cell_marker_line(editor, line_number)
|
|
206
|
+
return path, cell, line_number
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def _format_current_location(cls, stack, path, include_line):
|
|
210
|
+
full_path, cell, line_number = cls._get_current_location_parts(
|
|
211
|
+
stack,
|
|
212
|
+
path,
|
|
213
|
+
)
|
|
214
|
+
payload = {"path": full_path, "cell": cell}
|
|
215
|
+
if include_line:
|
|
216
|
+
payload["line"] = line_number
|
|
217
|
+
return json.dumps(payload, separators=(",", ": "))
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def _path_from_finfo(finfo):
|
|
221
|
+
if finfo is None:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
for attr in ("filename", "path"):
|
|
225
|
+
value = getattr(finfo, attr, None)
|
|
226
|
+
if isinstance(value, str) and value:
|
|
227
|
+
return value
|
|
228
|
+
|
|
229
|
+
getter = getattr(finfo, "get_filename", None)
|
|
230
|
+
if callable(getter):
|
|
231
|
+
try:
|
|
232
|
+
value = getter()
|
|
233
|
+
if isinstance(value, str) and value:
|
|
234
|
+
return value
|
|
235
|
+
except Exception:
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def _get_current_path(cls, stack):
|
|
242
|
+
if stack is None:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
for method_name in ("get_current_finfo", "get_current_filename"):
|
|
246
|
+
method = getattr(stack, method_name, None)
|
|
247
|
+
if callable(method):
|
|
248
|
+
try:
|
|
249
|
+
value = method()
|
|
250
|
+
except Exception:
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
if isinstance(value, str) and value:
|
|
254
|
+
return value
|
|
255
|
+
|
|
256
|
+
path = cls._path_from_finfo(value)
|
|
257
|
+
if path:
|
|
258
|
+
return path
|
|
259
|
+
|
|
260
|
+
current_finfo = getattr(stack, "data", None)
|
|
261
|
+
current_index = getattr(stack, "get_stack_index", None)
|
|
262
|
+
if isinstance(current_finfo, list) and callable(current_index):
|
|
263
|
+
try:
|
|
264
|
+
index = current_index()
|
|
265
|
+
if 0 <= index < len(current_finfo):
|
|
266
|
+
path = cls._path_from_finfo(current_finfo[index])
|
|
267
|
+
if path:
|
|
268
|
+
return path
|
|
269
|
+
except Exception:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
editor = None
|
|
273
|
+
getter = getattr(stack, "get_current_editor", None)
|
|
274
|
+
if callable(getter):
|
|
275
|
+
try:
|
|
276
|
+
editor = getter()
|
|
277
|
+
except Exception:
|
|
278
|
+
editor = None
|
|
279
|
+
|
|
280
|
+
if editor is not None:
|
|
281
|
+
for attr in ("filename", "path"):
|
|
282
|
+
value = getattr(editor, attr, None)
|
|
283
|
+
if isinstance(value, str) and value:
|
|
284
|
+
return value
|
|
285
|
+
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
def _get_ipython_console_plugin(self):
|
|
289
|
+
ipython_console = getattr(Plugins, "IPythonConsole", None)
|
|
290
|
+
if ipython_console is None:
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
return self.get_plugin(ipython_console)
|
|
295
|
+
except Exception:
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
@staticmethod
|
|
299
|
+
def _call_first(obj, method_names):
|
|
300
|
+
for method_name in method_names:
|
|
301
|
+
method = getattr(obj, method_name, None)
|
|
302
|
+
if callable(method):
|
|
303
|
+
try:
|
|
304
|
+
value = method()
|
|
305
|
+
except Exception:
|
|
306
|
+
continue
|
|
307
|
+
if value is not None:
|
|
308
|
+
return value
|
|
309
|
+
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
def _get_current_console_client(self, ipyconsole):
|
|
313
|
+
if ipyconsole is None:
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
return self._call_first(
|
|
317
|
+
ipyconsole,
|
|
318
|
+
(
|
|
319
|
+
"get_current_client",
|
|
320
|
+
"get_focus_client",
|
|
321
|
+
"get_current_widget",
|
|
322
|
+
),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def _get_current_shellwidget(self, ipyconsole, client):
|
|
326
|
+
shellwidget = self._call_first(
|
|
327
|
+
ipyconsole,
|
|
328
|
+
(
|
|
329
|
+
"get_current_shellwidget",
|
|
330
|
+
"get_focus_shellwidget",
|
|
331
|
+
),
|
|
332
|
+
)
|
|
333
|
+
if shellwidget is not None:
|
|
334
|
+
return shellwidget
|
|
335
|
+
|
|
336
|
+
if client is None:
|
|
337
|
+
return None
|
|
338
|
+
|
|
339
|
+
for attr in ("shellwidget", "_shellwidget"):
|
|
340
|
+
value = getattr(client, attr, None)
|
|
341
|
+
if value is not None:
|
|
342
|
+
return value
|
|
343
|
+
|
|
344
|
+
return self._call_first(client, ("get_shellwidget",))
|
|
345
|
+
|
|
346
|
+
@staticmethod
|
|
347
|
+
def _resolve_connection_file(ipyconsole, connection_file):
|
|
348
|
+
if not connection_file:
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
if os.path.isfile(connection_file):
|
|
352
|
+
return connection_file
|
|
353
|
+
|
|
354
|
+
widget_getter = getattr(ipyconsole, "get_widget", None)
|
|
355
|
+
if callable(widget_getter):
|
|
356
|
+
try:
|
|
357
|
+
widget = widget_getter()
|
|
358
|
+
except Exception:
|
|
359
|
+
widget = None
|
|
360
|
+
|
|
361
|
+
finder = getattr(widget, "find_connection_file", None)
|
|
362
|
+
if callable(finder):
|
|
363
|
+
try:
|
|
364
|
+
resolved = finder(connection_file)
|
|
365
|
+
except Exception:
|
|
366
|
+
resolved = None
|
|
367
|
+
|
|
368
|
+
if resolved and os.path.isfile(resolved):
|
|
369
|
+
return resolved
|
|
370
|
+
|
|
371
|
+
try:
|
|
372
|
+
resolved = find_connection_file(connection_file)
|
|
373
|
+
except Exception:
|
|
374
|
+
resolved = None
|
|
375
|
+
|
|
376
|
+
if resolved and os.path.isfile(resolved):
|
|
377
|
+
return resolved
|
|
378
|
+
|
|
379
|
+
return connection_file
|
|
380
|
+
|
|
381
|
+
@staticmethod
|
|
382
|
+
def _read_connection_info_from_file(connection_file):
|
|
383
|
+
if not connection_file or not os.path.isfile(connection_file):
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
with open(connection_file, "r", encoding="utf-8") as handle:
|
|
388
|
+
return json.load(handle)
|
|
389
|
+
except Exception:
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
@staticmethod
|
|
393
|
+
def _get_connection_file(shellwidget, client, kernel_client):
|
|
394
|
+
for obj in (shellwidget, client, kernel_client):
|
|
395
|
+
if obj is None:
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
getter = getattr(obj, "get_connection_file", None)
|
|
399
|
+
if callable(getter):
|
|
400
|
+
try:
|
|
401
|
+
value = getter()
|
|
402
|
+
except Exception:
|
|
403
|
+
value = None
|
|
404
|
+
if isinstance(value, str) and value:
|
|
405
|
+
return value
|
|
406
|
+
|
|
407
|
+
for attr in ("connection_file", "_connection_file"):
|
|
408
|
+
value = getattr(obj, attr, None)
|
|
409
|
+
if isinstance(value, str) and value:
|
|
410
|
+
return value
|
|
411
|
+
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
@staticmethod
|
|
415
|
+
def _get_kernel_client(shellwidget, client):
|
|
416
|
+
for obj in (shellwidget, client):
|
|
417
|
+
if obj is None:
|
|
418
|
+
continue
|
|
419
|
+
|
|
420
|
+
getter = getattr(obj, "get_kernel_client", None)
|
|
421
|
+
if callable(getter):
|
|
422
|
+
try:
|
|
423
|
+
value = getter()
|
|
424
|
+
except Exception:
|
|
425
|
+
value = None
|
|
426
|
+
if value is not None:
|
|
427
|
+
return value
|
|
428
|
+
|
|
429
|
+
for attr in ("kernel_client", "_kernel_client"):
|
|
430
|
+
value = getattr(obj, attr, None)
|
|
431
|
+
if value is not None:
|
|
432
|
+
return value
|
|
433
|
+
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def _get_connection_info(kernel_client, connection_file):
|
|
438
|
+
if kernel_client is not None:
|
|
439
|
+
getter = getattr(kernel_client, "get_connection_info", None)
|
|
440
|
+
if callable(getter):
|
|
441
|
+
for kwargs in ({"session": False}, {}):
|
|
442
|
+
try:
|
|
443
|
+
value = getter(**kwargs)
|
|
444
|
+
except TypeError:
|
|
445
|
+
continue
|
|
446
|
+
except Exception:
|
|
447
|
+
value = None
|
|
448
|
+
|
|
449
|
+
if isinstance(value, bytes):
|
|
450
|
+
try:
|
|
451
|
+
value = value.decode("utf-8")
|
|
452
|
+
except Exception:
|
|
453
|
+
value = None
|
|
454
|
+
|
|
455
|
+
if isinstance(value, str) and value.strip():
|
|
456
|
+
try:
|
|
457
|
+
return json.loads(value)
|
|
458
|
+
except Exception:
|
|
459
|
+
pass
|
|
460
|
+
|
|
461
|
+
if isinstance(value, dict) and value:
|
|
462
|
+
return value
|
|
463
|
+
|
|
464
|
+
return CopyCurrentPlugin._read_connection_info_from_file(connection_file)
|
|
465
|
+
|
|
466
|
+
@staticmethod
|
|
467
|
+
def _make_json_safe(value):
|
|
468
|
+
if isinstance(value, bytes):
|
|
469
|
+
try:
|
|
470
|
+
return value.decode("utf-8")
|
|
471
|
+
except Exception:
|
|
472
|
+
return value.decode("utf-8", errors="replace")
|
|
473
|
+
|
|
474
|
+
if isinstance(value, dict):
|
|
475
|
+
return {
|
|
476
|
+
CopyCurrentPlugin._make_json_safe(key): CopyCurrentPlugin._make_json_safe(item)
|
|
477
|
+
for key, item in value.items()
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if isinstance(value, (list, tuple)):
|
|
481
|
+
return [CopyCurrentPlugin._make_json_safe(item) for item in value]
|
|
482
|
+
|
|
483
|
+
return value
|
|
484
|
+
|
|
485
|
+
@staticmethod
|
|
486
|
+
def _format_connection_info(connection_info, connection_file):
|
|
487
|
+
safe_info = CopyCurrentPlugin._make_json_safe(connection_info)
|
|
488
|
+
body = json.dumps(safe_info, indent=2, sort_keys=True)
|
|
489
|
+
lines = [body]
|
|
490
|
+
if connection_file:
|
|
491
|
+
lines.extend(
|
|
492
|
+
(
|
|
493
|
+
"",
|
|
494
|
+
f"Connection file: {connection_file}",
|
|
495
|
+
"Connect with:",
|
|
496
|
+
f" jupyter console --existing {connection_file}",
|
|
497
|
+
)
|
|
498
|
+
)
|
|
499
|
+
return "\n".join(lines)
|
|
500
|
+
|
|
501
|
+
def _set_clipboard_text(self, title, text, status_message):
|
|
502
|
+
clipboard = QGuiApplication.clipboard()
|
|
503
|
+
if clipboard is None:
|
|
504
|
+
QMessageBox.warning(
|
|
505
|
+
self.main,
|
|
506
|
+
title,
|
|
507
|
+
"Could not access the clipboard.",
|
|
508
|
+
)
|
|
509
|
+
return False
|
|
510
|
+
|
|
511
|
+
clipboard.setText(text)
|
|
512
|
+
self.show_status_message(status_message, 3000)
|
|
513
|
+
return True
|
|
514
|
+
|
|
515
|
+
def copy_current_file_path(self):
|
|
516
|
+
stack = self._get_editorstack()
|
|
517
|
+
path = self._get_current_path(stack)
|
|
518
|
+
|
|
519
|
+
if not path:
|
|
520
|
+
QMessageBox.information(
|
|
521
|
+
self.main,
|
|
522
|
+
"Copy Current File Path",
|
|
523
|
+
"The current editor tab does not have a saved file path yet.",
|
|
524
|
+
)
|
|
525
|
+
return
|
|
526
|
+
|
|
527
|
+
normalized = os.path.abspath(path)
|
|
528
|
+
last_path, last_time, repeat_count = getattr(
|
|
529
|
+
self,
|
|
530
|
+
"_file_path_copy_state",
|
|
531
|
+
(None, 0.0, 0),
|
|
532
|
+
)
|
|
533
|
+
now = time.monotonic()
|
|
534
|
+
|
|
535
|
+
if (
|
|
536
|
+
last_path == normalized
|
|
537
|
+
and now - last_time <= self.REPEAT_PRESS_SECONDS
|
|
538
|
+
):
|
|
539
|
+
repeat_count += 1
|
|
540
|
+
else:
|
|
541
|
+
repeat_count = 1
|
|
542
|
+
|
|
543
|
+
self._file_path_copy_state = (normalized, now, repeat_count)
|
|
544
|
+
|
|
545
|
+
if repeat_count == 1:
|
|
546
|
+
self._set_clipboard_text(
|
|
547
|
+
"Copy Current File Path",
|
|
548
|
+
normalized,
|
|
549
|
+
f"Copied file path: {normalized}",
|
|
550
|
+
)
|
|
551
|
+
return
|
|
552
|
+
|
|
553
|
+
include_line = repeat_count >= 3
|
|
554
|
+
payload = self._format_current_location(stack, normalized, include_line)
|
|
555
|
+
self._set_clipboard_text(
|
|
556
|
+
"Copy Current Location",
|
|
557
|
+
payload,
|
|
558
|
+
f"Copied current location: {payload}",
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
def copy_current_kernel_connection_info(self):
|
|
562
|
+
ipyconsole = self._get_ipython_console_plugin()
|
|
563
|
+
client = self._get_current_console_client(ipyconsole)
|
|
564
|
+
shellwidget = self._get_current_shellwidget(ipyconsole, client)
|
|
565
|
+
kernel_client = self._get_kernel_client(shellwidget, client)
|
|
566
|
+
connection_file = self._resolve_connection_file(
|
|
567
|
+
ipyconsole,
|
|
568
|
+
self._get_connection_file(
|
|
569
|
+
shellwidget,
|
|
570
|
+
client,
|
|
571
|
+
kernel_client,
|
|
572
|
+
),
|
|
573
|
+
)
|
|
574
|
+
connection_info = self._get_connection_info(
|
|
575
|
+
kernel_client,
|
|
576
|
+
connection_file,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
if not connection_info:
|
|
580
|
+
QMessageBox.information(
|
|
581
|
+
self.main,
|
|
582
|
+
"Copy Kernel Connection Info",
|
|
583
|
+
"Could not find kernel connection info for the current IPython console.",
|
|
584
|
+
)
|
|
585
|
+
return
|
|
586
|
+
|
|
587
|
+
payload = self._format_connection_info(connection_info, connection_file)
|
|
588
|
+
self._set_clipboard_text(
|
|
589
|
+
"Copy Kernel Connection Info",
|
|
590
|
+
payload,
|
|
591
|
+
"Copied current kernel connection info.",
|
|
592
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spyder-copy-current
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Spyder plugin to copy the current file path and kernel connection info
|
|
5
|
+
Home-page: https://github.com/hruskamiro/spyder-copy-current
|
|
6
|
+
Author: Miroslav Hruska
|
|
7
|
+
Author-email: hruska.miro@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Source, https://github.com/hruskamiro/spyder-copy-current
|
|
10
|
+
Project-URL: Issues, https://github.com/hruskamiro/spyder-copy-current/issues
|
|
11
|
+
Keywords: spyder,plugin,ipython,jupyter,clipboard,kernel
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: IPython
|
|
14
|
+
Classifier: Framework :: Jupyter
|
|
15
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Topic :: Text Editors :: Integrated Development Environments (IDE)
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: jupyter_client<9,>=7.4.9
|
|
31
|
+
Requires-Dist: spyder<7,>=5.4
|
|
32
|
+
Requires-Dist: qtpy
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# spyder-copy-current
|
|
36
|
+
|
|
37
|
+
Small Spyder plugin that copies the current editor file path and the active IPython kernel connection info to the clipboard.
|
|
38
|
+
|
|
39
|
+
Default shortcuts:
|
|
40
|
+
|
|
41
|
+
- **`Ctrl+Alt+C`**: copy the current file path; press twice for `{"path": "<path>", "cell": "<cell>"}`; press three times for `{"path": "<path>", "cell": "<cell>", "line": <line>}`
|
|
42
|
+
- **`Ctrl+Alt+K`**: copy the active IPython kernel connection info
|
|
43
|
+
|
|
44
|
+
## Supported Versions
|
|
45
|
+
|
|
46
|
+
- Spyder `5.4.x` through `6.x`
|
|
47
|
+
- Python `3.8+`
|
|
48
|
+
|
|
49
|
+
## Behavior
|
|
50
|
+
|
|
51
|
+
- Copies the current file path to the system clipboard.
|
|
52
|
+
- Copies `{"path": "<path>", "cell": "<cell>"}` on the second quick press, and `{"path": "<path>", "cell": "<cell>", "line": <line>}` on the third quick press.
|
|
53
|
+
- Copies the current IPython kernel connection info to the system clipboard.
|
|
54
|
+
- Works from the active editor.
|
|
55
|
+
- Works from the active IPython console for kernel connection info.
|
|
56
|
+
- If the current tab has not been saved yet, it shows a message instead of copying anything.
|
|
57
|
+
- If the current IPython console does not expose kernel connection info, it shows a message instead of copying anything.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
Inside the same environment where Spyder runs:
|
|
62
|
+
|
|
63
|
+
Install the latest released version from PyPI:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install spyder-copy-current
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or install directly from GitHub:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install "git+https://github.com/hruskamiro/spyder-copy-current.git@main"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then restart Spyder.
|
|
76
|
+
|
|
77
|
+
## Compatibility Notes
|
|
78
|
+
|
|
79
|
+
- The plugin is intended for Spyder `5.4.x` through `6.x`.
|
|
80
|
+
- Existing user-defined Spyder shortcuts are preserved.
|
|
81
|
+
- The default shortcuts are only assigned if the corresponding Spyder shortcut entry is still empty.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
- Press `Ctrl+Alt+C` in the editor to copy the current file path. Press it twice in quick succession to copy `{"path": "<path>", "cell": "<cell>"}`; press it three times to include the cursor line number.
|
|
86
|
+
- Press `Ctrl+Alt+K` in the IPython console to copy the current kernel connection info.
|
|
87
|
+
|
|
88
|
+
The kernel connection info is copied in a `%connect_info`-style format so it can be reused with Jupyter clients when available.
|
|
89
|
+
|
|
90
|
+
## Shortcut
|
|
91
|
+
|
|
92
|
+
The plugin registers a keyboard shortcut in Spyder's shortcuts preferences under the editor context.
|
|
93
|
+
If that shortcut is still empty, it assigns `Ctrl+Alt+C` by default. Repeating it quickly adds the nearest cell marker, then the cursor line number.
|
|
94
|
+
It also registers a keyboard shortcut in the IPython console context and assigns `Ctrl+Alt+K` by default if that shortcut is still empty.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README-pypi.md
|
|
4
|
+
README.md
|
|
5
|
+
pyproject.toml
|
|
6
|
+
setup.cfg
|
|
7
|
+
setup.py
|
|
8
|
+
assets/spyder_copy_current.png
|
|
9
|
+
spyder_copy_current/__init__.py
|
|
10
|
+
spyder_copy_current/plugin.py
|
|
11
|
+
spyder_copy_current.egg-info/PKG-INFO
|
|
12
|
+
spyder_copy_current.egg-info/SOURCES.txt
|
|
13
|
+
spyder_copy_current.egg-info/dependency_links.txt
|
|
14
|
+
spyder_copy_current.egg-info/entry_points.txt
|
|
15
|
+
spyder_copy_current.egg-info/requires.txt
|
|
16
|
+
spyder_copy_current.egg-info/top_level.txt
|
|
17
|
+
spyder_copy_current/spyder/__init__.py
|
|
18
|
+
spyder_copy_current/spyder/plugin.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spyder_copy_current
|