rp 0.1.1361__tar.gz → 0.1.1362__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.
- {rp-0.1.1361 → rp-0.1.1362}/PKG-INFO +1 -1
- rp-0.1.1362/rp/rp_ptpython/bash_extract.py +563 -0
- rp-0.1.1362/rp/rp_ptpython/bash_inline.py +439 -0
- rp-0.1.1362/rp/rp_ptpython/bash_rename.py +386 -0
- rp-0.1.1362/rp/version.py +1 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp.egg-info/PKG-INFO +1 -1
- {rp-0.1.1361 → rp-0.1.1362}/rp.egg-info/SOURCES.txt +3 -0
- rp-0.1.1362/version.py +1 -0
- rp-0.1.1361/rp/version.py +0 -1
- rp-0.1.1361/version.py +0 -1
- {rp-0.1.1361 → rp-0.1.1362}/.gitignore +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.idea/QuickPython.iml +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.idea/QuickPython.time +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.idea/encodings.xml +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.idea/inspectionProfiles/Project_Default.xml +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.idea/misc.xml +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.idea/modules.xml +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.idea/vcs.xml +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.idea/workspace.xml +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.publish.sh.un~ +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.setup.py.un~ +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/.update_module_paths.py.un~ +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/LICENSE.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/README +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/Scrap +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/aoisdasd +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/increment_version.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/oijio +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/publish.sh +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/pyqtconsoletest.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/__main__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/documentation.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/Pyperclip.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/debug_comment.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/draw_code_minimap.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/graveyard.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/_compatibility.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/cache.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/file_io.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/grammar.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/normalizer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/parser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/pgen2/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/pgen2/generator.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/pgen2/grammar_parser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/diff.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/errors.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar26.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar27.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar310.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar311.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar312.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar33.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar34.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar35.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar36.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar37.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar38.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/grammar39.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/parser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/pep8.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/prefix.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/token.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/tokenize.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/python/tree.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/tree.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/parso/utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/peepdis/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/peepdis/core.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/peepdis/datastructures.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/peepdis/legacy.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/peepdis/preferences.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/powerpoint_converter.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/profile_vim_plugins.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/bindings/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/bindings/flann_ctypes.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/exceptions.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/index.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/io/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/io/binary_dataset.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/io/dat_dataset.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/io/dataset.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/io/hdf5_dataset.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/io/npy_dataset.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/util/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyflann/util/weave_tools.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/__version__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/app.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/cli.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/cli_utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/config/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/config/config.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/config/config_error.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/config/config_parser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/config/config_transformer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/config/validators/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/config/validators/options.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/logging.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/autocomplete/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/autocomplete/autocomplete_listener.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/common/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/common/jsonpath/JSONPathLexer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/common/jsonpath/JSONPathListener.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/common/jsonpath/JSONPathParser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/common/jsonpath/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/io/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/io/datasource.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/model/model.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/service/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/service/client.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/service/dispatcher.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/common/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/common/popup.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/common/selectable_text.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/autocomplete_popup/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/autocomplete_popup/autocomplete_popup.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/autocomplete_popup/autocomplete_popup_keymapper.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/help_bar/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/help_bar/help_bar.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/help_page/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/help_page/detailed_help_page.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/json_browser/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/json_browser/json_browser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/json_browser/json_browser_keymapper.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/query_bar/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/query_bar/query_bar.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/components/query_bar/query_bar_keymapper.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/array/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/array/array_end_node.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/array/array_end_widget.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/array/array_node.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/array/array_start_widget.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/array/array_unexpanded_widget.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/json_composite_end_node.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/json_composite_node.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/json_listbox.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/json_listwalker.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/json_simple_node.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/json_widget.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/node_factory.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/object/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/object/object_end_node.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/object/object_end_widget.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/object/object_node.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/object/object_start_widget.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/object/object_unexpanded_widget.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/primitive/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/primitive/base.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/primitive/boolean.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/primitive/generic.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/primitive/integer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/primitive/null.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/primitive/numeric.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/primitive/string.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/json_lib/selector/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/keymapper/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/keymapper/abstract_component_keymapper.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/keymapper/keymapper.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/keymapper/keymapper_config.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/keymapper/keymapper_config_parser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/theme/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/theme/theme.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/theme/theme_config.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/theme/theme_config_parser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/view_config.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/view_frame.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pyfx/view/view_manager.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pynput_recorder.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pypi_inspection.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/pytorch_module_explorer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/refactor/string_format/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/refactor/string_format/format_to_fstring_converter.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/refactor/string_format/fstring_converter.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/refactor/string_format/test_format_to_fstring_converter.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/refactor/string_format/test_fstring_converter.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/rp_black_macchiato.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/stamp_tensor.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/stdin_python_highlighter.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/__main__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/graphics/graphics/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/graphics/graphics/canvas.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/graphics/graphics/colors.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/graphics/graphics/console.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/graphics/graphics/funcs.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/graphics/graphics/nbinput.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/graphics/graphics/shapes.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/graphics/graphics/sprite.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/letters.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tetris/tetrisShapes.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/__main__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/argparser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/meta.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/methods/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/methods/ansi.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/methods/ansi_methods.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/methods/ascii.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/methods/method.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/methods/sixel.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/timg/renderer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/torch_hooks.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/torch_tools.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tracetraptest.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/tweenline.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/libs/webeval_dataset.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/list_of_modules.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/blm.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/chainsaw.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/death_of_the_mind.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/flappy.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/mario.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/matrix.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/mpi_untitled.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/py2048.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/pychess.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/rp_mpi.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/ryan_zshrc.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/shotgun.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/skia_trail_interactive_paint_demo.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/snake.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/sudoku.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/tetris.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/misc/trans.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/application.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/auto_suggest.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/buffer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/buffer_mapping.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/cache.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/clipboard/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/clipboard/base.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/clipboard/in_memory.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/clipboard/pyperclip.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/completion.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/completers/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/completers/base.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/completers/filesystem.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/completers/system.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/regular_languages/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/regular_languages/compiler.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/regular_languages/completion.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/regular_languages/lexer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/regular_languages/regex_parser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/regular_languages/validation.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/telnet/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/telnet/application.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/telnet/log.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/telnet/protocol.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/telnet/server.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/validators/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/contrib/validators/base.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/document.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/enums.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/asyncio_base.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/asyncio_posix.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/asyncio_win32.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/base.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/callbacks.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/inputhook.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/posix.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/posix_utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/select.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/eventloop/win32.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/filters/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/filters/base.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/filters/cli.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/filters/types.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/filters/utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/history.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/input.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/interface.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/bindings/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/bindings/basic.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/bindings/completion.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/bindings/emacs.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/bindings/named_commands.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/bindings/scroll.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/bindings/vi.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/defaults.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/digraphs.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/input_processor.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/manager.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/registry.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/key_binding/vi_state.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/keys.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/containers.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/controls.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/dimension.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/lexers.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/margins.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/menus.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/mouse_handlers.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/processors.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/prompt.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/screen.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/toolbars.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/untitled.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/layout/utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/mouse_events.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/output.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/reactive.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/renderer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/search_state.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/selection.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/shortcuts.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/styles/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/styles/base.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/styles/defaults.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/styles/from_dict.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/styles/from_pygments.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/styles/utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/terminal/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/terminal/conemu_output.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/terminal/vt100_input.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/terminal/vt100_output.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/terminal/win32_input.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/terminal/win32_output.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/token.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/validation.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/prompt_toolkit/win32_types.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/r.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/__main__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/ast_selection.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/async_analyzer_base.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/async_jedi_highlighter.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/backslash_commands.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/bash_completer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/bash_selection.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/completer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/completer_old.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/completion_ranker.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/completion_schema.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/completion_types.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/eventloop.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/filters.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/history_browser.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/ipython.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/jedi_highlighter.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/jedi_lexer.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/jedi_utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/key_bindings.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/layout.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/patch_linecache.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/path_completer_utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/prompt_style.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/python_input.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/r_iterm_comm.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/refactor_extract.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/refactor_inline.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/refactor_move.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/refactor_move_bash.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/refactor_rename.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/repl.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/rp_ptpdb/__init__.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/rp_ptpdb/commands.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/rp_ptpdb/completers.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/rp_ptpdb/completion_hints.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/rp_ptpdb/grammar.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/rp_ptpdb/key_bindings.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/rp_ptpdb/layout.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/rp_ptpdb/style.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/rp_ptpdb/toolbars.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/selection_utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/style.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/testable_keybindings.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/treesitter_selection.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/treesitter_semantic.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/unreachable_detector.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/utils.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/rp_ptpython/validator.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/ryan_vimrc.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp/web_evaluator.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp.egg-info/dependency_links.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp.egg-info/entry_points.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp.egg-info/requires.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/rp.egg-info/top_level.txt +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/setup.cfg +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/setup.py +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/untitled.c +0 -0
- {rp-0.1.1361 → rp-0.1.1362}/update_module_paths.py +0 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Extract Variable Refactoring Module for Bash
|
|
3
|
+
|
|
4
|
+
Extracts selected expressions into a variable assignment.
|
|
5
|
+
Handles bash-specific patterns like command substitution, arithmetic, etc.
|
|
6
|
+
"""
|
|
7
|
+
from typing import Tuple, Optional, List
|
|
8
|
+
import tree_sitter_bash as tsbash
|
|
9
|
+
from tree_sitter import Language, Parser, Tree, Node
|
|
10
|
+
|
|
11
|
+
_PARSER: Optional[Parser] = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_parser() -> Parser:
|
|
15
|
+
global _PARSER
|
|
16
|
+
if _PARSER is None:
|
|
17
|
+
_PARSER = Parser(Language(tsbash.language()))
|
|
18
|
+
return _PARSER
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse(code: str) -> Tree:
|
|
22
|
+
return _get_parser().parse(bytes(code, "utf8"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Node types that can be extracted
|
|
26
|
+
EXTRACTABLE_TYPES = {
|
|
27
|
+
'word', 'string', 'raw_string', 'concatenation',
|
|
28
|
+
'command_substitution', 'process_substitution',
|
|
29
|
+
'arithmetic_expansion', 'expansion', 'simple_expansion',
|
|
30
|
+
'number', 'array',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def find_smallest_containing(tree: Tree, start: int, end: int) -> Optional[Node]:
|
|
35
|
+
"""Find smallest node containing the byte range."""
|
|
36
|
+
best = None
|
|
37
|
+
def visit(node: Node):
|
|
38
|
+
nonlocal best
|
|
39
|
+
if node.start_byte <= start and end <= node.end_byte:
|
|
40
|
+
if best is None or (node.end_byte - node.start_byte) < (best.end_byte - best.start_byte):
|
|
41
|
+
best = node
|
|
42
|
+
for child in node.children:
|
|
43
|
+
visit(child)
|
|
44
|
+
visit(tree.root_node)
|
|
45
|
+
return best
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def find_smallest_extractable(tree: Tree, start: int, end: int) -> Optional[Node]:
|
|
49
|
+
"""Find smallest extractable expression containing the byte range."""
|
|
50
|
+
best = None
|
|
51
|
+
def visit(node: Node):
|
|
52
|
+
nonlocal best
|
|
53
|
+
if node.start_byte <= start and end <= node.end_byte:
|
|
54
|
+
if node.type in EXTRACTABLE_TYPES:
|
|
55
|
+
if best is None or (node.end_byte - node.start_byte) < (best.end_byte - best.start_byte):
|
|
56
|
+
best = node
|
|
57
|
+
for child in node.children:
|
|
58
|
+
visit(child)
|
|
59
|
+
visit(tree.root_node)
|
|
60
|
+
return best
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def find_containing_command(node: Node) -> Optional[Node]:
|
|
64
|
+
"""Find the containing command or statement."""
|
|
65
|
+
current = node
|
|
66
|
+
while current:
|
|
67
|
+
if current.type in ('command', 'pipeline', 'list', 'compound_statement',
|
|
68
|
+
'if_statement', 'while_statement', 'for_statement',
|
|
69
|
+
'case_statement', 'function_definition'):
|
|
70
|
+
return current
|
|
71
|
+
current = current.parent
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_line_start(code: str, byte_offset: int) -> int:
|
|
76
|
+
"""Get the start position of the line containing byte_offset."""
|
|
77
|
+
return code.rfind('\n', 0, byte_offset) + 1
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_line_indent(code: str, byte_offset: int) -> str:
|
|
81
|
+
"""Get indentation string for the line containing byte_offset."""
|
|
82
|
+
line_start = get_line_start(code, byte_offset)
|
|
83
|
+
indent_end = line_start
|
|
84
|
+
while indent_end < len(code) and code[indent_end] in ' \t':
|
|
85
|
+
indent_end += 1
|
|
86
|
+
return code[line_start:indent_end]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def find_duplicate_expressions(tree: Tree, code: str, expr: str, exclude_start: int) -> List[Tuple[int, int]]:
|
|
90
|
+
"""Find other occurrences of the same expression."""
|
|
91
|
+
# Simple text-based search for identical strings
|
|
92
|
+
duplicates = []
|
|
93
|
+
pos = 0
|
|
94
|
+
while True:
|
|
95
|
+
idx = code.find(expr, pos)
|
|
96
|
+
if idx == -1:
|
|
97
|
+
break
|
|
98
|
+
if idx != exclude_start:
|
|
99
|
+
duplicates.append((idx, idx + len(expr)))
|
|
100
|
+
pos = idx + 1
|
|
101
|
+
return duplicates
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def is_valid_bash_identifier(name: str) -> Tuple[bool, Optional[str]]:
|
|
105
|
+
"""Check if name is a valid bash variable identifier."""
|
|
106
|
+
if not name:
|
|
107
|
+
return False, "Name cannot be empty"
|
|
108
|
+
if name[0].isdigit():
|
|
109
|
+
return False, "Variable name cannot start with a digit"
|
|
110
|
+
for c in name:
|
|
111
|
+
if not (c.isalnum() or c == '_'):
|
|
112
|
+
return False, f"Invalid character '{c}' in variable name"
|
|
113
|
+
return True, None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def check_extract(code: str, start: int, end: int) -> Tuple[bool, Optional[str], Optional[dict]]:
|
|
117
|
+
"""
|
|
118
|
+
Check if extract refactoring is possible for selection.
|
|
119
|
+
|
|
120
|
+
Returns: (can_extract, error_message, info_dict)
|
|
121
|
+
"""
|
|
122
|
+
if start >= end:
|
|
123
|
+
return False, "No selection", None
|
|
124
|
+
|
|
125
|
+
tree = parse(code)
|
|
126
|
+
|
|
127
|
+
# Find an extractable node
|
|
128
|
+
node = find_smallest_extractable(tree, start, end)
|
|
129
|
+
if not node:
|
|
130
|
+
# Even if no AST node, allow extracting raw text
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
selected = code[start:end].strip()
|
|
134
|
+
if not selected:
|
|
135
|
+
return False, "Selection is empty", None
|
|
136
|
+
|
|
137
|
+
# Find duplicates
|
|
138
|
+
duplicates = find_duplicate_expressions(tree, code, selected, start)
|
|
139
|
+
|
|
140
|
+
return True, None, {
|
|
141
|
+
'selected': selected,
|
|
142
|
+
'duplicate_count': len(duplicates),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def is_inside_quotes(code: str, start: int, end: int) -> Optional[str]:
|
|
147
|
+
"""
|
|
148
|
+
Check if selection is inside quotes.
|
|
149
|
+
Returns the quote character if inside quotes, None otherwise.
|
|
150
|
+
"""
|
|
151
|
+
# Look for quote before start
|
|
152
|
+
for i in range(start - 1, -1, -1):
|
|
153
|
+
if code[i] in '"\'':
|
|
154
|
+
# Found a quote, check if it's opening
|
|
155
|
+
# Count quotes from line start
|
|
156
|
+
line_start = code.rfind('\n', 0, i) + 1
|
|
157
|
+
quote_char = code[i]
|
|
158
|
+
count = 0
|
|
159
|
+
for j in range(line_start, i + 1):
|
|
160
|
+
if code[j] == quote_char and (j == 0 or code[j-1] != '\\'):
|
|
161
|
+
count += 1
|
|
162
|
+
if count % 2 == 1: # Odd count means we're inside
|
|
163
|
+
return quote_char
|
|
164
|
+
break
|
|
165
|
+
elif code[i] == '\n':
|
|
166
|
+
break
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def classify_selection(code: str, start: int, end: int) -> str:
|
|
171
|
+
"""
|
|
172
|
+
Classify what kind of bash expression is selected.
|
|
173
|
+
|
|
174
|
+
Returns one of:
|
|
175
|
+
'quoted_string' - "..." or '...'
|
|
176
|
+
'command_sub' - $(...) or `...`
|
|
177
|
+
'variable' - $VAR or ${VAR}
|
|
178
|
+
'multi_word' - multiple unquoted words (needs array)
|
|
179
|
+
'single_word' - single unquoted word
|
|
180
|
+
'command' - looks like a full command (starts at line beginning)
|
|
181
|
+
'quoted_content' - content inside quotes (needs special handling)
|
|
182
|
+
"""
|
|
183
|
+
selected = code[start:end].strip()
|
|
184
|
+
|
|
185
|
+
# Check if we're selecting content inside quotes
|
|
186
|
+
inside_quote = is_inside_quotes(code, start, end)
|
|
187
|
+
if inside_quote:
|
|
188
|
+
return 'quoted_content'
|
|
189
|
+
|
|
190
|
+
# Check if it's a quoted string
|
|
191
|
+
if (selected.startswith('"') and selected.endswith('"')) or \
|
|
192
|
+
(selected.startswith("'") and selected.endswith("'")):
|
|
193
|
+
return 'quoted_string'
|
|
194
|
+
|
|
195
|
+
# Check if it's a command substitution
|
|
196
|
+
if (selected.startswith('$(') and selected.endswith(')')) or \
|
|
197
|
+
(selected.startswith('`') and selected.endswith('`')):
|
|
198
|
+
return 'command_sub'
|
|
199
|
+
|
|
200
|
+
# Check if it's an arithmetic expansion
|
|
201
|
+
if selected.startswith('$((') and selected.endswith('))'):
|
|
202
|
+
return 'arithmetic'
|
|
203
|
+
|
|
204
|
+
# Check if it's a variable reference
|
|
205
|
+
if selected.startswith('$'):
|
|
206
|
+
return 'variable'
|
|
207
|
+
|
|
208
|
+
# Check if it's a process substitution <() or >()
|
|
209
|
+
if (selected.startswith('<(') or selected.startswith('>(')) and selected.endswith(')'):
|
|
210
|
+
return 'process_sub'
|
|
211
|
+
|
|
212
|
+
# Check if it's a brace expansion {a,b,c} or {1..10}
|
|
213
|
+
if selected.startswith('{') and selected.endswith('}') and (',' in selected or '..' in selected):
|
|
214
|
+
return 'brace_expansion'
|
|
215
|
+
|
|
216
|
+
# Check if it contains glob characters (should not be quoted)
|
|
217
|
+
if any(c in selected for c in '*?['):
|
|
218
|
+
return 'glob'
|
|
219
|
+
|
|
220
|
+
# Check if selection is at line start (likely a command)
|
|
221
|
+
line_start = get_line_start(code, start)
|
|
222
|
+
content_start = line_start
|
|
223
|
+
while content_start < len(code) and code[content_start] in ' \t':
|
|
224
|
+
content_start += 1
|
|
225
|
+
if start == content_start:
|
|
226
|
+
return 'command'
|
|
227
|
+
|
|
228
|
+
# Check for multiple words (spaces in unquoted context)
|
|
229
|
+
# Simple heuristic: if there's whitespace not inside quotes, it's multi-word
|
|
230
|
+
in_quote = None
|
|
231
|
+
has_space = False
|
|
232
|
+
for c in selected:
|
|
233
|
+
if c in '"\'':
|
|
234
|
+
if in_quote == c:
|
|
235
|
+
in_quote = None
|
|
236
|
+
elif in_quote is None:
|
|
237
|
+
in_quote = c
|
|
238
|
+
elif c in ' \t' and in_quote is None:
|
|
239
|
+
has_space = True
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
return 'multi_word' if has_space else 'single_word'
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def extract_variable(code: str, start: int, end: int, var_name: str, replace_all: bool = True) -> Optional[Tuple[str, int]]:
|
|
246
|
+
"""
|
|
247
|
+
Extract selected expression into a variable with correct bash semantics.
|
|
248
|
+
|
|
249
|
+
Handles:
|
|
250
|
+
- Quoted strings: VAR="value"
|
|
251
|
+
- Command substitution: VAR=$(cmd)
|
|
252
|
+
- Multiple words: VAR=(word1 word2); use "${VAR[@]}"
|
|
253
|
+
- Single words: VAR=word
|
|
254
|
+
- Commands: VAR=$(command); capture output
|
|
255
|
+
|
|
256
|
+
Returns: (new_code, new_cursor_pos) or None
|
|
257
|
+
"""
|
|
258
|
+
valid, error = is_valid_bash_identifier(var_name)
|
|
259
|
+
if not valid:
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
if start >= end:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
selected = code[start:end]
|
|
266
|
+
if not selected.strip():
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
tree = parse(code)
|
|
270
|
+
selection_type = classify_selection(code, start, end)
|
|
271
|
+
|
|
272
|
+
# Find where to insert the assignment
|
|
273
|
+
node = find_smallest_containing(tree, start, end)
|
|
274
|
+
if node:
|
|
275
|
+
containing = find_containing_command(node)
|
|
276
|
+
if containing:
|
|
277
|
+
insert_pos = get_line_start(code, containing.start_byte)
|
|
278
|
+
else:
|
|
279
|
+
insert_pos = get_line_start(code, start)
|
|
280
|
+
else:
|
|
281
|
+
insert_pos = get_line_start(code, start)
|
|
282
|
+
|
|
283
|
+
indent = get_line_indent(code, start)
|
|
284
|
+
|
|
285
|
+
# Determine assignment and replacement based on selection type
|
|
286
|
+
if selection_type == 'quoted_content':
|
|
287
|
+
# Content inside quotes - expand selection to include the surrounding quotes
|
|
288
|
+
# Find the opening quote
|
|
289
|
+
quote_start = start - 1
|
|
290
|
+
while quote_start >= 0 and code[quote_start] not in '"\'':
|
|
291
|
+
quote_start -= 1
|
|
292
|
+
quote_char = code[quote_start] if quote_start >= 0 else '"'
|
|
293
|
+
# Find the closing quote
|
|
294
|
+
quote_end = end
|
|
295
|
+
while quote_end < len(code) and code[quote_end] != quote_char:
|
|
296
|
+
quote_end += 1
|
|
297
|
+
if quote_end < len(code):
|
|
298
|
+
quote_end += 1 # Include the closing quote
|
|
299
|
+
# Use the full quoted string
|
|
300
|
+
selected = code[quote_start:quote_end]
|
|
301
|
+
start = quote_start
|
|
302
|
+
end = quote_end
|
|
303
|
+
assignment = f'{indent}{var_name}={selected}\n'
|
|
304
|
+
replacement = f'"${var_name}"'
|
|
305
|
+
elif selection_type == 'quoted_string':
|
|
306
|
+
# Already quoted, use as-is
|
|
307
|
+
assignment = f'{indent}{var_name}={selected}\n'
|
|
308
|
+
replacement = f'"${var_name}"'
|
|
309
|
+
elif selection_type == 'command_sub':
|
|
310
|
+
# Command substitution, use as-is
|
|
311
|
+
assignment = f'{indent}{var_name}={selected}\n'
|
|
312
|
+
replacement = f'"${var_name}"'
|
|
313
|
+
elif selection_type == 'variable':
|
|
314
|
+
# Variable reference - wrap in quotes for safety
|
|
315
|
+
assignment = f'{indent}{var_name}={selected}\n'
|
|
316
|
+
replacement = f'"${var_name}"'
|
|
317
|
+
elif selection_type == 'arithmetic':
|
|
318
|
+
# Arithmetic expansion - use as-is
|
|
319
|
+
assignment = f'{indent}{var_name}={selected}\n'
|
|
320
|
+
replacement = f'"${var_name}"'
|
|
321
|
+
elif selection_type == 'process_sub':
|
|
322
|
+
# Process substitution - can't really extract, just store as-is
|
|
323
|
+
assignment = f'{indent}{var_name}={selected}\n'
|
|
324
|
+
replacement = f'${var_name}' # No quotes - it's a file path
|
|
325
|
+
elif selection_type == 'brace_expansion':
|
|
326
|
+
# Brace expansion - MUST NOT be quoted, use array
|
|
327
|
+
assignment = f'{indent}{var_name}=({selected})\n'
|
|
328
|
+
replacement = f'"${{{var_name}[@]}}"'
|
|
329
|
+
elif selection_type == 'glob':
|
|
330
|
+
# Glob pattern - MUST NOT be quoted, use array to capture matches
|
|
331
|
+
assignment = f'{indent}{var_name}=({selected})\n'
|
|
332
|
+
replacement = f'"${{{var_name}[@]}}"'
|
|
333
|
+
elif selection_type == 'command':
|
|
334
|
+
# Full command - capture output with $()
|
|
335
|
+
# Don't replace the command itself, just prepend the assignment
|
|
336
|
+
assignment = f'{indent}{var_name}=$({selected})\n'
|
|
337
|
+
replacement = None # Signal to not do replacement
|
|
338
|
+
elif selection_type == 'multi_word':
|
|
339
|
+
# Multiple words - use array
|
|
340
|
+
assignment = f'{indent}{var_name}=({selected})\n'
|
|
341
|
+
replacement = f'"${{{var_name}[@]}}"'
|
|
342
|
+
else: # single_word
|
|
343
|
+
# Single word - quote it for safety
|
|
344
|
+
assignment = f'{indent}{var_name}="{selected}"\n'
|
|
345
|
+
replacement = f'"${var_name}"'
|
|
346
|
+
|
|
347
|
+
# For now, only replace the original selection (not duplicates for complex cases)
|
|
348
|
+
# Duplicate replacement for multi-word arrays would be complex
|
|
349
|
+
if selection_type == 'multi_word':
|
|
350
|
+
replace_all = False
|
|
351
|
+
|
|
352
|
+
# If replacement is None (e.g., command extraction), just prepend assignment
|
|
353
|
+
if replacement is None:
|
|
354
|
+
new_code = assignment + code
|
|
355
|
+
new_cursor = len(indent) + len(var_name)
|
|
356
|
+
return new_code, new_cursor
|
|
357
|
+
|
|
358
|
+
# Find duplicates if replacing all
|
|
359
|
+
replacements = [(start, end)]
|
|
360
|
+
if replace_all:
|
|
361
|
+
duplicates = find_duplicate_expressions(tree, code, selected, start)
|
|
362
|
+
replacements.extend(duplicates)
|
|
363
|
+
|
|
364
|
+
# Sort in reverse order for replacement
|
|
365
|
+
replacements.sort(reverse=True)
|
|
366
|
+
|
|
367
|
+
# Build new code
|
|
368
|
+
new_code = code
|
|
369
|
+
|
|
370
|
+
# Replace all occurrences with variable reference
|
|
371
|
+
for s, e in replacements:
|
|
372
|
+
new_code = new_code[:s] + replacement + new_code[e:]
|
|
373
|
+
|
|
374
|
+
# Calculate offset adjustment for insertion point
|
|
375
|
+
offset_adjust = 0
|
|
376
|
+
for s, e in sorted(replacements):
|
|
377
|
+
if s < insert_pos:
|
|
378
|
+
old_len = e - s
|
|
379
|
+
new_len = len(replacement)
|
|
380
|
+
offset_adjust += new_len - old_len
|
|
381
|
+
|
|
382
|
+
adjusted_insert = insert_pos + offset_adjust
|
|
383
|
+
|
|
384
|
+
# Insert assignment
|
|
385
|
+
new_code = new_code[:adjusted_insert] + assignment + new_code[adjusted_insert:]
|
|
386
|
+
|
|
387
|
+
# New cursor at end of variable name in assignment
|
|
388
|
+
new_cursor = adjusted_insert + len(indent) + len(var_name)
|
|
389
|
+
|
|
390
|
+
return new_code, new_cursor
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# =============================================================================
|
|
394
|
+
# UI STATE
|
|
395
|
+
# =============================================================================
|
|
396
|
+
|
|
397
|
+
class BashExtractState:
|
|
398
|
+
"""State for extract variable UI."""
|
|
399
|
+
def __init__(self):
|
|
400
|
+
self.active = False
|
|
401
|
+
self.name = ""
|
|
402
|
+
self.name_cursor = 0
|
|
403
|
+
self.start = 0
|
|
404
|
+
self.end = 0
|
|
405
|
+
self.error = None
|
|
406
|
+
self.duplicate_count = 0
|
|
407
|
+
self.replace_all = True
|
|
408
|
+
self.original_code = ""
|
|
409
|
+
self.shell_prefix = False # True if code starts with !
|
|
410
|
+
|
|
411
|
+
def start_extract(self, start: int, end: int, duplicate_count: int, original_code: str, shell_prefix: bool = False):
|
|
412
|
+
self.active = True
|
|
413
|
+
self.name = ""
|
|
414
|
+
self.name_cursor = 0
|
|
415
|
+
self.start = start
|
|
416
|
+
self.end = end
|
|
417
|
+
self.error = None
|
|
418
|
+
self.duplicate_count = duplicate_count
|
|
419
|
+
self.replace_all = duplicate_count > 0
|
|
420
|
+
self.original_code = original_code
|
|
421
|
+
self.shell_prefix = shell_prefix
|
|
422
|
+
|
|
423
|
+
def reset(self):
|
|
424
|
+
self.active = False
|
|
425
|
+
self.name = ""
|
|
426
|
+
self.name_cursor = 0
|
|
427
|
+
self.start = 0
|
|
428
|
+
self.end = 0
|
|
429
|
+
self.error = None
|
|
430
|
+
self.duplicate_count = 0
|
|
431
|
+
self.replace_all = True
|
|
432
|
+
self.original_code = ""
|
|
433
|
+
self.shell_prefix = False
|
|
434
|
+
|
|
435
|
+
def has_valid_input(self) -> bool:
|
|
436
|
+
return len(self.name.strip()) > 0
|
|
437
|
+
|
|
438
|
+
def add_char(self, char: str):
|
|
439
|
+
self.name = self.name[:self.name_cursor] + char + self.name[self.name_cursor:]
|
|
440
|
+
self.name_cursor += 1
|
|
441
|
+
|
|
442
|
+
def delete_char(self):
|
|
443
|
+
if self.name_cursor > 0:
|
|
444
|
+
self.name = self.name[:self.name_cursor-1] + self.name[self.name_cursor:]
|
|
445
|
+
self.name_cursor -= 1
|
|
446
|
+
|
|
447
|
+
def cursor_left(self):
|
|
448
|
+
if self.name_cursor > 0:
|
|
449
|
+
self.name_cursor -= 1
|
|
450
|
+
|
|
451
|
+
def cursor_right(self):
|
|
452
|
+
if self.name_cursor < len(self.name):
|
|
453
|
+
self.name_cursor += 1
|
|
454
|
+
|
|
455
|
+
def delete_forward(self):
|
|
456
|
+
if self.name_cursor < len(self.name):
|
|
457
|
+
self.name = self.name[:self.name_cursor] + self.name[self.name_cursor+1:]
|
|
458
|
+
|
|
459
|
+
def toggle_mode(self):
|
|
460
|
+
"""Toggle replace all mode."""
|
|
461
|
+
if self.duplicate_count > 0:
|
|
462
|
+
self.replace_all = not self.replace_all
|
|
463
|
+
|
|
464
|
+
def get_preview(self) -> Optional[Tuple[str, int]]:
|
|
465
|
+
"""Get live preview of the extraction."""
|
|
466
|
+
if not self.active or not self.original_code:
|
|
467
|
+
return None
|
|
468
|
+
var_name = self.name.strip()
|
|
469
|
+
if not var_name:
|
|
470
|
+
return None
|
|
471
|
+
valid, _ = is_valid_bash_identifier(var_name)
|
|
472
|
+
if not valid:
|
|
473
|
+
return None
|
|
474
|
+
# Handle shell prefix (!)
|
|
475
|
+
if self.shell_prefix:
|
|
476
|
+
bash_code = self.original_code[1:]
|
|
477
|
+
adj_start = max(0, self.start - 1)
|
|
478
|
+
adj_end = max(0, self.end - 1)
|
|
479
|
+
result = extract_variable(bash_code, adj_start, adj_end, var_name, self.replace_all)
|
|
480
|
+
if result:
|
|
481
|
+
# Prepend ! back and adjust cursor
|
|
482
|
+
return ('!' + result[0], result[1] + 1)
|
|
483
|
+
return None
|
|
484
|
+
return extract_variable(self.original_code, self.start, self.end, var_name, self.replace_all)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
state = BashExtractState()
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def handle_start(code: str, start: int, end: int, shell_prefix: bool = False) -> Optional[Tuple[int, int]]:
|
|
491
|
+
"""
|
|
492
|
+
Start extract mode.
|
|
493
|
+
|
|
494
|
+
Returns: (expanded_start, expanded_end) if started, or None
|
|
495
|
+
"""
|
|
496
|
+
# Handle shell prefix (!)
|
|
497
|
+
if shell_prefix:
|
|
498
|
+
bash_code = code[1:] if code.startswith('!') else code
|
|
499
|
+
adj_start = max(0, start - 1)
|
|
500
|
+
adj_end = max(0, end - 1)
|
|
501
|
+
else:
|
|
502
|
+
bash_code = code
|
|
503
|
+
adj_start = start
|
|
504
|
+
adj_end = end
|
|
505
|
+
|
|
506
|
+
can_extract, error, info = check_extract(bash_code, adj_start, adj_end)
|
|
507
|
+
|
|
508
|
+
if not can_extract:
|
|
509
|
+
state.active = True
|
|
510
|
+
state.error = error
|
|
511
|
+
return None
|
|
512
|
+
|
|
513
|
+
# Try to expand to a full AST node
|
|
514
|
+
tree = parse(bash_code)
|
|
515
|
+
node = find_smallest_extractable(tree, adj_start, adj_end)
|
|
516
|
+
if node:
|
|
517
|
+
expanded_start = node.start_byte
|
|
518
|
+
expanded_end = node.end_byte
|
|
519
|
+
else:
|
|
520
|
+
expanded_start = adj_start
|
|
521
|
+
expanded_end = adj_end
|
|
522
|
+
|
|
523
|
+
# Store original positions with ! offset for proper restore
|
|
524
|
+
if shell_prefix:
|
|
525
|
+
state.start_extract(
|
|
526
|
+
start, # Original start position (with !)
|
|
527
|
+
expanded_end + 1 if node else end, # Adjust back for !
|
|
528
|
+
info['duplicate_count'],
|
|
529
|
+
code, # Original code with !
|
|
530
|
+
shell_prefix
|
|
531
|
+
)
|
|
532
|
+
return (expanded_start + 1, expanded_end + 1) # Adjust for caller
|
|
533
|
+
else:
|
|
534
|
+
state.start_extract(
|
|
535
|
+
expanded_start,
|
|
536
|
+
expanded_end,
|
|
537
|
+
info['duplicate_count'],
|
|
538
|
+
code,
|
|
539
|
+
shell_prefix
|
|
540
|
+
)
|
|
541
|
+
return (expanded_start, expanded_end)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def handle_confirm(code: str) -> Optional[Tuple[str, int]]:
|
|
545
|
+
"""Handle Enter. Perform the extraction."""
|
|
546
|
+
if not state.active:
|
|
547
|
+
return None
|
|
548
|
+
|
|
549
|
+
var_name = state.name.strip()
|
|
550
|
+
valid, error = is_valid_bash_identifier(var_name)
|
|
551
|
+
|
|
552
|
+
if not valid:
|
|
553
|
+
state.error = error
|
|
554
|
+
return None
|
|
555
|
+
|
|
556
|
+
result = extract_variable(code, state.start, state.end, var_name, state.replace_all)
|
|
557
|
+
state.reset()
|
|
558
|
+
return result
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def handle_cancel():
|
|
562
|
+
"""Handle Escape/Ctrl+C."""
|
|
563
|
+
state.reset()
|