├── tests
├── __init__.py
└── test_jedi.py
├── .gitattributes
├── .github
├── issue_template.md
├── pull_request_template.md
├── workflows
│ ├── test.yml
│ └── release.yml
└── release-drafter.yml
├── .editorconfig
├── README.md
├── .pre-commit-config.yaml
├── LICENSE
├── pyproject.toml
├── .gitignore
└── xontrib
└── jedi.py
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.xsh text linguist-language=Python
2 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | Hi! Thank you for the awesome xontrib! I gave it a star!
2 |
3 | I've found that ...
4 |
5 | ## For community
6 | ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment**
7 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | Hi! Thank you for the awesome xontrib! I gave it a star!
2 |
3 | Please take a look at my PR that ...
4 |
5 | ## For community
6 | ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment**
7 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Testing
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | testing:
13 | uses: xonsh/actions/.github/workflows/test-poetry-xontrib.yml@main
14 | with:
15 | xontrib_name: jedi
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # this is the top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 | indent_style = space
12 | indent_size = 4
13 |
14 | [*.{yml,json}]
15 | indent_size = 2
16 |
17 | [*.{md,markdown}]
18 | # don't trim trailing spaces because markdown syntax allows that
19 | trim_trailing_whitespace = false
20 |
21 | [{makefile,Makefile,MAKEFILE}]
22 | indent_style = tab
23 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | # Release drafter configuration https://github.com/release-drafter/release-drafter#configuration
2 | # Emojis were chosen to match the https://gitmoji.carloscuesta.me/
3 |
4 | name-template: "v$NEXT_PATCH_VERSION"
5 | tag-template: "v$NEXT_PATCH_VERSION"
6 |
7 | categories:
8 | - title: ":rocket: Features"
9 | labels: [enhancement, feature]
10 | - title: ":wrench: Fixes & Refactoring"
11 | labels: [bug, refactoring, bugfix, fix]
12 | - title: ":package: Build System & CI/CD"
13 | labels: [build, ci, testing]
14 | - title: ":boom: Breaking Changes"
15 | labels: [breaking]
16 | - title: ":pencil: Documentation"
17 | labels: [documentation]
18 | - title: ":arrow_up: Dependencies updates"
19 | labels: [dependencies]
20 |
21 | template: |
22 | ## What’s Changed
23 | $CHANGES
24 | ## :busts_in_silhouette: List of contributors
25 | $CONTRIBUTORS
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Xonsh Python completions using jedi.
3 |
4 |
5 |
6 | If you like the idea click ⭐ on the repo and tweet.
7 |
8 |
9 |
10 | ## Installation
11 |
12 | To install use pip:
13 |
14 | ```bash
15 | xpip install xontrib-jedi
16 | # or: xpip install -U git+https://github.com/xonsh/xontrib-jedi
17 | ```
18 |
19 | ## Usage
20 |
21 | ```xsh
22 | xontrib load jedi
23 |
24 | import json
25 | json.
26 | ```
27 |
28 | ## Release
29 |
30 | - update the version in `pyproject.toml`
31 | - Create a new release with the same tag using Github releases
32 |
33 | ## Credits
34 |
35 | This package was created with [xontrib cookiecutter template](https://github.com/xonsh/xontrib-cookiecutter).
36 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | default_language_version:
2 | # force all unspecified python hooks to run python3
3 | python: python3.10
4 |
5 | repos:
6 | - repo: https://github.com/astral-sh/ruff-pre-commit
7 | # Ruff version.
8 | rev: 'v0.9.7'
9 | hooks:
10 | - id: ruff
11 | args: [xontrib, --fix, --exit-non-zero-on-fix]
12 | pass_filenames: false
13 | - id: ruff-format
14 | args: [.]
15 | pass_filenames: false
16 |
17 | - repo: https://github.com/pre-commit/mirrors-mypy
18 | rev: 'v1.15.0' # Use the sha / tag you want to point at
19 | hooks:
20 | - id: mypy
21 | pass_filenames: false
22 | args: ['xontrib', '--ignore-missing-imports']
23 | - repo: https://github.com/pre-commit/pre-commit-hooks
24 | rev: v5.0.0
25 | hooks:
26 | - id: trailing-whitespace
27 | exclude: |
28 | (?x)^(
29 | docs/_static/.+
30 | )$
31 | - id: check-case-conflict
32 | - id: check-merge-conflict
33 | - id: check-yaml
34 | - id: check-toml
35 | - id: check-added-large-files
36 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # release to PyPI on new tags using OIDC
2 |
3 | name: Release to PyPI
4 |
5 | on:
6 | push:
7 | tags:
8 | - "v*"
9 |
10 | jobs:
11 | release-build:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - uses: actions/setup-python@v4
18 | with:
19 | python-version: "3.x"
20 |
21 | - name: build release distributions
22 | run: |
23 | pip install build
24 | python -m build
25 |
26 | - name: upload dists
27 | uses: actions/upload-artifact@v4
28 | with:
29 | name: release-dists
30 | path: dist/
31 |
32 | pypi-publish:
33 | runs-on: ubuntu-latest
34 | needs:
35 | - release-build
36 | permissions:
37 | id-token: write
38 |
39 | steps:
40 | - name: Retrieve release distributions
41 | uses: actions/download-artifact@v4
42 | with:
43 | name: release-dists
44 | path: dist/
45 |
46 | - name: Publish release distributions to PyPI
47 | uses: pypa/gh-action-pypi-publish@release/v1
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022, Xonsh Dev
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.
22 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 |
2 | [tool.poetry]
3 | name = "xontrib-jedi"
4 | version = "0.1.1"
5 | description = "Improved Xonsh's Python completions using jedi"
6 | authors = ["Xonsh Dev "]
7 |
8 | license = "MIT"
9 | readme = "README.md"
10 |
11 | repository = "https://github.com/xonsh/xontrib-jedi"
12 | keywords = ['xontrib', 'xonsh']
13 | classifiers = [
14 | "Intended Audience :: Developers",
15 | "License :: OSI Approved :: MIT License",
16 | "Natural Language :: English",
17 | "Operating System :: OS Independent",
18 | "Topic :: System :: Shells",
19 | "Topic :: System :: System Shells",
20 | "Topic :: Terminals",
21 | "Programming Language :: Python",
22 | "Programming Language :: Python :: 3",
23 | "Programming Language :: Python :: 3.6",
24 | "Programming Language :: Python :: 3.7",
25 | "Programming Language :: Python :: 3.8",
26 | ]
27 |
28 |
29 | packages = [{ include = "xontrib" }]
30 |
31 | [tool.poetry.urls]
32 | Documentation = "https://github.com/xonsh/xontrib-jedi/blob/master/README.md"
33 | Code = "https://github.com/xonsh/xontrib-jedi"
34 | "Issue tracker" = "https://github.com/xonsh/xontrib-jedi/issues"
35 |
36 |
37 | [tool.poetry.dependencies]
38 | python = ">=3.10,<4.0"
39 | jedi = ">=0.19"
40 | xonsh = ">=0.17"
41 |
42 | [tool.poetry.dev-dependencies]
43 | pytest = ">6.0"
44 |
45 | [build-system]
46 | requires = ["poetry-core>=1.0.0"]
47 | build-backend = "poetry.core.masonry.api"
48 |
49 | [tool.ruff.lint]
50 | select = [
51 | "B", # https://beta.ruff.rs/docs/rules/#flake8-bugbear-b
52 | #"D", # https://beta.ruff.rs/docs/rules/#pydocstyle-d
53 | "E", # https://beta.ruff.rs/docs/rules/#pycodestyle-e-w
54 | "F", # https://beta.ruff.rs/docs/rules/#pyflakes-f
55 | "I", # https://beta.ruff.rs/docs/rules/#isort-i
56 | "T10", # https://beta.ruff.rs/docs/rules/#flake8-debugger-t10
57 | "TID", # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid
58 | "YTT", # https://beta.ruff.rs/docs/rules/#flake8-2020-ytt
59 | "W", # https://beta.ruff.rs/docs/rules/#pycodestyle-e-w
60 | "UP", # https://beta.ruff.rs/docs/rules/#pyupgrade-up
61 | ]
62 | ignore = [
63 | "E501", # line length
64 | ]
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 | .idea/
131 | .DS_Store
132 |
--------------------------------------------------------------------------------
/xontrib/jedi.py:
--------------------------------------------------------------------------------
1 | """Use Jedi as xonsh's python completer."""
2 |
3 | # mypy: disable-error-code="attr-defined,name-defined"
4 |
5 | import os
6 |
7 | from xonsh.built_ins import XSH
8 | from xonsh.completers import completer
9 | from xonsh.completers.tools import (
10 | RichCompletion,
11 | contextual_completer,
12 | get_filter_function,
13 | )
14 | from xonsh.parsers.completion_context import CompletionContext
15 |
16 | __all__ = ()
17 |
18 | import jedi
19 |
20 | XONSH_SPECIAL_TOKENS = {
21 | "?",
22 | "??",
23 | "$(",
24 | "${",
25 | "$[",
26 | "![",
27 | "!(",
28 | "@(",
29 | "@$(",
30 | "@",
31 | }
32 |
33 |
34 | XONSH_SPECIAL_TOKENS_FIRST = {tok[0] for tok in XONSH_SPECIAL_TOKENS}
35 |
36 |
37 | @contextual_completer
38 | def complete_jedi(context: CompletionContext):
39 | """Completes python code using Jedi and xonsh operators"""
40 | if context.python is None:
41 | return None
42 |
43 | ctx = context.python.ctx or {}
44 |
45 | # if the first word is a known command (and we're not completing it), don't complete.
46 | # taken from xonsh/completers/python.py
47 | if context.command and context.command.arg_index != 0:
48 | first = context.command.args[0].value
49 | if first in XSH.commands_cache and first not in ctx: # type: ignore
50 | return None
51 |
52 | # if we're completing a possible command and the prefix contains a valid path, don't complete.
53 | if context.command:
54 | path_dir = os.path.dirname(context.command.prefix)
55 | if path_dir and os.path.isdir(os.path.expanduser(path_dir)):
56 | return None
57 |
58 | filter_func = get_filter_function()
59 | jedi.settings.case_insensitive_completion = not XSH.env.get(
60 | "CASE_SENSITIVE_COMPLETIONS"
61 | )
62 |
63 | source = context.python.multiline_code
64 | index = context.python.cursor_index
65 | row = source.count("\n", 0, index) + 1
66 | column = (
67 | index - source.rfind("\n", 0, index) - 1
68 | ) # will be `index - (-1) - 1` if there's no newline
69 |
70 | extra_ctx = {"__xonsh__": XSH}
71 | try:
72 | extra_ctx["_"] = _
73 | except NameError:
74 | pass
75 |
76 | script = jedi.Interpreter(source, [ctx, extra_ctx])
77 |
78 | script_comp = set()
79 | try:
80 | script_comp = script.complete(row, column)
81 | except Exception:
82 | pass
83 |
84 | res = {create_completion(comp) for comp in script_comp if should_complete(comp)}
85 |
86 | if index > 0:
87 | last_char = source[index - 1]
88 | res.update(
89 | RichCompletion(t, prefix_len=1)
90 | for t in XONSH_SPECIAL_TOKENS
91 | if filter_func(t, last_char)
92 | )
93 | else:
94 | res.update(RichCompletion(t, prefix_len=0) for t in XONSH_SPECIAL_TOKENS)
95 |
96 | return res
97 |
98 |
99 | def should_complete(comp: jedi.api.classes.Completion):
100 | """Make sure _* names are completed only when
101 | the user writes the first underscore
102 | """
103 | name = comp.name
104 | if not name.startswith("_"):
105 | return True
106 | completion = comp.complete
107 | # only if we're not completing the first underscore:
108 | return completion and len(completion) <= len(name) - 1
109 |
110 |
111 | def create_completion(comp: jedi.api.classes.Completion):
112 | """Create a RichCompletion from a Jedi Completion object"""
113 | comp_type = None
114 | description = None
115 |
116 | if comp.type != "instance":
117 | sigs = comp.get_signatures()
118 | if sigs:
119 | comp_type = comp.type
120 | description = sigs[0].to_string()
121 | if comp_type is None:
122 | # jedi doesn't know exactly what this is
123 | inf = comp.infer()
124 | if inf:
125 | comp_type = inf[0].type
126 | description = inf[0].description
127 |
128 | display = comp.name + ("()" if comp_type == "function" else "")
129 | description = description or comp.type
130 |
131 | prefix_len = len(comp.name) - len(comp.complete)
132 |
133 | return RichCompletion(
134 | comp.name,
135 | display=display,
136 | description=description,
137 | prefix_len=prefix_len,
138 | )
139 |
140 |
141 | # Jedi ignores leading '@(' and friends
142 | completer.add_one_completer("jedi_python", complete_jedi, " int",
98 | ("instance", "instance int"),
99 | ),
100 | RichCompletion(
101 | "xx", display="xx", description="instance int", prefix_len=1
102 | ),
103 | ),
104 | (
105 | # from jedi when code is 'xx=3\nx'
106 | ("statement", "xx", "x", None, ("instance", "instance int")),
107 | RichCompletion(
108 | "xx", display="xx", description="instance int", prefix_len=1
109 | ),
110 | ),
111 | (
112 | # from jedi when code is 'x.' and x=3
113 | (
114 | "function",
115 | "from_bytes",
116 | "from_bytes",
117 | "from_bytes(bytes, byteorder, *, signed=False)",
118 | ("function", "def __get__"),
119 | ),
120 | RichCompletion(
121 | "from_bytes",
122 | display="from_bytes()",
123 | description="from_bytes(bytes, byteorder, *, signed=False)",
124 | ),
125 | ),
126 | (
127 | # from jedi when code is 'x=3\nx.'
128 | ("function", "imag", "imag", None, ("instance", "instance int")),
129 | RichCompletion("imag", display="imag", description="instance int"),
130 | ),
131 | (
132 | # from '(3).from_bytes(byt'
133 | ("param", "bytes=", "es=", None, ("instance", "instance Sequence")),
134 | RichCompletion(
135 | "bytes=",
136 | display="bytes=",
137 | description="instance Sequence",
138 | prefix_len=3,
139 | ),
140 | ),
141 | (
142 | # from 'x.from_bytes(byt' when x=3
143 | ("param", "bytes=", "es=", None, None),
144 | RichCompletion(
145 | "bytes=", display="bytes=", description="param", prefix_len=3
146 | ),
147 | ),
148 | (
149 | # from 'import colle'
150 | ("module", "collections", "ctions", None, ("module", "module collections")),
151 | RichCompletion(
152 | "collections",
153 | display="collections",
154 | description="module collections",
155 | prefix_len=5,
156 | ),
157 | ),
158 | (
159 | # from 'NameErr'
160 | (
161 | "class",
162 | "NameError",
163 | "or",
164 | "NameError(*args: object)",
165 | ("class", "class NameError"),
166 | ),
167 | RichCompletion(
168 | "NameError",
169 | display="NameError",
170 | description="NameError(*args: object)",
171 | prefix_len=7,
172 | ),
173 | ),
174 | (
175 | # from 'a["' when a={'name':None}
176 | ("string", '"name"', 'name"', None, None),
177 | RichCompletion('"name"', display='"name"', description="string"),
178 | ),
179 | (
180 | # from 'open("/etc/pass'
181 | ("path", 'passwd"', 'wd"', None, None),
182 | RichCompletion(
183 | 'passwd"', display='passwd"', description="path", prefix_len=4
184 | ),
185 | ),
186 | (
187 | # from 'cla'
188 | ("keyword", "class", "ss", None, None),
189 | RichCompletion(
190 | "class", display="class", description="keyword", prefix_len=3
191 | ),
192 | ),
193 | ],
194 | )
195 | def test_rich_completions(jedi_xontrib, jedi_mock, completion, rich_completion):
196 | comp_type, comp_name, comp_complete, sig, inf = completion
197 | comp_mock = MagicMock()
198 | comp_mock.type = comp_type
199 | comp_mock.name = comp_name
200 | comp_mock.complete = comp_complete
201 | if sig:
202 | sig_mock = MagicMock()
203 | sig_mock.to_string.return_value = sig
204 | comp_mock.get_signatures.return_value = [sig_mock]
205 | else:
206 | comp_mock.get_signatures.return_value = []
207 | if inf:
208 | inf_type, inf_desc = inf
209 | inf_mock = MagicMock()
210 | inf_mock.type = inf_type
211 | inf_mock.description = inf_desc
212 | comp_mock.infer.return_value = [inf_mock]
213 | else:
214 | comp_mock.infer.return_value = []
215 |
216 | jedi_xontrib.XONSH_SPECIAL_TOKENS = []
217 | jedi_mock.Interpreter().complete.return_value = [comp_mock]
218 | completions = jedi_xontrib.complete_jedi(
219 | CompletionContext(python=PythonContext("", 0))
220 | )
221 | assert len(completions) == 1
222 | (ret_completion,) = completions
223 | assert isinstance(ret_completion, RichCompletion)
224 | assert ret_completion == rich_completion
225 | assert ret_completion.display == rich_completion.display
226 | assert ret_completion.description == rich_completion.description
227 |
228 |
229 | def test_special_tokens(jedi_xontrib):
230 | assert jedi_xontrib.complete_jedi(
231 | CompletionContext(python=PythonContext("", 0))
232 | ).issuperset(jedi_xontrib.XONSH_SPECIAL_TOKENS)
233 | assert jedi_xontrib.complete_jedi(
234 | CompletionContext(python=PythonContext("@", 1))
235 | ) == {"@", "@(", "@$("}
236 | assert jedi_xontrib.complete_jedi(
237 | CompletionContext(python=PythonContext("$", 1))
238 | ) == {"$[", "${", "$("}
239 |
240 |
241 | @skip_if_on_windows
242 | def test_no_command_path_completion(jedi_xontrib, completion_context_parse):
243 | assert jedi_xontrib.complete_jedi(completion_context_parse("./", 2)) is None
244 | assert jedi_xontrib.complete_jedi(completion_context_parse("~/", 2)) is None
245 | assert jedi_xontrib.complete_jedi(completion_context_parse("./e", 3)) is None
246 | assert jedi_xontrib.complete_jedi(completion_context_parse("/usr/bin/", 9)) is None
247 | assert (
248 | jedi_xontrib.complete_jedi(completion_context_parse("/usr/bin/e", 10)) is None
249 | )
250 |
--------------------------------------------------------------------------------