gitpane 1.0.0__py3-none-any.whl
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.
- gitpane/__init__.py +0 -0
- gitpane/account/__init__.py +0 -0
- gitpane/account/account_choice.py +297 -0
- gitpane/account/address_owner.py +67 -0
- gitpane/account/authentication.py +1811 -0
- gitpane/account/authentication_worker.py +836 -0
- gitpane/account/credential_manager.py +480 -0
- gitpane/account/credential_manager_install.py +171 -0
- gitpane/account/keyring_reader.py +135 -0
- gitpane/account/keyring_state.py +158 -0
- gitpane/account/ssh.py +312 -0
- gitpane/account/ssh_askpass.py +216 -0
- gitpane/account/ssh_prompt.py +222 -0
- gitpane/application.py +684 -0
- gitpane/commit/__init__.py +0 -0
- gitpane/commit/commit_metadata.py +88 -0
- gitpane/commit/commit_push.py +276 -0
- gitpane/commit/commit_write.py +830 -0
- gitpane/config.py +1066 -0
- gitpane/controller/__init__.py +0 -0
- gitpane/controller/account_controller.py +677 -0
- gitpane/controller/clone_controller.py +776 -0
- gitpane/controller/close_readiness.py +212 -0
- gitpane/controller/commit_timing_controller.py +566 -0
- gitpane/controller/conflict_controller.py +885 -0
- gitpane/controller/console_registry.py +38 -0
- gitpane/controller/emoji_window_controller.py +95 -0
- gitpane/controller/file_action_controller.py +479 -0
- gitpane/controller/history_read_controller.py +671 -0
- gitpane/controller/history_rewrite_controller.py +739 -0
- gitpane/controller/navigation_controller.py +658 -0
- gitpane/controller/opening_controller.py +375 -0
- gitpane/controller/operation_policy.py +76 -0
- gitpane/controller/rebase_controller.py +797 -0
- gitpane/controller/rebase_editor_controller.py +309 -0
- gitpane/controller/reference_form_controller.py +1677 -0
- gitpane/controller/reference_read_controller.py +242 -0
- gitpane/controller/refresh_controller.py +477 -0
- gitpane/controller/replay_controller.py +805 -0
- gitpane/controller/session_registry.py +337 -0
- gitpane/controller/setting_controller.py +592 -0
- gitpane/controller/shutdown_controller.py +345 -0
- gitpane/controller/staging_controller.py +199 -0
- gitpane/controller/staging_session_registry.py +47 -0
- gitpane/controller/stash_controller.py +367 -0
- gitpane/controller/transport_controller.py +813 -0
- gitpane/controller/welcome_controller.py +102 -0
- gitpane/controller/window_activation.py +116 -0
- gitpane/controller/worktree_state_controller.py +169 -0
- gitpane/emoji/__init__.py +0 -0
- gitpane/emoji/emoji_catalogue.py +156 -0
- gitpane/git_config/__init__.py +0 -0
- gitpane/git_config/git_setting.py +441 -0
- gitpane/history/__init__.py +0 -0
- gitpane/history/avatar.py +241 -0
- gitpane/history/file_history.py +359 -0
- gitpane/history/graph.py +145 -0
- gitpane/history/history_filter.py +31 -0
- gitpane/history/history_parse.py +605 -0
- gitpane/history/repository.py +1600 -0
- gitpane/history/repository_value.py +341 -0
- gitpane/history/revision.py +180 -0
- gitpane/history/revision_file.py +182 -0
- gitpane/history_rewrite/__init__.py +0 -0
- gitpane/history_rewrite/graph.py +210 -0
- gitpane/history_rewrite/guard.py +288 -0
- gitpane/history_rewrite/index.py +125 -0
- gitpane/history_rewrite/prepare.py +378 -0
- gitpane/history_rewrite/protocol.py +320 -0
- gitpane/history_rewrite/publish.py +444 -0
- gitpane/history_rewrite/recovery.py +481 -0
- gitpane/history_rewrite/value.py +229 -0
- gitpane/operation/__init__.py +0 -0
- gitpane/operation/console_line.py +313 -0
- gitpane/operation/console_tone.py +76 -0
- gitpane/operation/diagnostic.py +954 -0
- gitpane/operation/launch_signature.py +254 -0
- gitpane/operation/operation_status.py +87 -0
- gitpane/operation/operation_trace.py +766 -0
- gitpane/operation/repository_operation.py +781 -0
- gitpane/preference/__init__.py +0 -0
- gitpane/preference/preference_location.py +40 -0
- gitpane/preference/preference_store.py +590 -0
- gitpane/process/__init__.py +0 -0
- gitpane/process/exact_arguments.py +77 -0
- gitpane/process/git_output_text.py +20 -0
- gitpane/process/process_group.py +851 -0
- gitpane/process/process_log.py +114 -0
- gitpane/process/process_runner.py +1033 -0
- gitpane/process/released_child_watch.py +138 -0
- gitpane/recent/__init__.py +0 -0
- gitpane/recent/mount_table.py +422 -0
- gitpane/recent/recent_availability.py +414 -0
- gitpane/recent/recent_name.py +11 -0
- gitpane/recent/recent_search.py +114 -0
- gitpane/reference/__init__.py +0 -0
- gitpane/reference/reference_edit.py +2030 -0
- gitpane/reference/reference_snapshot.py +196 -0
- gitpane/reference/stash.py +730 -0
- gitpane/reference/stash_list.py +164 -0
- gitpane/sequencer/__init__.py +0 -0
- gitpane/sequencer/commit_message_rule.py +154 -0
- gitpane/sequencer/conflict.py +506 -0
- gitpane/sequencer/merge_recovery.py +163 -0
- gitpane/sequencer/merge_state.py +166 -0
- gitpane/sequencer/mergetool.py +856 -0
- gitpane/sequencer/rebase.py +750 -0
- gitpane/sequencer/rebase_edit_file.py +224 -0
- gitpane/sequencer/rebase_state.py +523 -0
- gitpane/sequencer/replay.py +1435 -0
- gitpane/sequencer/replay_legacy.py +253 -0
- gitpane/sequencer/replay_state.py +747 -0
- gitpane/submodule/__init__.py +0 -0
- gitpane/submodule/checkout_refusal.py +102 -0
- gitpane/submodule/checkout_refusal_notice.py +111 -0
- gitpane/submodule/cloned_without_checkout.py +65 -0
- gitpane/submodule/submodule_discovery.py +1180 -0
- gitpane/submodule/submodule_loss.py +218 -0
- gitpane/system/__init__.py +0 -0
- gitpane/system/appearance_portal.py +125 -0
- gitpane/system/application_identity.py +48 -0
- gitpane/system/command_line.py +36 -0
- gitpane/system/desktop_icon_fact.py +128 -0
- gitpane/system/desktop_icon_support.py +270 -0
- gitpane/system/desktop_shortcut.py +536 -0
- gitpane/system/icns_image.py +35 -0
- gitpane/system/installed_launcher.py +136 -0
- gitpane/system/linux_distribution.py +151 -0
- gitpane/system/network_folder.py +46 -0
- gitpane/system/path_identity.py +95 -0
- gitpane/system/private_tree_removal.py +47 -0
- gitpane/system/process_program.py +63 -0
- gitpane/system/qt_folder.py +38 -0
- gitpane/system/ready_channel_watch.py +67 -0
- gitpane/system/startup_activation.py +65 -0
- gitpane/system/terminal_detach.py +384 -0
- gitpane/system/xdg_session.py +85 -0
- gitpane/text/__init__.py +0 -0
- gitpane/text/text_query.py +97 -0
- gitpane/text/typed_path.py +75 -0
- gitpane/text/typed_text.py +17 -0
- gitpane/thread/__init__.py +0 -0
- gitpane/thread/weak_signal.py +93 -0
- gitpane/tool/__init__.py +0 -0
- gitpane/tool/app_bundle.py +164 -0
- gitpane/tool/desktop_text_editor.py +156 -0
- gitpane/tool/difftool.py +429 -0
- gitpane/tool/editor.py +445 -0
- gitpane/tool/editor_bridge.py +193 -0
- gitpane/tool/editor_recipe.py +481 -0
- gitpane/tool/file_reveal.py +94 -0
- gitpane/tool/git_executable.py +264 -0
- gitpane/tool/git_version.py +62 -0
- gitpane/tool/kdiff3.py +41 -0
- gitpane/tool/kdiff3_install.py +162 -0
- gitpane/tool/launch_services_text_editor.py +104 -0
- gitpane/tool/mergetool_recipe.py +84 -0
- gitpane/tool/system_editor_default.py +80 -0
- gitpane/tool/system_text_editor.py +44 -0
- gitpane/tool/system_tool.py +816 -0
- gitpane/tool/terminal_recipe.py +390 -0
- gitpane/transport/__init__.py +0 -0
- gitpane/transport/branch_exchange.py +1690 -0
- gitpane/transport/clone.py +1273 -0
- gitpane/transport/credential_read_retry.py +35 -0
- gitpane/transport/git_refusal.py +207 -0
- gitpane/transport/push_tracking.py +121 -0
- gitpane/transport/refusal_notice.py +225 -0
- gitpane/transport/remote.py +747 -0
- gitpane/transport/remote_config.py +544 -0
- gitpane/ui/__init__.py +0 -0
- gitpane/ui/console/__init__.py +0 -0
- gitpane/ui/console/error_dialog.py +332 -0
- gitpane/ui/console/operation_dialog.py +408 -0
- gitpane/ui/console/operation_output.py +91 -0
- gitpane/ui/console/operation_output_view.py +373 -0
- gitpane/ui/emoji/__init__.py +0 -0
- gitpane/ui/emoji/emoji_font.py +257 -0
- gitpane/ui/emoji/emoji_grid.py +125 -0
- gitpane/ui/emoji/emoji_window.py +154 -0
- gitpane/ui/emoji/file_action_icon.py +131 -0
- gitpane/ui/emoji/opentype_family_name.py +182 -0
- gitpane/ui/history_rewrite/__init__.py +0 -0
- gitpane/ui/history_rewrite/details.py +78 -0
- gitpane/ui/history_rewrite/preview.py +565 -0
- gitpane/ui/history_rewrite/preview_model.py +291 -0
- gitpane/ui/history_rewrite/question.py +87 -0
- gitpane/ui/history_rewrite/recovery_dialog.py +262 -0
- gitpane/ui/look/__init__.py +0 -0
- gitpane/ui/look/action_icon.py +504 -0
- gitpane/ui/look/application_icon.py +78 -0
- gitpane/ui/look/application_style.py +317 -0
- gitpane/ui/look/avatar_label.py +53 -0
- gitpane/ui/look/color_contrast.py +43 -0
- gitpane/ui/look/desktop_appearance_follower.py +118 -0
- gitpane/ui/look/fixed_font.py +21 -0
- gitpane/ui/look/gtk_settings.py +116 -0
- gitpane/ui/look/icon_density.py +16 -0
- gitpane/ui/look/reference_font.py +19 -0
- gitpane/ui/look/theme_color.py +63 -0
- gitpane/ui/look/theme_look.py +95 -0
- gitpane/ui/look/theme_palette.py +173 -0
- gitpane/ui/look/warning_icon.py +86 -0
- gitpane/ui/look/windows_control_look.py +250 -0
- gitpane/ui/reader/__init__.py +0 -0
- gitpane/ui/reader/commit_message.py +129 -0
- gitpane/ui/reader/commit_panel.py +378 -0
- gitpane/ui/reader/diff_panel.py +626 -0
- gitpane/ui/reader/file_action.py +138 -0
- gitpane/ui/reader/file_tree_panel.py +235 -0
- gitpane/ui/reader/reader_toolbar.py +56 -0
- gitpane/ui/reader/revision_tree.py +80 -0
- gitpane/ui/reader/search_result.py +165 -0
- gitpane/ui/reader/source_text.py +134 -0
- gitpane/ui/reader/source_view.py +151 -0
- gitpane/ui/reader/syntax_span.py +67 -0
- gitpane/ui/reference/__init__.py +0 -0
- gitpane/ui/reference/branch_selector.py +106 -0
- gitpane/ui/reference/checkout_dialog.py +419 -0
- gitpane/ui/reference/checkout_revision_dialog.py +182 -0
- gitpane/ui/reference/commit_summary.py +339 -0
- gitpane/ui/reference/create_branch_dialog.py +346 -0
- gitpane/ui/reference/create_tag_dialog.py +283 -0
- gitpane/ui/reference/delete_local_branch_dialog.py +323 -0
- gitpane/ui/reference/delete_tag_dialog.py +394 -0
- gitpane/ui/reference/form_opening.py +53 -0
- gitpane/ui/reference/reference_dialog.py +417 -0
- gitpane/ui/reference/reference_feedback.py +62 -0
- gitpane/ui/reference/reference_form.py +139 -0
- gitpane/ui/reference/rename_branch_dialog.py +179 -0
- gitpane/ui/reference/reset_branch_dialog.py +430 -0
- gitpane/ui/reference/reset_dialog.py +263 -0
- gitpane/ui/reference/revision_dialog.py +121 -0
- gitpane/ui/reference/revision_picker.py +227 -0
- gitpane/ui/reference/select_branches_dialog.py +199 -0
- gitpane/ui/reference/stash_dialog.py +171 -0
- gitpane/ui/repository/__init__.py +0 -0
- gitpane/ui/repository/graph_delegate.py +298 -0
- gitpane/ui/repository/history_avatar.py +130 -0
- gitpane/ui/repository/history_model.py +179 -0
- gitpane/ui/repository/repository_list_popup.py +319 -0
- gitpane/ui/repository/repository_window.py +2580 -0
- gitpane/ui/resource/ArrowDown.png +0 -0
- gitpane/ui/resource/ArrowUp.png +0 -0
- gitpane/ui/resource/BranchCheckout.png +0 -0
- gitpane/ui/resource/BranchCreate.png +0 -0
- gitpane/ui/resource/BranchDelete.png +0 -0
- gitpane/ui/resource/CopyToClipboard.png +0 -0
- gitpane/ui/resource/GitPane-16.png +0 -0
- gitpane/ui/resource/GitPane-24.png +0 -0
- gitpane/ui/resource/GitPane-256.png +0 -0
- gitpane/ui/resource/GitPane-32.png +0 -0
- gitpane/ui/resource/GitPane-48.png +0 -0
- gitpane/ui/resource/GitPane-64.png +0 -0
- gitpane/ui/resource/GitPane.ico +0 -0
- gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
- gitpane/ui/resource/NotoColorEmoji_WindowsCompatible.ttf +0 -0
- gitpane/ui/resource/Renamed.png +0 -0
- gitpane/ui/resource/ResetAnotherBranchToHere.png +0 -0
- gitpane/ui/resource/ResetCurrentBranchToHere.png +0 -0
- gitpane/ui/resource/Stash.png +0 -0
- gitpane/ui/resource/Tag.png +0 -0
- gitpane/ui/resource/TagCreate.png +0 -0
- gitpane/ui/resource/TagDelete.png +0 -0
- gitpane/ui/resource/Unmerged.png +0 -0
- gitpane/ui/resource/checkout.png +0 -0
- gitpane/ui/sequencer/__init__.py +0 -0
- gitpane/ui/sequencer/conflict_dialog.py +532 -0
- gitpane/ui/sequencer/merge_recovery_dialog.py +67 -0
- gitpane/ui/sequencer/rebase_dialog.py +461 -0
- gitpane/ui/sequencer/rebase_editor_window.py +220 -0
- gitpane/ui/sequencer/replay_conflict_prompt.py +105 -0
- gitpane/ui/sequencer/replay_dialog.py +501 -0
- gitpane/ui/setting/__init__.py +0 -0
- gitpane/ui/setting/account_page.py +724 -0
- gitpane/ui/setting/add_account_dialog.py +1005 -0
- gitpane/ui/setting/credential_manager_dialog.py +151 -0
- gitpane/ui/setting/git_setting_widget.py +388 -0
- gitpane/ui/setting/setting_block.py +19 -0
- gitpane/ui/setting/setting_dialog.py +750 -0
- gitpane/ui/setting/ssh_widget.py +232 -0
- gitpane/ui/setting/utility_page.py +240 -0
- gitpane/ui/staging/__init__.py +0 -0
- gitpane/ui/staging/staging_action.py +356 -0
- gitpane/ui/staging/staging_window.py +745 -0
- gitpane/ui/transport/__init__.py +0 -0
- gitpane/ui/transport/clone_dialog.py +879 -0
- gitpane/ui/transport/push_dialog.py +580 -0
- gitpane/ui/transport/push_rejection_prompt.py +100 -0
- gitpane/ui/transport/push_tracking_question.py +88 -0
- gitpane/ui/transport/remote_dialog.py +943 -0
- gitpane/ui/welcome/__init__.py +0 -0
- gitpane/ui/welcome/invalid_repository_prompt.py +114 -0
- gitpane/ui/welcome/welcome_window.py +1137 -0
- gitpane/ui/widget/__init__.py +0 -0
- gitpane/ui/widget/busy_indicator.py +106 -0
- gitpane/ui/widget/commit_date_text.py +55 -0
- gitpane/ui/widget/content_growth.py +262 -0
- gitpane/ui/widget/content_measure.py +99 -0
- gitpane/ui/widget/dialog_layout.py +668 -0
- gitpane/ui/widget/drop_down_wheel_filter.py +32 -0
- gitpane/ui/widget/flow_layout.py +110 -0
- gitpane/ui/widget/long_text.py +542 -0
- gitpane/ui/widget/mnemonic.py +13 -0
- gitpane/ui/widget/numbered_text_edit.py +127 -0
- gitpane/ui/widget/plain_label.py +15 -0
- gitpane/ui/widget/plain_tooltip.py +19 -0
- gitpane/ui/widget/refresh_advice.py +25 -0
- gitpane/ui/widget/result_label.py +227 -0
- gitpane/ui/widget/result_status_bar.py +53 -0
- gitpane/ui/widget/scrolled_text.py +69 -0
- gitpane/ui/widget/shortcut_text.py +31 -0
- gitpane/ui/widget/standard_icon_label.py +55 -0
- gitpane/ui/widget/system_shortcut.py +75 -0
- gitpane/ui/widget/unavailable_reason.py +29 -0
- gitpane/ui/widget/wheel_safe_drop_down.py +24 -0
- gitpane/ui/window_control/__init__.py +0 -0
- gitpane/ui/window_control/button_focus.py +42 -0
- gitpane/ui/window_control/compositor_answer.py +152 -0
- gitpane/ui/window_control/compositor_growth_cap.py +73 -0
- gitpane/ui/window_control/desktop_activation.py +203 -0
- gitpane/ui/window_control/dialog_modality.py +122 -0
- gitpane/ui/window_control/item_activation.py +42 -0
- gitpane/ui/window_control/modal_focus_relay.py +124 -0
- gitpane/ui/window_control/wayland_activation.py +695 -0
- gitpane/ui/window_control/window_placement.py +305 -0
- gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
- gitpane/vendor/w32.exe +0 -0
- gitpane/vendor/w64-arm.exe +0 -0
- gitpane/vendor/w64.exe +0 -0
- gitpane/worktree/__init__.py +0 -0
- gitpane/worktree/command_sequence.py +110 -0
- gitpane/worktree/file_action_target.py +61 -0
- gitpane/worktree/file_menu_action.py +52 -0
- gitpane/worktree/historical_restore.py +361 -0
- gitpane/worktree/ignore_rule.py +266 -0
- gitpane/worktree/mutation.py +1539 -0
- gitpane/worktree/partial_patch.py +435 -0
- gitpane/worktree/staged_reset.py +447 -0
- gitpane/worktree/staging.py +1418 -0
- gitpane/worktree/trash.py +302 -0
- gitpane/worktree/worktree_file.py +406 -0
- gitpane/worktree/worktree_service.py +584 -0
- gitpane/worktree/worktree_snapshot.py +630 -0
- gitpane-1.0.0.dist-info/METADATA +141 -0
- gitpane-1.0.0.dist-info/RECORD +353 -0
- gitpane-1.0.0.dist-info/WHEEL +5 -0
- gitpane-1.0.0.dist-info/entry_points.txt +2 -0
- gitpane-1.0.0.dist-info/licenses/LICENSE +21 -0
- gitpane-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +109 -0
- gitpane-1.0.0.dist-info/licenses/src/gitpane/ui/resource/NotoColorEmoji-OFL.txt +92 -0
- gitpane-1.0.0.dist-info/licenses/src/gitpane/vendor/DISTLIB-LICENSE.txt +284 -0
- gitpane-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Show one rebase edit Git waits for until the user continues, saves or aborts it."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from PySide6.QtCore import Qt, Signal
|
|
9
|
+
from PySide6.QtGui import QFontDatabase, QKeySequence, QPalette, QShortcut
|
|
10
|
+
from PySide6.QtWidgets import QApplication, QDialog, QHBoxLayout, QMessageBox, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget
|
|
11
|
+
from shiboken6 import isValid
|
|
12
|
+
|
|
13
|
+
from gitpane.config import DARK_BACKGROUND_LIGHTNESS_LIMIT, ERROR_DARK_COLOR, ERROR_LIGHT_COLOR, LINE_SEPARATOR, OPERATION_HEIGHT, OPERATION_WIDTH
|
|
14
|
+
from gitpane.recent.recent_name import repository_display_name
|
|
15
|
+
from gitpane.tool.editor import EditorRequestKind
|
|
16
|
+
from gitpane.ui.look.theme_look import follow_theme
|
|
17
|
+
from gitpane.ui.widget.long_text import WrappingLabel
|
|
18
|
+
from gitpane.ui.widget.numbered_text_edit import NumberedTextEdit
|
|
19
|
+
from gitpane.ui.widget.system_shortcut import EDITOR_SAVE_SHORTCUTS, EDITOR_VALIDATE_SHORTCUTS
|
|
20
|
+
from gitpane.ui.window_control.dialog_modality import open_window_modal, prepare_message_box
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from PySide6.QtCore import QRect
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RebaseEditorText(Enum):
|
|
27
|
+
"""Texts the rebase editor window shows, one member per text so a misspelt name is an error."""
|
|
28
|
+
|
|
29
|
+
CONTINUE = "Continue"
|
|
30
|
+
SAVE = "Save"
|
|
31
|
+
ABORT = "Abort"
|
|
32
|
+
ABORT_QUESTION_TITLE = "Abort the rebase?"
|
|
33
|
+
ABORT_QUESTION = "Abort the whole rebase and go back to where it started?"
|
|
34
|
+
ABORT_REBASE = "Abort rebase"
|
|
35
|
+
KEEP_EDITING = "Keep editing"
|
|
36
|
+
EMPTY_MESSAGE = "Commit message cannot be empty"
|
|
37
|
+
SAVE_FAILED = "Could not save the file: {cause}"
|
|
38
|
+
READ_FAILED = "GitPane cannot read the file Git is waiting for. {button} hands it back unchanged. {cause}"
|
|
39
|
+
ENCODING_FAILED = "the text cannot be written in the file's encoding"
|
|
40
|
+
# The cause shown if a write ends with neither a written file nor an error, which the write function never does.
|
|
41
|
+
WRITE_GAVE_NO_RESULT = "the write gave no result"
|
|
42
|
+
# The windows are not modal, so the folder of the repository tells apart the editors of two repositories.
|
|
43
|
+
WINDOW_TITLE = "{kind} — {repository}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EditorValidation(Enum):
|
|
47
|
+
"""What the validating button of the window does: hand the text to Git so the rebase goes on, or save it while the rebase stays paused."""
|
|
48
|
+
|
|
49
|
+
CONTINUE = "continue"
|
|
50
|
+
SAVE = "save"
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def label(self) -> str:
|
|
54
|
+
"""Answer the text of the validating button."""
|
|
55
|
+
return (RebaseEditorText.CONTINUE if self == EditorValidation.CONTINUE else RebaseEditorText.SAVE).value
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RebaseEditorWindow(QDialog):
|
|
59
|
+
"""Edit the text of one file Git waits for, as Git wrote it, until the user answers with the validating button or Abort.
|
|
60
|
+
|
|
61
|
+
The window is never modal: the user can read the graph of the repository while editing. Its close button and
|
|
62
|
+
Escape ask before aborting the whole rebase, so neither can leave Git waiting without a window nor abort by mistake.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
validate_requested = Signal()
|
|
66
|
+
save_requested = Signal()
|
|
67
|
+
abort_requested = Signal()
|
|
68
|
+
|
|
69
|
+
def __init__(self, kind: EditorRequestKind, validation: EditorValidation, text: str, repository_dir: str, parent: QWidget | None = None) -> None:
|
|
70
|
+
"""Show Git's text in a numbered fixed-width editor, under a hidden context header, above a hidden error line and the two answer buttons.
|
|
71
|
+
|
|
72
|
+
The title names the kind of edit and the repository folder, so the editors of two repositories are told apart.
|
|
73
|
+
"""
|
|
74
|
+
super().__init__(parent)
|
|
75
|
+
self.kind = kind
|
|
76
|
+
self.validation = validation
|
|
77
|
+
self.answered = False
|
|
78
|
+
self.busy = False
|
|
79
|
+
self.abort_question: QMessageBox | None = None
|
|
80
|
+
self.setWindowTitle(RebaseEditorText.WINDOW_TITLE.value.format(kind=kind.window_title, repository=repository_display_name(repository_dir)))
|
|
81
|
+
self.resize(OPERATION_WIDTH, OPERATION_HEIGHT)
|
|
82
|
+
layout = QVBoxLayout(self)
|
|
83
|
+
self.context_label = WrappingLabel()
|
|
84
|
+
self.context_label.setWordWrap(True)
|
|
85
|
+
self.context_label.hide()
|
|
86
|
+
layout.addWidget(self.context_label)
|
|
87
|
+
self.text_edit = NumberedTextEdit()
|
|
88
|
+
self.text_edit.setAccessibleName(kind.window_title)
|
|
89
|
+
# Monochrome on purpose: the user chose plain fixed-width text over coloured commands and dimmed comments.
|
|
90
|
+
self.text_edit.setFont(QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont))
|
|
91
|
+
# An instruction stays on one visual line, as Git reads it; a message wraps in the window without adding a line to the text.
|
|
92
|
+
wrap_mode = QPlainTextEdit.LineWrapMode.NoWrap if kind == EditorRequestKind.SEQUENCE else QPlainTextEdit.LineWrapMode.WidgetWidth
|
|
93
|
+
self.text_edit.setLineWrapMode(wrap_mode)
|
|
94
|
+
self.text_edit.setPlainText(text)
|
|
95
|
+
layout.addWidget(self.text_edit, 1)
|
|
96
|
+
self.error_label = WrappingLabel()
|
|
97
|
+
self.error_label.setWordWrap(True)
|
|
98
|
+
self.error_label.hide()
|
|
99
|
+
layout.addWidget(self.error_label)
|
|
100
|
+
button_row = QHBoxLayout()
|
|
101
|
+
button_row.addStretch(1)
|
|
102
|
+
self.validate_button = QPushButton(validation.label)
|
|
103
|
+
self.validate_button.clicked.connect(self.validate_requested)
|
|
104
|
+
self.abort_button = QPushButton(RebaseEditorText.ABORT.value)
|
|
105
|
+
self.abort_button.clicked.connect(self.abort_requested)
|
|
106
|
+
for button in (self.validate_button, self.abort_button):
|
|
107
|
+
# Enter must never trigger Abort or Continue from the text; each command stays an explicit click or Space on its focused button.
|
|
108
|
+
button.setAutoDefault(False)
|
|
109
|
+
button_row.addWidget(button)
|
|
110
|
+
layout.addLayout(button_row)
|
|
111
|
+
for sequence in EDITOR_VALIDATE_SHORTCUTS:
|
|
112
|
+
validate_shortcut = QShortcut(QKeySequence(sequence), self)
|
|
113
|
+
validate_shortcut.setContext(Qt.ShortcutContext.WindowShortcut)
|
|
114
|
+
validate_shortcut.activated.connect(self.press_validate_button)
|
|
115
|
+
for sequence in EDITOR_SAVE_SHORTCUTS:
|
|
116
|
+
save_shortcut = QShortcut(QKeySequence(sequence), self)
|
|
117
|
+
save_shortcut.setContext(Qt.ShortcutContext.WindowShortcut)
|
|
118
|
+
save_shortcut.activated.connect(self.ask_save)
|
|
119
|
+
self.refresh_metrics()
|
|
120
|
+
follow_theme(self, self.refresh_metrics)
|
|
121
|
+
|
|
122
|
+
def edited_text(self) -> str:
|
|
123
|
+
"""Answer the text as Git must read it back, with line feeds between lines and nothing else changed."""
|
|
124
|
+
return self.text_edit.raw_text()
|
|
125
|
+
|
|
126
|
+
def press_validate_button(self) -> None:
|
|
127
|
+
"""Press the validating button from the keyboard, as a click would, unless a write already runs."""
|
|
128
|
+
if self.validate_button.isEnabled():
|
|
129
|
+
self.validate_button.click()
|
|
130
|
+
|
|
131
|
+
def ask_save(self) -> None:
|
|
132
|
+
"""Ask for the text to be saved without answering Git, unless a write already runs."""
|
|
133
|
+
if not self.busy:
|
|
134
|
+
self.save_requested.emit()
|
|
135
|
+
|
|
136
|
+
def show_context(self, lines: tuple[str, ...]) -> None:
|
|
137
|
+
"""Show the repository and the commits an edit is about above the text, or hide the header when there is nothing to show."""
|
|
138
|
+
self.context_label.setText(LINE_SEPARATOR.join(lines))
|
|
139
|
+
self.context_label.setVisible(bool(lines))
|
|
140
|
+
|
|
141
|
+
def show_error(self, text: str) -> None:
|
|
142
|
+
"""Show a problem above the buttons, or hide the line when there is none."""
|
|
143
|
+
self.error_label.setText(text)
|
|
144
|
+
self.error_label.setVisible(bool(text))
|
|
145
|
+
|
|
146
|
+
def set_busy(self, busy: bool) -> None:
|
|
147
|
+
"""Disable both answers and the shortcuts while the text is being written, so a second request cannot answer twice."""
|
|
148
|
+
self.busy = busy
|
|
149
|
+
self.validate_button.setEnabled(not busy)
|
|
150
|
+
self.abort_button.setEnabled(not busy)
|
|
151
|
+
|
|
152
|
+
def set_read_only(self, reason: str) -> None:
|
|
153
|
+
"""Keep the text visible but not editable, saying why; the answers stay available."""
|
|
154
|
+
self.text_edit.setReadOnly(True)
|
|
155
|
+
self.show_error(reason)
|
|
156
|
+
|
|
157
|
+
def present(self) -> None:
|
|
158
|
+
"""Show the window in front of the repository window with the keyboard in its text, without blocking the other windows."""
|
|
159
|
+
self.show()
|
|
160
|
+
self.raise_()
|
|
161
|
+
self.activateWindow()
|
|
162
|
+
self.text_edit.setFocus()
|
|
163
|
+
|
|
164
|
+
def close_after_answer(self) -> None:
|
|
165
|
+
"""Close the window once Git has its answer, or once the request is gone, without any question; an open question closes with it.
|
|
166
|
+
|
|
167
|
+
Called again on a closed window, it does nothing, so the window reports its end once.
|
|
168
|
+
"""
|
|
169
|
+
self.answered = True
|
|
170
|
+
question = self.abort_question
|
|
171
|
+
if question is not None and isValid(question):
|
|
172
|
+
question.close()
|
|
173
|
+
if self.isVisible():
|
|
174
|
+
QDialog.reject(self)
|
|
175
|
+
|
|
176
|
+
def reject(self) -> None:
|
|
177
|
+
"""Ask before aborting the whole rebase when the user closes the window or presses Escape.
|
|
178
|
+
|
|
179
|
+
Qt closes a dialog through this method, from its close button as from Escape. Keep editing is the default and
|
|
180
|
+
the answer of Escape, so a second Escape never aborts; a question already open is brought back instead of a second one.
|
|
181
|
+
"""
|
|
182
|
+
if self.answered:
|
|
183
|
+
QDialog.reject(self)
|
|
184
|
+
return
|
|
185
|
+
question = self.abort_question
|
|
186
|
+
if question is not None and isValid(question):
|
|
187
|
+
question.raise_()
|
|
188
|
+
question.activateWindow()
|
|
189
|
+
return
|
|
190
|
+
box = QMessageBox(QMessageBox.Icon.Warning, RebaseEditorText.ABORT_QUESTION_TITLE.value, RebaseEditorText.ABORT_QUESTION.value, parent=self)
|
|
191
|
+
prepare_message_box(box, RebaseEditorText.ABORT_QUESTION_TITLE.value)
|
|
192
|
+
box.setTextFormat(Qt.TextFormat.PlainText)
|
|
193
|
+
abort_rebase_button = box.addButton(RebaseEditorText.ABORT_REBASE.value, QMessageBox.ButtonRole.DestructiveRole)
|
|
194
|
+
keep_editing_button = box.addButton(RebaseEditorText.KEEP_EDITING.value, QMessageBox.ButtonRole.RejectRole)
|
|
195
|
+
box.setDefaultButton(keep_editing_button)
|
|
196
|
+
box.setEscapeButton(keep_editing_button)
|
|
197
|
+
self.abort_question = box
|
|
198
|
+
|
|
199
|
+
def finished() -> None:
|
|
200
|
+
"""Abort only on an explicit Abort rebase while Git still waits; any other ending keeps the window and its text."""
|
|
201
|
+
self.abort_question = None
|
|
202
|
+
aborted = box.clickedButton() is abort_rebase_button and not self.answered
|
|
203
|
+
box.deleteLater()
|
|
204
|
+
if aborted:
|
|
205
|
+
self.abort_requested.emit()
|
|
206
|
+
|
|
207
|
+
box.finished.connect(finished)
|
|
208
|
+
open_window_modal(box)
|
|
209
|
+
|
|
210
|
+
def refresh_metrics(self, available: QRect | None = None) -> None:
|
|
211
|
+
"""Keep the error line readable on light and dark backgrounds, again after each switch of appearance.
|
|
212
|
+
|
|
213
|
+
The choice is read on the application palette: at a palette notice, the window's own palette can still be the
|
|
214
|
+
previous one.
|
|
215
|
+
"""
|
|
216
|
+
dark = QApplication.palette().color(QPalette.ColorRole.Window).lightnessF() < DARK_BACKGROUND_LIGHTNESS_LIMIT
|
|
217
|
+
color = ERROR_DARK_COLOR if dark else ERROR_LIGHT_COLOR
|
|
218
|
+
error_style = f"color: {color}; font-weight: bold;"
|
|
219
|
+
if self.error_label.styleSheet() != error_style:
|
|
220
|
+
self.error_label.setStyleSheet(error_style)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Ask whether to resolve the conflicts of a paused Cherry-pick or Revert right away."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import Qt, Signal
|
|
8
|
+
from PySide6.QtWidgets import QDialog, QHBoxLayout, QLabel, QPushButton, QStyle, QVBoxLayout
|
|
9
|
+
|
|
10
|
+
from gitpane.ui.look.theme_look import follow_theme
|
|
11
|
+
from gitpane.ui.widget.dialog_layout import (
|
|
12
|
+
MESSAGE_BOX_BODY_MARGINS,
|
|
13
|
+
MESSAGE_BOX_BUTTON_BAND,
|
|
14
|
+
MESSAGE_BOX_BUTTON_SIZE,
|
|
15
|
+
MESSAGE_BOX_ICON_SIDE,
|
|
16
|
+
MESSAGE_BOX_ICON_TO_TEXT_SPACE,
|
|
17
|
+
BandButtonSizing,
|
|
18
|
+
install_banded_layout,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from PySide6.QtGui import QKeyEvent
|
|
23
|
+
from PySide6.QtWidgets import QWidget
|
|
24
|
+
|
|
25
|
+
CONFLICT_PROMPT_TITLE = "Merge conflicts"
|
|
26
|
+
CONFLICT_PROMPT_TEXT = "There are unresolved merge conflicts, solve conflicts now?"
|
|
27
|
+
# A Windows message box answers Y and N directly (with or without Alt) and shows no underlined letters, so the dialog
|
|
28
|
+
# handles these keys itself instead of using mnemonics.
|
|
29
|
+
YES_KEY, NO_KEY = Qt.Key.Key_Y, Qt.Key.Key_N
|
|
30
|
+
ANSWER_MODIFIER_SET = {Qt.KeyboardModifier.NoModifier, Qt.KeyboardModifier.AltModifier}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ReplayConflictPrompt(QDialog):
|
|
34
|
+
"""Offer an explicit Yes/No choice that never blocks the repository window.
|
|
35
|
+
|
|
36
|
+
A dedicated dialog rather than a QMessageBox keeps the Yes contract explicit (one signal, emitted once)
|
|
37
|
+
instead of comparing the finished() code with a button enumeration, and leaves room for a custom layout.
|
|
38
|
+
No, Escape and the title-bar close all leave the operation paused.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
resolve_requested = Signal()
|
|
42
|
+
|
|
43
|
+
def __init__(self, parent: QWidget | None = None) -> None:
|
|
44
|
+
"""Build the non-modal prompt with Yes as the default answer."""
|
|
45
|
+
super().__init__(parent)
|
|
46
|
+
self.resolve_emitted = False
|
|
47
|
+
self.setWindowTitle(CONFLICT_PROMPT_TITLE)
|
|
48
|
+
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
49
|
+
# Like a Windows message box, the title bar has only the close button: without the system menu hint Windows
|
|
50
|
+
# shows no window icon, and there is no help button. The customize hint stops Qt from adding its default hints.
|
|
51
|
+
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowCloseButtonHint)
|
|
52
|
+
layout = QVBoxLayout()
|
|
53
|
+
layout.setSpacing(0)
|
|
54
|
+
message_row = QHBoxLayout()
|
|
55
|
+
message_row.setSpacing(MESSAGE_BOX_ICON_TO_TEXT_SPACE)
|
|
56
|
+
self.icon_label = QLabel()
|
|
57
|
+
self.apply_theme_look()
|
|
58
|
+
self.icon_label.setFixedSize(MESSAGE_BOX_ICON_SIDE, MESSAGE_BOX_ICON_SIDE)
|
|
59
|
+
message_row.addWidget(self.icon_label)
|
|
60
|
+
self.text_label = QLabel(CONFLICT_PROMPT_TEXT)
|
|
61
|
+
# The question is one short fixed sentence; like a message box it reads on a single line instead of wrapping
|
|
62
|
+
# into the narrow width a word-wrapped label proposes.
|
|
63
|
+
self.text_label.setTextFormat(Qt.TextFormat.PlainText)
|
|
64
|
+
self.text_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
|
65
|
+
message_row.addWidget(self.text_label, 1)
|
|
66
|
+
layout.addLayout(message_row)
|
|
67
|
+
self.yes_button = QPushButton("Yes")
|
|
68
|
+
self.yes_button.setFixedSize(MESSAGE_BOX_BUTTON_SIZE)
|
|
69
|
+
self.no_button = QPushButton("No")
|
|
70
|
+
self.no_button.setFixedSize(MESSAGE_BOX_BUTTON_SIZE)
|
|
71
|
+
self.no_button.setAutoDefault(False)
|
|
72
|
+
# Like the Git Extensions message box: the question on a white body, Yes and No on the grey area below it.
|
|
73
|
+
self.button_band = install_banded_layout(self, layout, [self.yes_button, self.no_button], MESSAGE_BOX_BUTTON_BAND, MESSAGE_BOX_BODY_MARGINS, button_sizing=BandButtonSizing.WINDOW)
|
|
74
|
+
follow_theme(self, self.apply_theme_look)
|
|
75
|
+
# Set once Yes belongs to the dialog, so Qt keeps it as the main default when the focus moves.
|
|
76
|
+
self.yes_button.setDefault(True)
|
|
77
|
+
self.yes_button.clicked.connect(self._answer_yes)
|
|
78
|
+
self.no_button.clicked.connect(self.reject)
|
|
79
|
+
# No explicit resize or move here: Qt sizes the dialog to its content when it is first shown, and the shared
|
|
80
|
+
# new-window placement then centres that final frame on the repository window. Fitting the geometry before the
|
|
81
|
+
# first show froze a stale size instead of the message-box shape.
|
|
82
|
+
|
|
83
|
+
def apply_theme_look(self) -> None:
|
|
84
|
+
"""Show the question icon the style gives for the current appearance; laid again after a light/dark switch.
|
|
85
|
+
|
|
86
|
+
The style redraws a question icon that cannot be read on a dark window, so the icon of one appearance is not the
|
|
87
|
+
icon of the other. It is asked for the application palette, which already holds the new colors at a switch.
|
|
88
|
+
"""
|
|
89
|
+
self.icon_label.setPixmap(self.style().standardIcon(QStyle.StandardPixmap.SP_MessageBoxQuestion).pixmap(MESSAGE_BOX_ICON_SIDE, MESSAGE_BOX_ICON_SIDE))
|
|
90
|
+
|
|
91
|
+
def keyPressEvent(self, event: QKeyEvent) -> None:
|
|
92
|
+
"""Answer Yes on Y and No on N, as a Windows message box does; other keys keep the dialog behavior."""
|
|
93
|
+
button_by_key = {YES_KEY: self.yes_button, NO_KEY: self.no_button}
|
|
94
|
+
button = button_by_key.get(event.key())
|
|
95
|
+
if button is None or event.modifiers() not in ANSWER_MODIFIER_SET:
|
|
96
|
+
super().keyPressEvent(event)
|
|
97
|
+
return
|
|
98
|
+
button.click()
|
|
99
|
+
|
|
100
|
+
def _answer_yes(self) -> None:
|
|
101
|
+
"""Request resolution at most once, then close the prompt."""
|
|
102
|
+
if not self.resolve_emitted:
|
|
103
|
+
self.resolve_emitted = True
|
|
104
|
+
self.resolve_requested.emit()
|
|
105
|
+
self.accept()
|