├── .python-version ├── .gitignore ├── img └── showcase.png ├── sublime ├── Default (OSX).sublime-keymap ├── Default (Linux).sublime-keymap ├── Default.sublime-commands ├── Main.sublime-menu └── Default.sublime-keymap ├── messages.json ├── syntax ├── GitUntracked.sublime-syntax └── GitStatus.sublime-syntax ├── mypy.ini ├── messages ├── install.txt ├── 0.5.0.txt ├── 0.6.0.txt └── 0.4.0.txt ├── tox.ini ├── core ├── diff_view.py ├── layout.py ├── status_view.py ├── git_diff_view.py ├── view_manager.py └── git_commands.py ├── utils.py ├── LICENSE ├── command_stage_unstage_file.py ├── command_goto_file.py ├── command_dismiss_changes.py ├── README.md ├── command_goto_hunk.py ├── command_stage_unstage_hunk.py ├── command_dismiss_hunk.py ├── main.py ├── stubs ├── sublime_plugin.pyi └── sublime.pyi ├── command_update_status_view.py └── command_update_diff_view.py /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .mypy_cache/ 2 | -------------------------------------------------------------------------------- /img/showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/predragnikolic/sublime-git-diff-view/HEAD/img/showcase.png -------------------------------------------------------------------------------- /sublime/Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["alt+shift+g"], "command": "toggle_git_diff_view"} 3 | ] -------------------------------------------------------------------------------- /sublime/Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+shift+g"], "command": "toggle_git_diff_view"} 3 | ] -------------------------------------------------------------------------------- /sublime/Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Git Diff View: Toggle", 4 | "command": "toggle_git_diff_view" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "install": "messages/install.txt", 3 | "0.4.0": "messages/0.4.0.txt", 4 | "0.5.0": "messages/0.5.0.txt", 5 | "0.6.0": "messages/0.6.0.txt" 6 | } 7 | -------------------------------------------------------------------------------- /syntax/GitUntracked.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: Git Untracked 4 | hidden: true 5 | file_extensions: 6 | - guf # stands for Git Untracked Format 7 | scope: text.guf # Used to show files in Git Diff View that are untracked 8 | 9 | contexts: 10 | main: 11 | - match: .* 12 | scope: markup.untracked -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | # check_untyped_defs = True 4 | disallow_untyped_defs = True 5 | strict_optional = True 6 | mypy_path = stubs 7 | 8 | [mypy-tests] 9 | check_untyped_defs = True 10 | disallow_untyped_defs = False 11 | 12 | [mypy-third_party.*] 13 | ignore_errors = True 14 | ignore_missing_imports = True 15 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | Thanks you for installing GitDiffView! 2 | 3 | ### Instructions 4 | 5 | The default keybinding for toggling the view is `ctrl+shift+g`(Linux) or `alt+shift+g`(Mac). 6 | The git view won't open if there are no git changes. 7 | 8 | Need more info or have some issues... go here 9 | > https://github.com/predragnikolic/sublime-git-diff-view 10 | -------------------------------------------------------------------------------- /messages/0.5.0.txt: -------------------------------------------------------------------------------- 1 | => 0.5.0 2 | 3 | ## What's Changed 4 | * Introduce `highlight_file_names` setting by @lumnn. 5 | * Improve diff view of modified files by splitting diffs of partially staged files into 2 sections - Staged and Unstaged by @lumnn. 6 | * Hide sidebar and panel when Diff View is open by @lumnn 7 | 8 | **Full Changelog**: https://github.com/predragnikolic/sublime-git-diff-view/compare/0.4.4...0.5.0 9 | -------------------------------------------------------------------------------- /messages/0.6.0.txt: -------------------------------------------------------------------------------- 1 | => 0.6.0 2 | 3 | 4 | 5 | ## BREAKING CHANGES 6 | Sublime Text 3 is no longer supported. 7 | 8 | ## What's Changed 9 | - Settings are removed. 10 | - New UI for staging files 11 | - Char diff highlights 12 | - Improvement to the logic responsible to restore old window layout. 13 | 14 | ## Development Changes 15 | - Almost a complete rewrite of the plugin. 16 | - Switched to Python 3.8. 17 | - Added `mypy` and `flake8`. -------------------------------------------------------------------------------- /messages/0.4.0.txt: -------------------------------------------------------------------------------- 1 | => 0.4.0 2 | 3 | 📦 NEW: 4 | * Git status view updates when you stage/unstage/commit through the terminal, command palette, or anywhere. 5 | 6 | ![showcase](https://user-images.githubusercontent.com/22029477/53237438-fd0d1c80-3696-11e9-9053-062fa2c151cf.gif) 7 | 8 | 9 | 🐛 FIX: 10 | * No scroll jump when staging|unstaging 11 | 12 | Source https://github.com/predragnikolic/sublime-git-diff-view/releases/tag/0.4.0. 13 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py3 8 | skipsdist = True 9 | 10 | [flake8] 11 | exclude = third_party 12 | max-line-length = 120 13 | 14 | [testenv] 15 | deps = 16 | mypy==0.910 17 | commands = 18 | mypy . 19 | -------------------------------------------------------------------------------- /core/diff_view.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | import sublime 3 | 4 | 5 | DIFF_VIEW_NAME = "Diff View" 6 | 7 | 8 | def get_diff_view(views: List[sublime.View]) -> Optional[sublime.View]: 9 | ''' Return the diff View ''' 10 | for view in views: 11 | if view.name() == DIFF_VIEW_NAME: 12 | return view 13 | return None 14 | 15 | 16 | def create_diff_view(window: sublime.Window) -> sublime.View: 17 | view = window.new_file() 18 | view.set_name(DIFF_VIEW_NAME) 19 | view.settings().set("line_numbers", False) 20 | view.set_scratch(True) 21 | return view 22 | -------------------------------------------------------------------------------- /sublime/Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "preferences", 4 | "children": [ 5 | { 6 | "caption": "Package Settings", 7 | "mnemonic": "P", 8 | "id": "package-settings", 9 | "children": [ 10 | { 11 | "caption": "Git Diff View", 12 | "children": [ 13 | { 14 | "caption": "Key Bindings", 15 | "command": "edit_settings", 16 | "args": { 17 | "base_file": "${packages}/GitDiffView/sublime/Default (${platform}).sublime-keymap", 18 | "user_file": "${packages}/User/Default (${platform}).sublime-keymap" 19 | } 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | ] -------------------------------------------------------------------------------- /syntax/GitStatus.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | name: Git Status 4 | hidden: true 5 | file_extensions: 6 | - gsf # stands for Git Removed Format 7 | scope: text.gsf # Used to add scopes to the Git Status View 8 | 9 | contexts: 10 | main: 11 | - match: '[^ ]\s+\b(M|MM)\b' 12 | scope: markup.changed 13 | 14 | - match: '[^ ]\s+\b(UU)\b' 15 | scope: markup.changed 16 | 17 | - match: '[^ ]\s+\b(A)\b' 18 | scope: markup.inserted 19 | 20 | - match: '[^ ]\s+\b(D)\b' 21 | scope: markup.deleted 22 | 23 | - match: '[^ ]\s+\b(R)\b' 24 | scope: markup.changed 25 | 26 | - match: '[^ ]\s+\b(C)\b' 27 | scope: markup.changed 28 | 29 | - match: (\?\?) 30 | scope: markup.untracked 31 | 32 | - match: '(\[)(\w+)(\])' 33 | captures: 34 | 2: keyword.control -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Optional 3 | import sublime 4 | 5 | 6 | def get_line(view: sublime.View) -> Optional[int]: 7 | point = get_point(view) 8 | if point is None: 9 | return None 10 | return view.rowcol(view.line(point).begin())[0] 11 | 12 | 13 | def get_line_at_point(view: sublime.View, point: int) -> int: 14 | return view.rowcol(view.line(point).begin())[0] 15 | 16 | def get_selected_lines(view: sublime.View) -> list[int]: 17 | lines = [] 18 | for r in view.sel(): 19 | start_line = get_line_at_point(view, r.begin()) 20 | end_line = get_line_at_point(view, r.end()) +1 21 | lines.extend([*range(start_line, end_line)]) 22 | return lines 23 | 24 | def get_point(view: sublime.View) -> Optional[int]: 25 | sel = view.sel() 26 | if not sel: 27 | return None 28 | return sel[0].b 29 | -------------------------------------------------------------------------------- /core/layout.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | 4 | def two_columns(window: sublime.Window) -> None: 5 | ''' Set two column layout. ''' 6 | grid = { 7 | "cols": [0.0, 0.3, 1.0], 8 | "rows": [0.0, 1.0], 9 | "cells": [[0, 0, 1, 1], [1, 0, 2, 1]] 10 | } 11 | window.run_command('set_layout', grid) 12 | 13 | def insert_into_first_column(window: sublime.Window, view: sublime.View) -> None: 14 | ''' Insert into first column a view. ''' 15 | insert_into_column(window, 0, view) 16 | 17 | 18 | def insert_into_second_column(window: sublime.Window, view: sublime.View) -> None: 19 | ''' Insert into second column a view. ''' 20 | insert_into_column(window, 1, view) 21 | 22 | 23 | def insert_into_column(window: sublime.Window, column: int, view: sublime.View) -> None: 24 | ''' Insert into a given column a view. 25 | Where column index starts at `0`. ''' 26 | window.set_view_index(view, column, 0) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Предраг Николић 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 | -------------------------------------------------------------------------------- /core/status_view.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | import sublime 3 | 4 | STATUS_VIEW_NAME = "Git Status" 5 | prev_formatted_git_status = '' 6 | 7 | 8 | def get_status_view(views: List[sublime.View]) -> Optional[sublime.View]: 9 | ''' Return the git status View ''' 10 | for view in views: 11 | if view.name() == STATUS_VIEW_NAME: 12 | return view 13 | return None 14 | 15 | 16 | def create_status_view(window: sublime.Window) -> sublime.View: 17 | view = window.new_file() 18 | # configure view 19 | default_syntax = "Packages/GitDiffView/syntax/GitStatus.sublime-syntax" 20 | syntax = default_syntax 21 | view.set_syntax_file(syntax) 22 | view.settings().set('highlight_line', False) 23 | view.settings().set("line_numbers", False) 24 | view.settings().set("scroll_past_end", False) 25 | view.settings().set("draw_centered", False) 26 | view.settings().set("tab_size", 4) 27 | view.settings().set("show_minimap", False) 28 | view.settings().set("word_wrap", False) 29 | view.settings().set("draw_indent_guides", False) 30 | view.set_name(STATUS_VIEW_NAME) 31 | view.set_scratch(True) 32 | # disable editing of the view 33 | view.set_read_only(True) 34 | return view 35 | -------------------------------------------------------------------------------- /command_stage_unstage_file.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from .core.git_commands import Git 3 | from .core.git_diff_view import GitDiffView 4 | from .utils import get_selected_lines, get_line 5 | import sublime_plugin 6 | from .command_update_diff_view import update_diff_view 7 | 8 | # command: git_diff_view_stage_unstage 9 | class GitDiffViewStageUnstageCommand(sublime_plugin.TextCommand): 10 | def run(self, _: sublime.Edit) -> None: 11 | window = self.view.window() 12 | if not window: 13 | return 14 | selected_lines = get_selected_lines(self.view) 15 | line = get_line(self.view) 16 | if line is None: 17 | return 18 | git = Git(window) 19 | git_statuses = GitDiffView.git_statuses[window.id()] 20 | git_status = git_statuses[line] 21 | if not git_status: 22 | return 23 | 24 | file_names = [git_statuses[line]['file_name'] for line in selected_lines] 25 | if git_status["is_staged"]: 26 | git.unstage_files(file_names) 27 | else: 28 | git.stage_files(file_names) 29 | git_statuses = git.git_statuses() 30 | self.view.run_command('update_status_view', { 31 | 'git_statuses': git_statuses, 32 | }) 33 | update_diff_view(self.view, git_status) 34 | -------------------------------------------------------------------------------- /command_goto_file.py: -------------------------------------------------------------------------------- 1 | from .core.git_commands import Git 2 | from .core.git_diff_view import GitDiffView 3 | from .utils import get_line 4 | from os import path 5 | import sublime 6 | import sublime_plugin 7 | 8 | 9 | # command: git_diff_view_goto_file 10 | class GitDiffViewGotoFileCommand(sublime_plugin.TextCommand): 11 | def run(self, _: sublime.Edit) -> None: 12 | window = self.view.window() 13 | if not window: 14 | return 15 | line = get_line(self.view) 16 | if line is None: 17 | return 18 | git = Git(window) 19 | git_statuses = GitDiffView.git_statuses[window.id()] 20 | git_status = git_statuses[line] 21 | if not git_status: 22 | return 23 | if 'D' in git_status["modification_type"]: 24 | window.status_message("GitDiffVIew: Can't go to a deleted file") 25 | return 26 | file_name = git_status["file_name"] 27 | if not git.git_root_dir or not file_name: 28 | return 29 | absolute_path_to_file = path.join(git.git_root_dir, 30 | git_status["file_name"]) 31 | window.run_command('toggle_git_diff_view') 32 | view = window.open_file(absolute_path_to_file) 33 | 34 | def deffer_focus_view() -> None: 35 | if window: 36 | window.focus_view(view) 37 | 38 | sublime.set_timeout(deffer_focus_view) 39 | -------------------------------------------------------------------------------- /command_dismiss_changes.py: -------------------------------------------------------------------------------- 1 | from .command_update_diff_view import update_diff_view 2 | from .core.git_commands import Git 3 | from .core.git_diff_view import GitDiffView 4 | from .utils import get_line 5 | import sublime 6 | import sublime_plugin 7 | 8 | 9 | # command: git_diff_view_dismiss_changes 10 | class GitDiffViewDismissChangesCommand(sublime_plugin.TextCommand): 11 | def run(self, _: sublime.Edit) -> None: 12 | window = self.view.window() 13 | if not window: 14 | return 15 | line = get_line(self.view) 16 | if line is None: 17 | return 18 | git = Git(window) 19 | git_statuses = GitDiffView.git_statuses[window.id()] 20 | git_status = git_statuses[line] 21 | if not git_status: 22 | return 23 | 24 | def done(option: int) -> None: 25 | if option == -1: 26 | return 27 | # 0 -> Discard changes 28 | if git_status["is_staged"]: 29 | git.unstage_files([git_status["file_name"]]) 30 | if git_status["modification_type"] == '??': 31 | git.clean(git_status["file_name"]) 32 | else: 33 | git.checkout(git_status["file_name"]) 34 | git_statuses = git.git_statuses() 35 | self.view.run_command('update_status_view', { 36 | 'git_statuses': git_statuses, 37 | }) 38 | try: 39 | new_git_status = git_statuses[line] 40 | update_diff_view(self.view, new_git_status) 41 | except: 42 | update_diff_view(self.view, None) 43 | 44 | self.view.show_popup_menu([ 45 | 'Confirm Discard' 46 | ], done) 47 | window.focus_view(self.view) 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Diff View 2 | 3 | Get a quick overview of changes before committing them. 4 | 5 | ### Features 6 | 7 | - Show modified files (AKA "Status View") 8 | - View diff for a file. (AKA "Diff View") 9 | - Stage/Unstage files/hunks 10 | - Discard changes to files/hunks 11 | - Goto a file 12 | 13 | ![Example](img/showcase.png) 14 | 15 | ### Getting Started 16 | 17 | Open the command palette and run `Package Control: Install Package`, then select `GitDiffView`. 18 | 19 | Toggle the git diff view with `ctrl+shift+g`(Linux) or `alt+shift+g`(Mac) or via the command palette by selecting: `Git Diff View: Toggle`. 20 | The git diff view won't open if there are no git changes. 21 | 22 | 23 | ### Keybindings in Status View (the right view) 24 | 25 | - a / space - stage/unstage file 26 | - d / backspace - dismiss file changes 27 | - g - open file 28 | 29 | 30 | ### Keybindings in Diff View (the left view) 31 | 32 | - a / space - stage/unstage hunk 33 | - d / backspace - dismiss hunk change 34 | 35 | Type of modification will be shown in the git status, next to the file name. 36 | Here is a list of the types: 37 | 38 | ``` 39 | "??" - Untracked 40 | " A" - Added 41 | "AM" - Added and Staged 42 | " M" - Modified 43 | "MM" - Modified and Staged 44 | " D" - Deleted 45 | " R" - Renamed 46 | " C" - Copied 47 | "UU" - Unmerged(Conflict) 48 | ``` 49 | 50 | ### Workflow Example 51 | 52 | [HowToUseIt.webm](https://github.com/predragnikolic/sublime-git-diff-view/assets/22029477/3af9654c-664c-4d0c-94bf-faa6af804e5c) 53 | 54 | > NOTE: 55 | For other commands like `git commit`, `git push`, `git pull`, use a different plugin like [Git](https://github.com/kemayo/sublime-text-git) or [GitSavvy](https://github.com/divmain/GitSavvy). 56 | -------------------------------------------------------------------------------- /sublime/Default.sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["a"], 4 | "command": "git_diff_view_stage_unstage", 5 | "context": [{"key": "selector", "operator": "equal", "operand": "text.gsf"}] 6 | }, 7 | { 8 | "keys": [" "], 9 | "command": "git_diff_view_stage_unstage", 10 | "context": [{"key": "selector", "operator": "equal", "operand": "text.gsf"}] 11 | }, 12 | { 13 | "keys": ["a"], 14 | "command": "git_diff_view_stage_unstage_hunk", 15 | "context": [{"key": "selector", "operator": "equal", "operand": "source.diff"}] 16 | }, 17 | { 18 | "keys": [" "], 19 | "command": "git_diff_view_stage_unstage_hunk", 20 | "context": [{"key": "selector", "operator": "equal", "operand": "source.diff"}] 21 | }, 22 | { 23 | "keys": ["d"], 24 | "command": "git_diff_view_dismiss_changes", 25 | "context": [{"key": "selector", "operator": "equal", "operand": "text.gsf"}] 26 | }, 27 | { 28 | "keys": ["backspace"], 29 | "command": "git_diff_view_dismiss_changes", 30 | "context": [{"key": "selector", "operator": "equal", "operand": "text.gsf"}] 31 | }, 32 | { 33 | "keys": ["d"], 34 | "command": "git_diff_view_dismiss_hunk_changes", 35 | "context": [{"key": "selector", "operator": "equal", "operand": "source.diff"}] 36 | }, 37 | { 38 | "keys": ["backspace"], 39 | "command": "git_diff_view_dismiss_hunk_changes", 40 | "context": [{"key": "selector", "operator": "equal", "operand": "source.diff"}] 41 | }, 42 | { 43 | "keys": ["g"], 44 | "command": "git_diff_view_goto_file", 45 | "context": [{"key": "selector", "operator": "equal", "operand": "text.gsf"}] 46 | }, 47 | { 48 | "keys": ["g"], 49 | "command": "git_diff_view_goto_hunk", 50 | "context": [{"key": "selector", "operator": "equal", "operand": "source.diff"}] 51 | } 52 | ] 53 | -------------------------------------------------------------------------------- /command_goto_hunk.py: -------------------------------------------------------------------------------- 1 | from .core.status_view import get_status_view 2 | from .core.git_commands import Git 3 | from .core.git_diff_view import GitDiffView 4 | from .utils import get_line 5 | from os import path 6 | import sublime 7 | import sublime_plugin 8 | 9 | 10 | # command: git_diff_view_goto_file 11 | class GitDiffViewGotoHunkCommand(sublime_plugin.TextCommand): 12 | def run(self, _: sublime.Edit) -> None: 13 | diff_view = self.view 14 | window = self.view.window() 15 | if not window: 16 | return 17 | status_view = get_status_view(window.views()) 18 | if not status_view: 19 | return 20 | line = get_line(status_view) 21 | if line is None: 22 | return 23 | git = Git(window) 24 | git_statuses = GitDiffView.git_statuses[window.id()] 25 | git_status = git_statuses[line] 26 | if not git_status: 27 | return 28 | if 'D' in git_status["modification_type"]: 29 | window.status_message("GitDiffVIew: Can't go to a deleted file") 30 | return 31 | file_name = git_status["file_name"] 32 | if not git.git_root_dir or not file_name: 33 | return 34 | absolute_path_to_file = path.join(git.git_root_dir, 35 | git_status["file_name"]) 36 | sel = diff_view.sel() 37 | if not sel: 38 | return 39 | cursor = sel[0].b 40 | start_patch = diff_view.find('^@@.+@@', cursor, sublime.REVERSE) 41 | row = 0 42 | if start_patch: 43 | header = diff_view.substr(start_patch).split(' ') # ['@@', '-23,5', '+23,10', '@@'] 44 | row = int(header[2].replace('-', '').replace('+', '').split(',')[0]) # row = 23 45 | window.run_command('toggle_git_diff_view') 46 | 47 | def deffer_goto_file(): 48 | if window.is_valid(): 49 | view = window.open_file(f"{absolute_path_to_file}:{row}", sublime.ENCODED_POSITION) 50 | window.focus_view(view) 51 | 52 | sublime.set_timeout(deffer_goto_file, 50) 53 | -------------------------------------------------------------------------------- /core/git_diff_view.py: -------------------------------------------------------------------------------- 1 | from ..command_update_diff_view import update_diff_view 2 | from .view_manager import ViewsManager 3 | from .layout import insert_into_first_column, insert_into_second_column, two_columns 4 | from .diff_view import create_diff_view, DIFF_VIEW_NAME 5 | from .git_commands import GitStatus 6 | from .status_view import STATUS_VIEW_NAME, create_status_view 7 | from typing import Dict, List 8 | import sublime 9 | 10 | 11 | WindowId = int 12 | 13 | 14 | class GitDiffView: 15 | ''' Shows the git status and git diff. ''' 16 | git_statuses: Dict[WindowId, List[GitStatus]] = {} 17 | ''' stores the last result of the `git.git_statuses()` call for fast reads''' 18 | 19 | def __init__(self, window: sublime.Window) -> None: 20 | self.window: sublime.Window = window 21 | 22 | def close(self) -> None: 23 | ''' Closes the git status view and git diff view. ''' 24 | for view in self.window.views(): 25 | if view.name() in [STATUS_VIEW_NAME, DIFF_VIEW_NAME]: 26 | view.close() 27 | 28 | def open(self) -> None: 29 | ''' Opens the git status view, and git diff view. ''' 30 | git_statuses = GitDiffView.git_statuses[self.window.id()] 31 | status_view = create_status_view(self.window) 32 | status_view.run_command('update_status_view', { 33 | 'git_statuses': git_statuses, 34 | }) 35 | two_columns(self.window) 36 | insert_into_first_column(self.window, status_view) 37 | diff_view = create_diff_view(self.window) 38 | insert_into_second_column(self.window, diff_view) 39 | # select first line, Status View 40 | cursor_position = ViewsManager.status_view_last_position.get(self.window.id(), 0) 41 | sel = status_view.sel() 42 | sel.clear() 43 | sel.add(cursor_position) 44 | status_view.show(cursor_position) 45 | line_index, _ = status_view.rowcol(cursor_position) 46 | # display first line , Git View 47 | git_status = git_statuses[line_index] 48 | if not git_status: 49 | return 50 | update_diff_view(diff_view, git_status) 51 | self.window.focus_view(status_view) 52 | -------------------------------------------------------------------------------- /command_stage_unstage_hunk.py: -------------------------------------------------------------------------------- 1 | from .command_update_diff_view import update_diff_view 2 | import sublime 3 | from .core.git_commands import Git 4 | from .core.status_view import get_status_view 5 | from .core.git_diff_view import GitDiffView 6 | from .utils import get_line 7 | import sublime_plugin 8 | import os 9 | 10 | 11 | # command: git_diff_view_stage_unstage_hunk 12 | class GitDiffViewStageUnstageHunkCommand(sublime_plugin.TextCommand): 13 | def run(self, _: sublime.Edit) -> None: 14 | window = self.view.window() 15 | if not window: 16 | return 17 | diff_view = self.view 18 | sel = diff_view.sel() 19 | if not sel: 20 | return 21 | cursor = sel[0].b 22 | status_view = get_status_view(window.views()) 23 | if not status_view: 24 | return 25 | line = get_line(status_view) 26 | if line is None: 27 | return 28 | git = Git(window) 29 | git_statuses = GitDiffView.git_statuses[window.id()] 30 | git_status = git_statuses[line] 31 | if not git_status: 32 | return 33 | head = git.diff_head(git_status['file_name']) 34 | 35 | start_patch = diff_view.find('^@@', cursor, sublime.REVERSE) 36 | if not start_patch: 37 | return 38 | 39 | end_patch = diff_view.find('^@@', cursor) 40 | if not end_patch: 41 | end_patch = sublime.Region(diff_view.size()) 42 | hunk_content = diff_view.substr(sublime.Region(start_patch.begin(), end_patch.begin())) 43 | patch_content = f"{head}\n{hunk_content}" 44 | 45 | not_staged = not git_status['is_staged'] or diff_view.find('^Unstaged', cursor, sublime.REVERSE) 46 | temp_patch_file = os.path.join(sublime.cache_path(), 'temp_patch_file.patch') 47 | patch_file = open(temp_patch_file, 'w', encoding='utf-8') 48 | try: 49 | patch_file.write(patch_content) 50 | patch_file.close() 51 | if not_staged: 52 | git.stage_patch(temp_patch_file) 53 | else: 54 | git.unstage_patch(temp_patch_file) 55 | finally: 56 | patch_file.close() 57 | os.remove(patch_file.name) 58 | 59 | new_git_statuses = git.git_statuses() 60 | new_git_status = new_git_statuses[line] 61 | status_view.run_command('update_status_view', { 62 | 'git_statuses': new_git_statuses, 63 | }) 64 | update_diff_view(self.view, new_git_status) 65 | -------------------------------------------------------------------------------- /command_dismiss_hunk.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .command_update_diff_view import update_diff_view 4 | from .core.status_view import get_status_view 5 | from .core.git_commands import Git 6 | from .core.git_diff_view import GitDiffView 7 | from .utils import get_line 8 | import sublime 9 | import sublime_plugin 10 | 11 | 12 | # command: git_diff_view_dismiss_hunk_changes 13 | class GitDiffViewDismissHunkChangesCommand(sublime_plugin.TextCommand): 14 | def run(self, _: sublime.Edit) -> None: 15 | def done(option: int) -> None: 16 | if option == -1: 17 | return 18 | window = self.view.window() 19 | if not window: 20 | return 21 | diff_view = self.view 22 | sel = diff_view.sel() 23 | if not sel: 24 | return 25 | cursor = sel[0].b 26 | status_view = get_status_view(window.views()) 27 | if not status_view: 28 | return 29 | line = get_line(status_view) 30 | if line is None: 31 | return 32 | git = Git(window) 33 | git_statuses = GitDiffView.git_statuses[window.id()] 34 | git_status = git_statuses[line] 35 | if not git_status: 36 | return 37 | head = git.diff_head(git_status['file_name']) 38 | 39 | start_patch = diff_view.find('^@@', cursor, sublime.REVERSE) 40 | if not start_patch: 41 | return 42 | 43 | end_patch = diff_view.find('^@@', cursor) 44 | if not end_patch: 45 | end_patch = sublime.Region(diff_view.size()) 46 | hunk_content = diff_view.substr(sublime.Region(start_patch.begin(), end_patch.begin())) 47 | patch_content = f"{head}\n{hunk_content}" 48 | 49 | not_staged = not git_status['is_staged'] or diff_view.find('^Unstaged', cursor, sublime.REVERSE) 50 | temp_patch_file = os.path.join(sublime.cache_path(), 'temp_patch_file.patch') 51 | patch_file = open(temp_patch_file, 'w', encoding='utf-8') 52 | try: 53 | patch_file.write(patch_content) 54 | patch_file.close() 55 | if not_staged: 56 | git.discard_patch(temp_patch_file) 57 | else: 58 | # for staged files first unstage patch 59 | git.unstage_patch(temp_patch_file) 60 | git.discard_patch(temp_patch_file) 61 | finally: 62 | patch_file.close() 63 | os.remove(patch_file.name) 64 | 65 | new_git_statuses = git.git_statuses() 66 | status_view.run_command('update_status_view', { 67 | 'git_statuses': new_git_statuses, 68 | }) 69 | try: 70 | new_git_status = new_git_statuses[line] 71 | update_diff_view(self.view, new_git_status) 72 | except: 73 | update_diff_view(self.view, None) 74 | 75 | self.view.show_popup_menu([ 76 | 'Discard Hunk' 77 | ], done) 78 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Callable 3 | 4 | from .command_update_diff_view import update_diff_view 5 | from .core.diff_view import DIFF_VIEW_NAME 6 | from .core.git_commands import Git 7 | from .core.git_diff_view import GitDiffView 8 | from .core.status_view import get_status_view, STATUS_VIEW_NAME 9 | from .core.view_manager import SESSION_DIR, ViewsManager 10 | from .utils import get_line, get_point 11 | import sublime 12 | import sublime_plugin 13 | import os 14 | import re 15 | 16 | 17 | STOP_INTERVAL = False 18 | def set_interval(fn: Callable) -> None: 19 | def interval() -> None: 20 | fn() 21 | if not STOP_INTERVAL: 22 | sublime.set_timeout(interval, 2000) 23 | sublime.set_timeout(interval, 2000) 24 | 25 | 26 | def refresh_list() -> None: 27 | ''' Refresh git status view content''' 28 | window = sublime.active_window() 29 | view = get_status_view(window.views()) 30 | if view is None: 31 | return 32 | git_statuses = Git(window).git_statuses() 33 | view.run_command('update_status_view', { 34 | 'git_statuses': git_statuses, 35 | }) 36 | 37 | 38 | def plugin_loaded() -> None: 39 | # create session dir where the session file will be stored 40 | if not os.path.exists(SESSION_DIR): 41 | os.makedirs(SESSION_DIR) 42 | status_view = get_status_view(sublime.active_window().views()) 43 | # If status view is open when sublime starts, 44 | if status_view is not None: 45 | set_interval(refresh_list) 46 | 47 | 48 | # command: close_git_diff_view 49 | class CloseGitDiffViewCommand(sublime_plugin.TextCommand): 50 | def run(self, _: sublime.Edit) -> None: 51 | global STOP_INTERVAL 52 | window = self.view.window() 53 | if not window: 54 | return 55 | git_diff_view = GitDiffView(window) 56 | git_diff_view.close() 57 | 58 | git = Git(window) 59 | views_manager = ViewsManager(window, git.git_root_dir or "") 60 | views_manager.restore() 61 | 62 | STOP_INTERVAL = True 63 | 64 | 65 | # command: open_git_diff_view 66 | class OpenGitDiffViewCommand(sublime_plugin.TextCommand): 67 | def run(self, _: sublime.Edit) -> None: 68 | global STOP_INTERVAL 69 | window = self.view.window() 70 | if not window: 71 | return 72 | Git.reset_command_cache() 73 | git = Git(window) 74 | # open GitView 75 | # array of dict that holds information about 76 | # the file, type of modification, and if the file is staged 77 | git_statuses = git.git_statuses() 78 | if not git_statuses: 79 | window.status_message('No git changes to show.') 80 | return 81 | views_manager = ViewsManager(window, git.git_root_dir or "") 82 | views_manager.prepare() 83 | 84 | git_diff_view = GitDiffView(window) 85 | git_diff_view.open() 86 | 87 | STOP_INTERVAL = False 88 | set_interval(refresh_list) 89 | 90 | 91 | # command: toggle_git_diff_view 92 | class ToggleGitDiffViewCommand(sublime_plugin.TextCommand): 93 | def run(self, _: sublime.Edit) -> None: 94 | global STOP_INTERVAL 95 | window = self.view.window() 96 | if not window: 97 | return 98 | if ViewsManager.is_git_view_open(window.views()): 99 | self.view.run_command('close_git_diff_view') 100 | else: 101 | self.view.run_command('open_git_diff_view') 102 | 103 | 104 | class SelectionChangedEvent(sublime_plugin.EventListener): 105 | def on_pre_close(self, view: sublime.View) -> None: 106 | window = view.window() 107 | if not window: 108 | return 109 | if not ViewsManager.is_git_view_open(window.views()): 110 | return 111 | if view.name() in [STATUS_VIEW_NAME, DIFF_VIEW_NAME]: 112 | point = get_point(view) 113 | if view.name() == STATUS_VIEW_NAME and point: 114 | ViewsManager.status_view_last_position[window.id()] = point 115 | 116 | def deffer_close_diff_view() -> None: 117 | if window: 118 | window.run_command('close_git_diff_view') 119 | 120 | sublime.set_timeout(deffer_close_diff_view) 121 | 122 | def on_selection_modified(self, view: sublime.View) -> None: 123 | if view.name() != STATUS_VIEW_NAME: 124 | return 125 | window = view.window() 126 | if not window: 127 | return 128 | status_view = view 129 | line = get_line(status_view) 130 | if line is None: 131 | return 132 | _, y =status_view.viewport_position() 133 | status_view.set_viewport_position((0, y)) # make sure that the labels are always visible 134 | git_statuses = GitDiffView.git_statuses[window.id()] 135 | try: 136 | git_status = git_statuses[line] 137 | update_diff_view(view, git_status) 138 | except Exception: 139 | update_diff_view(view, None) 140 | 141 | -------------------------------------------------------------------------------- /stubs/sublime_plugin.pyi: -------------------------------------------------------------------------------- 1 | # Stubs for sublime_plugin (Python 3.5) 2 | # 3 | # NOTE: This dynamically typed stub was automatically generated by stubgen. 4 | from sublime import View, Window, Edit, Buffer 5 | from typing import Any, Optional, Dict, List 6 | assert Optional 7 | 8 | # api_ready = ... # type: bool 9 | # application_command_classes = ... # type: Any 10 | # window_command_classes = ... # type: Any 11 | # text_command_classes = ... # type: Any 12 | # view_event_listener_classes = ... # type: Any 13 | view_event_listeners = ... # type: Dict[int, List[ViewEventListener]] 14 | # all_command_classes = ... # type: Any 15 | # all_callbacks = ... # type: Any 16 | # profile = ... # type: Any 17 | 18 | # def unload_module(module): ... 19 | # def on_api_ready(): ... 20 | # def is_view_event_listener_applicable(cls, view): ... 21 | # def create_view_event_listeners(classes, view): ... 22 | # def check_view_event_listeners(view): ... 23 | # def attach_view(view): ... 24 | 25 | # check_all_view_event_listeners_scheduled = ... # type: bool 26 | 27 | # def check_all_view_event_listeners(): ... 28 | # def detach_view(view): ... 29 | # def event_listeners_for_view(view): ... 30 | # def find_view_event_listener(view, cls): ... 31 | # def on_new(view_id): ... 32 | # def on_new_async(view_id): ... 33 | # def on_clone(view_id): ... 34 | # def on_clone_async(view_id): ... 35 | 36 | # class Summary: 37 | # max = ... # type: float 38 | # sum = ... # type: float 39 | # count = ... # type: int 40 | # def __init__(self) -> None: ... 41 | # def record(self, x): ... 42 | 43 | # def run_callback(event, callback, expr): ... 44 | # def run_view_listener_callback(view, name): ... 45 | # def run_async_view_listener_callback(view, name): ... 46 | # def on_load(view_id): ... 47 | # def on_load_async(view_id): ... 48 | # def on_pre_close(view_id): ... 49 | # def on_close(view_id): ... 50 | # def on_pre_save(view_id): ... 51 | # def on_pre_save_async(view_id): ... 52 | # def on_post_save(view_id): ... 53 | # def on_post_save_async(view_id): ... 54 | # def on_modified(view_id): ... 55 | # def on_modified_async(view_id): ... 56 | # def on_selection_modified(view_id): ... 57 | # def on_selection_modified_async(view_id): ... 58 | # def on_activated(view_id): ... 59 | # def on_activated_async(view_id): ... 60 | # def on_deactivated(view_id): ... 61 | # def on_deactivated_async(view_id): ... 62 | # def on_query_context(view_id, key, operator, operand, match_all): ... 63 | # def normalise_completion(c): ... 64 | # def on_query_completions(view_id, prefix, locations): ... 65 | # def on_hover(view_id, point, hover_zone): ... 66 | # def on_text_command(view_id, name, args): ... 67 | # def on_window_command(window_id, name, args): ... 68 | # def on_post_text_command(view_id, name, args): ... 69 | # def on_post_window_command(window_id, name, args): ... 70 | 71 | 72 | class Command: 73 | def name(self) -> str: 74 | ... 75 | 76 | # def is_enabled_(self, args): ... 77 | def is_enabled(self) -> bool: 78 | ... 79 | 80 | # def is_visible_(self, args): ... 81 | def is_visible(self) -> bool: 82 | ... 83 | 84 | # def is_checked_(self, args): ... 85 | def is_checked(self) -> bool: 86 | ... 87 | 88 | # def description_(self, args): ... 89 | def description(self) -> str: 90 | ... 91 | 92 | # def filter_args(self, args): ... 93 | def want_event(self) -> bool: 94 | ... 95 | 96 | 97 | class ApplicationCommand(Command): 98 | ... 99 | 100 | 101 | class WindowCommand(Command): 102 | window = ... # type: Window 103 | 104 | def __init__(self, window: Window) -> None: 105 | ... 106 | 107 | # def run_(self, edit_token, args): ... 108 | 109 | # def run(self) -> None: (mypy issue #5876). 110 | # ... 111 | 112 | 113 | class TextCommand(Command): 114 | view = ... # type: View 115 | 116 | def __init__(self, view: View) -> None: 117 | ... 118 | 119 | # def run_(self, edit_token, args): ... 120 | # def run(self, edit: Edit) -> None: (mypy issue #5876) 121 | # ... 122 | 123 | 124 | class EventListener: 125 | ... 126 | 127 | 128 | class TextChangeListener: 129 | 130 | buffer = ... # type: Buffer 131 | 132 | @classmethod 133 | def is_applicable(cls, buffer: Buffer) -> bool: 134 | ... 135 | 136 | def __init__(self) -> None: 137 | ... 138 | 139 | def attach(self, buffer: Buffer) -> None: 140 | ... 141 | 142 | def detach(self) -> None: 143 | ... 144 | 145 | def is_attached(self) -> bool: 146 | ... 147 | 148 | 149 | class ViewEventListener: 150 | @classmethod 151 | def is_applicable(cls, settings: dict) -> bool: 152 | ... 153 | 154 | @classmethod 155 | def applies_to_primary_view_only(cls) -> bool: 156 | ... 157 | 158 | view = ... # type: View 159 | 160 | def __init__(self, view: View) -> None: 161 | ... 162 | 163 | 164 | class CommandInputHandler: 165 | ... 166 | 167 | 168 | class TextInputHandler(CommandInputHandler): 169 | ... 170 | 171 | 172 | class ListInputHandler(CommandInputHandler): 173 | ... 174 | -------------------------------------------------------------------------------- /command_update_status_view.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | from .core.git_commands import GitStatus, ModificationType 3 | from .core.status_view import get_status_view 4 | import sublime_plugin 5 | import sublime 6 | 7 | 8 | # command: update_status_view 9 | class UpdateStatusViewCommand(sublime_plugin.TextCommand): 10 | def __init__(self, view: sublime.View) -> None: 11 | super().__init__(view) 12 | self.phantom_set = sublime.PhantomSet(view, "status_view_phantoms") 13 | self.prev_git_statuses: str = '' 14 | 15 | def _is_same(self, git_statuses: List[GitStatus]) -> bool: 16 | return self.prev_git_statuses == str(git_statuses) 17 | 18 | def run(self, edit: sublime.Edit, git_statuses: List[GitStatus]) -> None: 19 | if self._is_same(git_statuses): 20 | return 21 | self.prev_git_statuses = str(git_statuses) 22 | window = self.view.window() 23 | if not window: 24 | return 25 | active_view = window.active_view() 26 | status_view = get_status_view(window.views()) 27 | if status_view is None: 28 | return 29 | files = [] 30 | phantoms: List[sublime.Phantom] = [] 31 | for git_status in git_statuses: 32 | file_name = git_status['file_name'] 33 | if git_status['old_file_name']: 34 | file_name = f"{git_status['old_file_name']} -> {git_status['file_name']}" 35 | files.append(file_name) 36 | new_content = '\n'.join(files) 37 | # update diff view if necessary 38 | if len(git_statuses) < 1: 39 | new_content = "No changes" 40 | # update status view 41 | status_view.set_read_only(False) 42 | status_view.replace(edit, sublime.Region(0, status_view.size()), new_content) 43 | status_view.set_read_only(True) 44 | 45 | style = status_view.style() 46 | changed = status_view.style_for_scope("markup.changed").get('foreground') 47 | inserted = status_view.style_for_scope("markup.inserted").get('foreground') 48 | deleted = status_view.style_for_scope("markup.deleted").get('foreground') 49 | untracked = status_view.style_for_scope("markup.untracked").get('foreground') 50 | comment = status_view.style_for_scope("comment").get('foreground') 51 | renamed = style.get('purplish') 52 | background = style.get('background') 53 | 54 | for i, git_status in enumerate(git_statuses): 55 | point = status_view.text_point(i, 0) 56 | modification_type = git_status['modification_type'] 57 | primary_color = "#ff0000" 58 | if modification_type == 'MM': 59 | primary_color = changed 60 | elif 'A' in modification_type: 61 | primary_color = inserted 62 | elif 'M' in modification_type: 63 | primary_color = changed 64 | elif 'U' in modification_type: 65 | primary_color = changed 66 | elif 'R' in modification_type: 67 | primary_color = renamed 68 | elif 'C' in modification_type: 69 | primary_color = changed 70 | elif '?' in modification_type: 71 | primary_color = untracked 72 | elif 'D' in modification_type: 73 | primary_color = deleted 74 | 75 | is_staged = git_status['is_staged'] 76 | unstaged_styles = f"color: {primary_color}; border: 1px solid {primary_color};" 77 | staged_styles = f"color: {background}; background-color: {primary_color}; border: 1px solid {primary_color};" 78 | styles = staged_styles if is_staged else unstaged_styles 79 | 80 | readable_modification_type = modification_type_to_readable(modification_type) 81 | title = f'{readable_modification_type} and STAGED' if is_staged else f'{readable_modification_type} and UNSTAGED' 82 | phantom = sublime.Phantom(sublime.Region(point), f'''
83 |
{git_status['modification_type'].strip()}
84 |
''', sublime.LAYOUT_INLINE) 85 | help_phantom = sublime.Phantom(sublime.Region(status_view.size(), status_view.size()), f'''
86 |
a - stage/unstage a file
87 |
d - discard changes to a file
88 |
g - goto a file
89 |
''', sublime.LAYOUT_BLOCK) 90 | phantoms.append(help_phantom) 91 | 92 | phantoms.append(phantom) 93 | self.phantom_set.update(phantoms) 94 | if active_view: 95 | window.focus_view(active_view) 96 | 97 | 98 | def modification_type_to_readable(modification_type: ModificationType) -> str: 99 | title_dict: Dict[ModificationType, str] = { 100 | "??": "File is UNTRACKED", 101 | " A": "File is ADDED", 102 | "AM": "File is ADDED", 103 | " M": "File is MODIFIED", 104 | "MM": "File is MODIFIED", 105 | " D": "File is Deleted", 106 | " R": "File is Renamed", 107 | " C": "File is Copied", 108 | "UU": "File has Conflict/s" 109 | } 110 | return title_dict.get(modification_type, modification_type) 111 | -------------------------------------------------------------------------------- /core/view_manager.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from .diff_view import DIFF_VIEW_NAME 4 | from .status_view import STATUS_VIEW_NAME 5 | from typing import Any, Dict, List, Optional 6 | import sublime 7 | import json 8 | from pathlib import Path 9 | 10 | SESSION_DIR = path.join(sublime.cache_path(), 'GitDiffView') 11 | 12 | WindowId = int 13 | FileName = str 14 | Layout = Dict[str, Any] 15 | 16 | RootDirPath = str 17 | 18 | class ViewsManager: 19 | ''' Responsible for storing views and reopening them later. ''' 20 | status_view_last_position: Dict[WindowId, int] = {} 21 | 22 | def __init__(self, window: sublime.Window, root_dir: RootDirPath) -> None: 23 | self.window: sublime.Window = window 24 | self.root_dir = root_dir 25 | self.session_file_path = path.join(SESSION_DIR, 'session.json') 26 | self.previous_file_names: List[FileName] = [] 27 | self.last_active_file_name: Optional[FileName] = None 28 | self.last_cursor_pos: Optional[int] = None 29 | self.last_sidebar_state: Optional[bool] = None 30 | self.last_active_panel: Optional[str] = None 31 | self.last_layout: Optional[Layout] = None 32 | self.view_to_group: Dict[FileName, int] = {} 33 | # create session file if not exists 34 | if not path.exists(self.session_file_path): 35 | file_path = Path(self.session_file_path) 36 | file_path.touch() 37 | file = open(file_path, "w+") 38 | file.write(json.dumps({})) 39 | file.close() 40 | 41 | @staticmethod 42 | def is_git_view_open(views: List[sublime.View]) -> bool: 43 | for view in views: 44 | if view.name() in [STATUS_VIEW_NAME, DIFF_VIEW_NAME]: 45 | return True 46 | return False 47 | 48 | def prepare(self) -> None: 49 | # save layout 50 | self.last_layout = self.window.layout() 51 | self.save_views_for_later() 52 | self.window.set_sidebar_visible(False) 53 | self.window.run_command('hide_panel') 54 | 55 | self.save_to_session_file() 56 | 57 | def load_session_file(self) -> None: 58 | if not path.exists(self.session_file_path): 59 | return 60 | saved_file = open(self.session_file_path, "r") 61 | dict = json.loads(saved_file.read()) or {} 62 | saved_file.close() 63 | saved_settings = dict.pop(self.root_dir, {}) 64 | self.previous_file_names = saved_settings.get('previous_file_names', []) 65 | self.last_active_file_name = saved_settings.get('last_active_file_name', None) 66 | self.last_cursor_pos = saved_settings.get('last_cursor_pos', None) 67 | self.last_sidebar_state = saved_settings.get('last_sidebar_state', None) 68 | self.last_active_panel = saved_settings.get('last_active_panel', None) 69 | self.last_layout = saved_settings.get('last_layout', None) 70 | self.view_to_group = saved_settings.get('view_to_group', {}) 71 | file_path = Path(self.session_file_path) 72 | file_path.touch(exist_ok=True) 73 | file = open(file_path, "w+") 74 | file.write(json.dumps(dict)) 75 | file.close() 76 | 77 | def save_to_session_file(self) -> None: 78 | saved_file = open(self.session_file_path, "r") 79 | dict = json.loads(saved_file.read()) or {} 80 | saved_file.close() 81 | dict[self.root_dir] = {} 82 | dict[self.root_dir]['previous_file_names'] = self.previous_file_names 83 | dict[self.root_dir]['last_active_file_name'] = self.last_active_file_name 84 | dict[self.root_dir]['last_cursor_pos'] = self.last_cursor_pos 85 | dict[self.root_dir]['last_sidebar_state'] = self.last_sidebar_state 86 | dict[self.root_dir]['last_active_panel'] = self.last_active_panel 87 | dict[self.root_dir]['last_layout'] = self.last_layout 88 | dict[self.root_dir]['view_to_group'] = self.view_to_group 89 | file_path = Path(self.session_file_path) 90 | file_path.touch(exist_ok=True) 91 | file = open(file_path, "w+") 92 | file.write(json.dumps(dict)) 93 | file.close() 94 | 95 | 96 | def restore(self) -> None: 97 | self.load_session_file() 98 | # restore layout 99 | last_layout = self.last_layout 100 | if last_layout: 101 | self.window.set_layout(last_layout) 102 | # restore views 103 | views = self.previous_file_names 104 | for file_name in views: 105 | if file_name: 106 | group = self.view_to_group.get(file_name, -1) 107 | self.window.open_file(file_name, group=group) 108 | # restore last active view 109 | last_active_file_name = self.last_active_file_name 110 | if last_active_file_name is not None: 111 | view = self.window.open_file(last_active_file_name) 112 | 113 | def trying_restoring_the_cursor(view: sublime.View) -> None: 114 | if not view.is_loading(): 115 | self._restore_cursor_pos(view) 116 | else: 117 | # cant put the cursor if the view is not loaded 118 | # so try it again 119 | sublime.set_timeout(lambda: 120 | trying_restoring_the_cursor(view), 20) 121 | 122 | trying_restoring_the_cursor(view) 123 | # restore sidebar 124 | last_sidebar_state = self.last_sidebar_state 125 | if last_sidebar_state is not None: 126 | self.window.set_sidebar_visible(True) 127 | # restore panel 128 | last_active_panel = self.last_active_panel 129 | if last_active_panel: 130 | self.window.run_command("show_panel", { "panel": last_active_panel }) 131 | 132 | def save_views_for_later(self) -> None: 133 | view = self.window.active_view() 134 | if view: 135 | file_name = view.file_name() 136 | # save active view 137 | if file_name: 138 | self.last_active_file_name = file_name 139 | # save cursor 140 | self.last_cursor_pos = view.sel()[0].begin() 141 | # save sidebar 142 | self.last_sidebar_state = self.window.is_sidebar_visible() 143 | # save panel 144 | active_panel = self.window.active_panel() 145 | if active_panel: 146 | self.last_active_panel = active_panel 147 | # save open views 148 | self._save_open_file_names(self.window) 149 | 150 | def _restore_cursor_pos(self, view: sublime.View) -> None: 151 | cursor_pos = self.last_cursor_pos 152 | if not cursor_pos: 153 | return 154 | # put the cursor there 155 | sel = view.sel() 156 | sel.clear() 157 | sel.add(cursor_pos) 158 | 159 | # show the position at center 160 | # if the row is greater then the last visible row 161 | row = view.rowcol(cursor_pos)[0] 162 | last_visible_row = 18 163 | if row > last_visible_row: 164 | view.show_at_center(cursor_pos) 165 | 166 | def _save_open_file_names(self, window: sublime.Window) -> None: 167 | self.previous_file_names = [] 168 | num_groups = window.num_groups() 169 | for group in range(num_groups): 170 | for view in window.views_in_group(group): 171 | file_name = view.file_name() 172 | if file_name: 173 | self.view_to_group[file_name] = group 174 | self.previous_file_names.append(file_name) 175 | view.close() 176 | -------------------------------------------------------------------------------- /command_update_diff_view.py: -------------------------------------------------------------------------------- 1 | from .core.diff_view import get_diff_view 2 | from .core.git_commands import Git, GitStatus, remove_first_lines 3 | from typing import List, Optional 4 | import sublime 5 | import sublime_plugin 6 | import difflib as dl 7 | import re 8 | import os 9 | 10 | 11 | def update_diff_view(view: sublime.View, git_status: Optional[GitStatus]): 12 | window = view.window() 13 | if not window: 14 | return 15 | git = Git(window) 16 | views = window.views() 17 | diff_view = get_diff_view(views) 18 | if not diff_view: 19 | return 20 | if git_status is None: 21 | diff_view.erase_regions('git_diff_view.add_bg') 22 | diff_view.erase_regions('git_diff_view.removed_bg') 23 | diff_view.erase_regions('git_diff_view.add_char') 24 | diff_view.erase_regions('git_diff_view.remove_char') 25 | diff_view.run_command('git_diff_view_update_view', {'content': 'No diff'}) 26 | return 27 | modification_type = git_status['modification_type'] 28 | file_name = git_status['file_name'] 29 | # enable editing the file for editing 30 | diff_output = '' 31 | if 'MM' == modification_type: 32 | diff_view.set_syntax_file('Packages/Diff/Diff.sublime-syntax') 33 | diff_output = ( 34 | "Staged\n" + 35 | "======\n" + 36 | git.diff_file_staged(file_name) + 37 | "Unstaged\n" + 38 | "========\n" + 39 | git.diff_file_unstaged(file_name) 40 | ) 41 | elif 'M' in modification_type: 42 | diff_view.set_syntax_file('Packages/Diff/Diff.sublime-syntax') 43 | diff_output = remove_first_lines(git.diff_file(file_name), 4) 44 | elif 'U' in modification_type: 45 | diff_view.set_syntax_file('Packages/Diff/Diff.sublime-syntax') 46 | diff_output = remove_first_lines(git.diff_file(file_name), 4) 47 | elif 'A' in modification_type: 48 | diff_view.set_syntax_file('Packages/Diff/Diff.sublime-syntax') 49 | diff_output = remove_first_lines(git.diff_file(file_name), 5) 50 | elif 'R' in modification_type: 51 | diff_view.set_syntax_file('Packages/Diff/Diff.sublime-syntax') 52 | diff_output = remove_first_lines(git.diff_file(file_name), 4) 53 | elif 'C' in modification_type: 54 | diff_view.set_syntax_file('Packages/Diff/Diff.sublime-syntax') 55 | diff_output = remove_first_lines(git.diff_file(file_name), 4) 56 | elif '?' in modification_type: 57 | syntax = get_syntax(file_name, view) 58 | diff_view.set_syntax_file(syntax or 'Packages/GitDiffView/syntax/GitUntracked.sublime-syntax') 59 | diff_output = git.show_added_file(file_name) 60 | elif 'D' in modification_type: 61 | diff_view.set_syntax_file('Packages/Diff/Diff.sublime-syntax') 62 | diff_output = remove_first_lines(git.diff_file(file_name), 5) 63 | diff_view.run_command('git_diff_view_update_view', {'content': diff_output}) 64 | 65 | add_diff_highlights(diff_view) 66 | visible_regions = diff_view.visible_region() 67 | 68 | if (visible_regions.end() - visible_regions.begin()) < 10: # when switching from a larger diff to view a smaller diff, the smaller diff will not be visible, so we scroll to top 69 | diff_view.set_viewport_position((0, 0)) 70 | 71 | 72 | def add_diff_highlights(diff_view: sublime.View) -> None: 73 | # st_bug_first_call_to_find_all_will_not_work_correctly = diff_view.find_all('') # haha, one new ST bug :D, without this line, the bellow diff_view.find_all will not work. Looks like the first call to diff_view.find_all will not work correctly. 74 | added_lines = diff_view.find_all('^\\+.*') 75 | removed_lines = diff_view.find_all('^\\-.*') 76 | 77 | add_changes: List[sublime.Region] = [] 78 | remove_changes: List[sublime.Region] = [] 79 | plus_minus_sign_offset = 1 80 | # TODO: The way diffs are found is probably not the best... PRs to improve this are welcome 81 | diffs = list(dl.ndiff( 82 | [diff_view.substr(sublime.Region(r.begin() + plus_minus_sign_offset, r.end())) for r in removed_lines], 83 | [diff_view.substr(sublime.Region(r.begin() + plus_minus_sign_offset, r.end())) for r in added_lines])) 84 | start_find_pt = 0 85 | for i, diff in enumerate(diffs): 86 | is_change = diff.startswith('?') 87 | if is_change: 88 | look_text = diffs[i-1][2:] #+ + font-size: 0.9em dsa; 89 | diff_text = diff[2:] #? ^ ++++ 90 | look_region = diff_view.find(re.escape(look_text), start_find_pt) 91 | if look_region is None: 92 | continue 93 | row, _ = diff_view.rowcol(look_region.begin()) 94 | # for debugging 95 | # print('look text', look_text) 96 | # print('diff text', diff_text) 97 | addition_matches = re.finditer(r'\++', diff_text) 98 | for i in addition_matches: 99 | start, end = i.span(0) 100 | start_point = diff_view.text_point(row, start + plus_minus_sign_offset) 101 | end_point = diff_view.text_point(row, end + plus_minus_sign_offset) 102 | add_changes.append(sublime.Region(start_point, end_point)) 103 | 104 | removal_matches = re.finditer(r'\-+', diff_text) 105 | for i in removal_matches: 106 | start, end = i.span(0) 107 | start_point = diff_view.text_point(row, start + plus_minus_sign_offset) 108 | end_point = diff_view.text_point(row, end + plus_minus_sign_offset) 109 | remove_changes.append(sublime.Region(start_point, end_point)) 110 | 111 | diff_text_without_leading_tilde = ' ' + diff_text[1:] # diff_text 112 | modification_matches = re.finditer('\\^+', diff_text_without_leading_tilde) 113 | for i in modification_matches: 114 | start, end = i.span(0) 115 | start_point = diff_view.text_point(row, start + plus_minus_sign_offset) 116 | end_point = diff_view.text_point(row, end + plus_minus_sign_offset) 117 | 118 | if diff_view.match_selector(start_point, 'markup.deleted.diff'): 119 | remove_changes.append(sublime.Region(start_point, end_point)) 120 | else: 121 | add_changes.append(sublime.Region(start_point, end_point)) 122 | 123 | diff_view.add_regions('git_diff_view.add_bg', added_lines, "diff.inserted") 124 | diff_view.add_regions('git_diff_view.removed_bg', removed_lines, "diff.deleted") 125 | diff_view.add_regions('git_diff_view.add_char', add_changes, "diff.inserted.char") 126 | diff_view.add_regions('git_diff_view.remove_char', remove_changes, "diff.deleted.char") 127 | 128 | 129 | class GitDiffViewUpdateView(sublime_plugin.TextCommand): 130 | def run(self, edit: sublime.Edit, content: str) -> None: 131 | self.view.set_read_only(False) 132 | regions = [r for r in self.view.sel()] 133 | self.view.replace(edit, sublime.Region(0, self.view.size()), content) 134 | sel = self.view.sel() 135 | sel.clear() 136 | sel.add_all(regions) 137 | # disable editing the file for showing 138 | self.view.set_read_only(True) 139 | 140 | def get_syntax(file_name: str, view: sublime.View) -> Optional[str]: 141 | window = view.window() 142 | if not window: 143 | return None 144 | if not os.path.exists(file_name): 145 | return None 146 | tmp_buffer = window.open_file(file_name, sublime.TRANSIENT) 147 | tmp_buffer.set_scratch(True) 148 | # Even if is_loading() is true the view's settings can be 149 | # retrieved; settings assigned before open_file() returns. 150 | syntax = str(tmp_buffer.settings().get("syntax", "")) 151 | window.run_command("close") 152 | window.focus_view(view) 153 | return syntax 154 | -------------------------------------------------------------------------------- /core/git_commands.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import List, Literal, Optional, Tuple, TypedDict, cast 3 | import re 4 | import subprocess 5 | import sublime 6 | 7 | 8 | ModificationType = Literal[ 9 | "??", # Untracked 10 | " A", # Added 11 | "AM", # Added and Staged 12 | " M", # Modified 13 | "MM", # Modified and Staged 14 | " D", # Deleted 15 | " R", # Renamed 16 | " C", # Copied 17 | "UU", # Unmerged(Conflict) 18 | ] 19 | 20 | GitStatus = TypedDict('GitStatus', { 21 | "file_name": str, 22 | "modification_type": ModificationType, 23 | "is_staged": bool, 24 | "old_file_name": Optional[str] 25 | }) 26 | 27 | 28 | class Git: 29 | ''' Responsible for running git commands throughout `subprocess` 30 | and returning it's output. ''' 31 | # _diff_file_cache = {} 32 | _show_added_file_cache = {} 33 | 34 | @staticmethod 35 | def reset_command_cache(): 36 | # Git._diff_file_cache = {} 37 | Git._show_added_file_cache = {} 38 | 39 | def __init__(self, window: sublime.Window) -> None: 40 | self.window = window 41 | self.git_root_dir = None 42 | self.git_root_dir = str(self.run(['git rev-parse --show-toplevel']).strip()) 43 | 44 | def branch_name(self): 45 | cmd = ['git rev-parse --abbrev-ref HEAD'] 46 | return self.run(cmd).strip() 47 | 48 | def git_statuses(self) -> List[GitStatus]: 49 | statuses: List[GitStatus] = [] 50 | # array of staged statuses 51 | staged_files = self.diff().splitlines() 52 | git_status_output = self.status_untracked_files() 53 | # normalize git status output 54 | files_changed = git_status_output.splitlines() 55 | files_changed = list(map(lambda file: file.strip(), files_changed)) 56 | for file in files_changed: 57 | reg_result = re.search( 58 | r"(.{0,2})\s+(.+)", 59 | file 60 | ) 61 | if not reg_result: 62 | return [] 63 | modification_type, file = reg_result.groups() 64 | # if file contains spaces the name will 65 | # be warped with quotes, so we strip them 66 | file = file.strip("\"") 67 | # strip spaces from type if left 68 | modification_type = modification_type.strip() 69 | old_file_name = None 70 | if 'R' in modification_type: 71 | old_file_name, file = self.split_filename_at_arrow(file) 72 | if 'C' in modification_type: 73 | old_file_name, file = self.split_filename_at_arrow(file) 74 | # append space to modification type, looks prettier 75 | if len(modification_type) < 2: 76 | modification_type = ' {}'.format(modification_type) 77 | statuses.append({ 78 | "file_name": file, 79 | "modification_type": cast(ModificationType, modification_type), 80 | "is_staged": file in staged_files, 81 | "old_file_name": old_file_name 82 | }) 83 | statuses = sorted(statuses, key=lambda k: k['file_name']) 84 | # bad code, fix circular imports (: 85 | from .git_diff_view import GitDiffView 86 | GitDiffView.git_statuses[self.window.id()] = statuses # store 87 | return statuses 88 | 89 | def split_filename_at_arrow(self, file: str) -> Tuple[str, str]: 90 | ''' If the file has a `->` than split it in two files. 91 | Returns the `old_file_name`, than the `new_file`. ''' 92 | old_file_name, new_file = file.split("->") 93 | file = new_file.replace("\"", "") 94 | file = file.strip() 95 | old_file_name = old_file_name.replace("\"", "") 96 | old_file_name = old_file_name.strip() 97 | return old_file_name, file 98 | 99 | def status_untracked_files(self) -> str: 100 | cmd = ['git status --porcelain --untracked-files'] 101 | return self.run(cmd) 102 | 103 | def diff(self) -> str: 104 | cmd = ['git diff --name-only --cached'] 105 | return self.run(cmd) 106 | 107 | def diff_all_changes(self) -> str: 108 | cmd = ['git diff'] 109 | return self.run(cmd) 110 | 111 | def diff_staged(self) -> str: 112 | cmd = ['git diff --staged'] 113 | return self.run(cmd) 114 | 115 | def diff_file(self, file_name: str) -> str: 116 | file_name = escape_special_characters(file_name) 117 | # if file_name in Git._diff_file_cache: 118 | # return Git._diff_file_cache[file_name] 119 | cmd = ['git diff --no-color HEAD -- {}'.format(file_name)] 120 | result = self.run(cmd) 121 | # Git._diff_file_cache[file_name] = result 122 | return result 123 | 124 | def diff_head(self, file_name: str) -> str: 125 | file_name = escape_special_characters(file_name) 126 | cmd = ['git diff --no-color HEAD -- {}'.format(file_name)] 127 | output = '' 128 | try: 129 | output = diff_head(self.run(cmd)) 130 | except Exception: 131 | output = '' 132 | return output 133 | 134 | def diff_file_staged(self, file_name: str) -> str: 135 | file_name = escape_special_characters(file_name) 136 | cmd = ['git diff --no-color --staged -- {}'.format(file_name)] 137 | output = remove_first_lines(self.run(cmd), 4) 138 | return output 139 | 140 | def diff_file_unstaged(self, file_name: str) -> str: 141 | file_name = escape_special_characters(file_name) 142 | cmd = ['git diff --no-color -- {}'.format(file_name)] 143 | output = remove_first_lines(self.run(cmd), 4) 144 | return output 145 | 146 | def show_added_file(self, file_name: str) -> str: 147 | file_name = escape_special_characters(file_name) 148 | if file_name in Git._show_added_file_cache: 149 | return Git._show_added_file_cache[file_name] 150 | cmd = ['cat {}'.format(file_name)] 151 | result = self.run(cmd) 152 | Git._show_added_file_cache[file_name] = result 153 | return result 154 | 155 | def show_deleted_file(self, file_name: str) -> str: 156 | file_name = escape_special_characters(file_name) 157 | cmd = ['git show HEAD:{}'.format(file_name)] 158 | return self.run(cmd) 159 | 160 | def stage_files(self, file_names: list[str]) -> str: 161 | """ stage file """ 162 | file_name = [escape_special_characters(file_name) for file_name in file_names] 163 | cmd = [f'git add {" ".join(file_name)}'] 164 | return self.run(cmd) 165 | 166 | def unstage_files(self, file_names: list[str]) -> str: 167 | """ unstage file """ 168 | file_name = [escape_special_characters(file_name) for file_name in file_names] 169 | cmd = [f'git reset HEAD -- {" ".join(file_name)}'] 170 | return self.run(cmd) 171 | 172 | def checkout(self, file_name: str) -> str: 173 | """ dismiss changes """ 174 | file_name = escape_special_characters(file_name) 175 | cmd = ['git checkout -- {}'.format(file_name)] 176 | return self.run(cmd) 177 | 178 | def clean(self, file_name: str) -> str: 179 | file_name = escape_special_characters(file_name) 180 | cmd = ['git clean -f {}'.format(file_name)] 181 | return self.run(cmd) 182 | 183 | def stage_patch(self, file_name: str) -> str: 184 | file_name = escape_special_characters(file_name) 185 | cmd = [f'git apply --cache {file_name}'] 186 | return self.run(cmd) 187 | 188 | def discard_patch(self, file_name: str) -> str: 189 | file_name = escape_special_characters(file_name) 190 | cmd = [f'git apply --reverse {file_name}'] 191 | return self.run(cmd) 192 | 193 | def unstage_patch(self, file_name: str) -> str: 194 | file_name = escape_special_characters(file_name) 195 | cmd = [f'git apply -R --cache {file_name}'] 196 | return self.run(cmd) 197 | 198 | def run(self, cmd: List[str]) -> str: 199 | cwd = self.git_root_dir if self.git_root_dir else self.window.extract_variables()['folder'] 200 | p = subprocess.Popen(cmd, 201 | bufsize=-1, 202 | cwd=cwd, 203 | stdin=subprocess.PIPE, 204 | stdout=subprocess.PIPE, 205 | stderr=subprocess.STDOUT, 206 | shell=True) 207 | output, _ = p.communicate() 208 | if p.returncode == 1: 209 | decoded_error = output.decode('utf-8') 210 | print(f'GitDiffView: An error happened while running this command "{cmd}".', decoded_error) 211 | raise Exception(f'GitDiffView: An error happened while running this command "{cmd}". {decoded_error}') 212 | return output.decode('utf-8') 213 | 214 | 215 | def escape_special_characters(file_name: str) -> str: 216 | file_name = file_name.replace('(', '\\(') 217 | file_name = file_name.replace(')', '\\)') 218 | return file_name.replace(' ', '\\ ') 219 | 220 | 221 | def remove_first_lines(text: str, number_of_lines: int) -> str: 222 | return text.split("\n", number_of_lines)[number_of_lines] 223 | 224 | 225 | def diff_head(diff: str) -> str: 226 | return "\n".join(diff.split("\n", 4)[:4]) 227 | -------------------------------------------------------------------------------- /stubs/sublime.pyi: -------------------------------------------------------------------------------- 1 | # Stubs for sublime (Python 3.5) 2 | # 3 | # NOTE: This dynamically typed stub was automatically generated by stubgen. 4 | 5 | from typing import Any, Callable, Dict, Iterator, List, Optional, Reversible, Sequence, Tuple, Union 6 | import enum 7 | 8 | 9 | class _LogWriter: 10 | def flush(self) -> None: 11 | ... 12 | 13 | def write(self, s: str) -> None: 14 | ... 15 | 16 | 17 | HOVER_TEXT = ... # type: int 18 | HOVER_GUTTER = ... # type: int 19 | HOVER_MARGIN = ... # type: int 20 | ENCODED_POSITION = ... # type: int 21 | TRANSIENT = ... # type: int 22 | SEMI_TRANSIENT = ... # type: int 23 | FORCE_GROUP = ... # type: int 24 | ADD_TO_SELECTION = ... # type: int 25 | REPLACE_MRU = ... # type: int 26 | CLEAR_TO_RIGHT = ... # type: int 27 | IGNORECASE = ... # type: int 28 | LITERAL = ... # type: int 29 | REVERSE = ... # type: int 30 | COOPERATE_WITH_AUTO_COMPLETE = ... # type: int 31 | HIDE_ON_MOUSE_MOVE = ... # type: int 32 | HIDE_ON_MOUSE_MOVE_AWAY = ... # type: int 33 | KEEP_ON_SELECTION_MODIFIED = ... # type: int 34 | DRAW_EMPTY = ... # type: int 35 | HIDE_ON_MINIMAP = ... # type: int 36 | DRAW_EMPTY_AS_OVERWRITE = ... # type: int 37 | PERSISTENT = ... # type: int 38 | DRAW_OUTLINED = ... # type: int 39 | DRAW_NO_FILL = ... # type: int 40 | DRAW_NO_OUTLINE = ... # type: int 41 | DRAW_SOLID_UNDERLINE = ... # type: int 42 | DRAW_STIPPLED_UNDERLINE = ... # type: int 43 | DRAW_SQUIGGLY_UNDERLINE = ... # type: int 44 | HIDDEN = ... # type: int 45 | OP_EQUAL = ... # type: int 46 | OP_NOT_EQUAL = ... # type: int 47 | OP_REGEX_MATCH = ... # type: int 48 | CLASS_WORD_START = ... # type: int 49 | CLASS_WORD_END = ... # type: int 50 | CLASS_PUNCTUATION_START = ... # type: int 51 | CLASS_PUNCTUATION_END = ... # type: int 52 | CLASS_SUB_WORD_START = ... # type: int 53 | CLASS_SUB_WORD_END = ... # type: int 54 | INHIBIT_EXPLICIT_COMPLETIONS = ... # type: int 55 | INHIBIT_REORDER = ... # type: int 56 | DYNAMIC_COMPLETIONS = ... # type: int 57 | COMPLETION_FLAG_KEEP_PREFIX = ... # type: int 58 | DIALOG_CANCEL = ... # type: int 59 | DIALOG_YES = ... # type: int 60 | DIALOG_NO = ... # type: int 61 | UI_ELEMENT_SIDE_BAR = ... # type: int 62 | UI_ELEMENT_MINIMAP = ... # type: int 63 | UI_ELEMENT_TABS = ... # type: int 64 | UI_ELEMENT_STATUS_BAR = ... # type: int 65 | UI_ELEMENT_MENU = ... # type: int 66 | UI_ELEMENT_OPEN_FILES = ... # type: int 67 | LAYOUT_INLINE = ... # type: int 68 | LAYOUT_BELOW = ... # type: int 69 | LAYOUT_BLOCK = ... # type: int 70 | KIND_ID_AMBIGUOUS = ... # type: int 71 | KIND_ID_KEYWORD = ... # type: int 72 | KIND_ID_TYPE = ... # type: int 73 | KIND_ID_COLOR_DARK = ... # type: int 74 | KIND_ID_COLOR_LIGHT = ... # type: int 75 | KIND_ID_COLOR_BLUISH = ... # type: int 76 | KIND_ID_COLOR_CYANISH = ... # type: int 77 | KIND_ID_COLOR_GREENISH = ... # type: int 78 | KIND_ID_COLOR_ORANGISH = ... # type: int 79 | KIND_ID_COLOR_PINKISH = ... # type: int 80 | KIND_ID_COLOR_PURPLISH = ... # type: int 81 | KIND_ID_COLOR_REDISH = ... # type: int 82 | KIND_ID_COLOR_YELLOWISH = ... # type: int 83 | KIND_ID_FUNCTION = ... # type: int 84 | KIND_ID_NAMESPACE = ... # type: int 85 | KIND_ID_NAVIGATION = ... # type: int 86 | KIND_ID_MARKUP = ... # type: int 87 | KIND_ID_VARIABLE = ... # type: int 88 | KIND_ID_SNIPPET = ... # type: int 89 | KIND_AMBIGUOUS = ... # type: Tuple[int, str, str] 90 | KIND_KEYWORD = ... # type: Tuple[int, str, str] 91 | KIND_TYPE = ... # type: Tuple[int, str, str] 92 | KIND_FUNCTION = ... # type: Tuple[int, str, str] 93 | KIND_NAMESPACE = ... # type: Tuple[int, str, str] 94 | KIND_NAVIGATION = ... # type: Tuple[int, str, str] 95 | KIND_MARKUP = ... # type: Tuple[int, str, str] 96 | KIND_VARIABLE = ... # type: Tuple[int, str, str] 97 | KIND_SNIPPET = ... # type: Tuple[int, str, str] 98 | COMPLETION_FORMAT_TEXT = ... # type: int 99 | COMPLETION_FORMAT_SNIPPET = ... # type: int 100 | WANT_EVENT = ... # type: int 101 | 102 | SYMBOL_SOURCE_ANY = ... # type: int 103 | SYMBOL_SOURCE_INDEX = ... # type: int 104 | SYMBOL_SOURCE_OPEN_FILES = ... # type: int 105 | 106 | SYMBOL_TYPE_ANY = ... # type: int 107 | SYMBOL_TYPE_DEFINITION = ... # type: int 108 | SYMBOL_TYPE_REFERENCE = ... # type: int 109 | 110 | class Settings: 111 | settings_id = ... # type: int 112 | 113 | def __init__(self, id: int) -> None: 114 | ... 115 | 116 | def get(self, key: str, default: Optional[Any] = ...) -> Optional[Any]: 117 | ... 118 | 119 | def has(self, key: str) -> bool: 120 | ... 121 | 122 | def set(self, key: str, value: Any) -> None: 123 | ... 124 | 125 | def erase(self, key: str) -> None: 126 | ... 127 | 128 | def add_on_change(self, tag: str, callback: Callable[[], None]) -> None: 129 | ... 130 | 131 | def clear_on_change(self, tag: str) -> None: 132 | ... 133 | 134 | 135 | def version() -> str: 136 | ... 137 | 138 | 139 | def platform() -> str: 140 | ... 141 | 142 | 143 | def arch() -> str: 144 | ... 145 | 146 | 147 | def channel() -> str: 148 | ... 149 | 150 | 151 | def executable_path() -> str: 152 | ... 153 | 154 | 155 | def executable_hash() -> str: 156 | ... 157 | 158 | 159 | def packages_path() -> str: 160 | ... 161 | 162 | 163 | def installed_packages_path() -> str: 164 | ... 165 | 166 | 167 | def cache_path() -> str: 168 | ... 169 | 170 | 171 | def status_message(msg: str) -> None: 172 | ... 173 | 174 | 175 | def error_message(msg: str) -> None: 176 | ... 177 | 178 | 179 | def message_dialog(msg: str) -> None: 180 | ... 181 | 182 | 183 | def ok_cancel_dialog(msg: str, ok_title: str = ...) -> bool: 184 | ... 185 | 186 | 187 | def yes_no_cancel_dialog(msg: str, yes_title: str = ..., no_title: str = ...) -> int: 188 | ... 189 | 190 | 191 | def run_command(cmd: str, args: Optional[Any] = ...) -> None: 192 | ... 193 | 194 | 195 | def get_clipboard(size_limit: int = ...) -> str: 196 | ... 197 | 198 | 199 | def set_clipboard(text: str) -> None: 200 | ... 201 | 202 | 203 | def log_commands(flag: bool) -> None: 204 | ... 205 | 206 | 207 | def log_input(flag: bool) -> None: 208 | ... 209 | 210 | 211 | def log_result_regex(flag: bool) -> None: 212 | ... 213 | 214 | 215 | def log_indexing(flag: bool) -> None: 216 | ... 217 | 218 | 219 | def log_build_systems(flag: bool) -> None: 220 | ... 221 | 222 | 223 | def score_selector(scope_name: str, selector: str) -> int: 224 | ... 225 | 226 | 227 | def load_resource(name: str) -> str: 228 | ... 229 | 230 | 231 | def load_binary_resource(name: str) -> bytes: 232 | ... 233 | 234 | 235 | def find_resources(pattern: str) -> Sequence[str]: 236 | ... 237 | 238 | 239 | def encode_value(val: Any, pretty: bool = ...) -> str: 240 | ... 241 | 242 | 243 | def decode_value(data: str) -> Any: 244 | ... 245 | 246 | 247 | def expand_variables(val: Any, variables: dict) -> Any: 248 | ... 249 | 250 | 251 | def load_settings(base_name: str) -> Settings: 252 | ... 253 | 254 | 255 | def save_settings(base_name: str) -> None: 256 | ... 257 | 258 | 259 | def set_timeout(f: Callable[[], Any], timeout_ms: int = ...) -> None: 260 | ... 261 | 262 | 263 | def set_timeout_async(f: Callable[[], Any], timeout_ms: int = ...) -> None: 264 | ... 265 | 266 | 267 | def active_window() -> 'Window': 268 | ... 269 | 270 | 271 | def windows() -> 'Sequence[Window]': 272 | ... 273 | 274 | 275 | def get_macro() -> Sequence[dict]: 276 | ... 277 | 278 | 279 | def syntax_from_path(syntax_path: str) -> Optional[Syntax]: 280 | ... 281 | 282 | 283 | def command_url(cmd: str, args: Optional[dict] = ...) -> str: 284 | ... 285 | 286 | 287 | class Syntax: 288 | path = ... # type: str 289 | name = ... # type: str 290 | hidden = ... # type: bool 291 | scope = ... # type: str 292 | 293 | def __init__(self, path: str, name: str, hidden: bool, scope: str) -> None: 294 | ... 295 | 296 | 297 | class SymbolLocation: 298 | path = ... # type: str 299 | display_name = ... # type: str 300 | row = ... # type: int 301 | col = ... # type: int 302 | syntax = ... # type: str 303 | type = ... # type: int 304 | kind = ... # type: int 305 | 306 | def __init__( 307 | self, 308 | path: str, 309 | display_name: str, 310 | row: int, 311 | col: int, 312 | syntax: str, 313 | type: int, 314 | kind: int) -> None: 315 | ... 316 | 317 | class CompletionItem: 318 | flags = ... # type: int 319 | details = ... # type: str 320 | annotation = ... # type: str 321 | kind = ... # type: Tuple[int, str, str] 322 | 323 | def __init__( 324 | self, 325 | trigger: str, 326 | annotation: str = "", 327 | completion: str = "", 328 | completion_format: int = COMPLETION_FORMAT_TEXT, 329 | kind: Tuple[int, str, str] = KIND_AMBIGUOUS, 330 | details: str = "") -> None: 331 | ... 332 | 333 | @classmethod 334 | def snippet_completion( 335 | cls, 336 | trigger: str, 337 | snippet: str, 338 | annotation: str = " ", 339 | kind: Tuple[int, str, str] = KIND_SNIPPET, 340 | details: str = "") -> 'CompletionItem': 341 | ... 342 | 343 | @classmethod 344 | def command_completion(cls, 345 | trigger: str, 346 | command: str, 347 | args: dict = {}, 348 | annotation: str = "", 349 | kind: Tuple[int, str, str] = KIND_AMBIGUOUS, 350 | details: str = "" 351 | ) -> 'CompletionItem': 352 | ... 353 | 354 | 355 | class CompletionList: 356 | def set_completions(self, completions: List[CompletionItem], flags: int = 0) -> None: 357 | ... 358 | 359 | 360 | class Window: 361 | window_id = ... # type: int 362 | settings_object = ... # type: Settings 363 | template_settings_object = ... # type: Any 364 | 365 | def __init__(self, id: int) -> None: 366 | ... 367 | 368 | def __eq__(self, other: object) -> bool: 369 | ... 370 | 371 | def __bool__(self) -> bool: 372 | ... 373 | 374 | def id(self) -> int: 375 | ... 376 | 377 | def is_valid(self) -> bool: 378 | ... 379 | 380 | def symbol_locations(self, sym: str, source: Optional[int] = ..., type: Optional[int]= ..., kind_id: Optional[int]= ..., kind_letter: Optional[str]= ...) -> List[SymbolLocation]: 381 | ... 382 | 383 | # def hwnd(self): ... 384 | def active_sheet(self) -> 'Sheet': 385 | ... 386 | 387 | def active_view(self) -> 'Optional[View]': 388 | ... 389 | 390 | def run_command(self, cmd: str, args: Optional[Any] = ...) -> None: 391 | ... 392 | 393 | def new_file(self, flags: int = ..., syntax: str = ...) -> 'View': 394 | ... 395 | 396 | def open_file(self, fname: str, flags: int = ..., group: int = ...) -> 'View': 397 | ... 398 | 399 | def find_open_file(self, fname: str) -> 'Optional[View]': 400 | ... 401 | 402 | def num_groups(self) -> int: 403 | ... 404 | 405 | def active_group(self) -> int: 406 | ... 407 | 408 | def focus_group(self, idx: int) -> None: 409 | ... 410 | 411 | def focus_sheet(self, sheet: 'Sheet') -> None: 412 | ... 413 | 414 | def focus_view(self, view: 'View') -> None: 415 | ... 416 | 417 | def get_sheet_index(self, sheet: 'Sheet') -> Tuple[int, int]: 418 | ... 419 | 420 | def get_view_index(self, view: 'View') -> Tuple[int, int]: 421 | ... 422 | 423 | def set_sheet_index(self, sheet: 'Sheet', group: int, idx: int) -> None: 424 | ... 425 | 426 | def set_view_index(self, view: 'View', group: int, idx: int) -> None: 427 | ... 428 | 429 | def sheets(self) -> 'List[Sheet]': 430 | ... 431 | 432 | def selected_sheets(self) -> 'List[Sheet]': 433 | ... 434 | 435 | def select_sheets(self, sheets: 'List[Sheet]') -> None: 436 | ... 437 | 438 | def selected_sheets_in_group(self, group: int) -> 'List[Sheet]': 439 | ... 440 | 441 | def views(self) -> 'List[View]': 442 | ... 443 | 444 | def active_sheet_in_group(self, group: int) -> 'Sheet': 445 | ... 446 | 447 | def active_view_in_group(self, group: int) -> 'View': 448 | ... 449 | 450 | def sheets_in_group(self, group: int) -> 'List[Sheet]': 451 | ... 452 | 453 | def views_in_group(self, group: int) -> 'List[View]': 454 | ... 455 | 456 | def transient_sheet_in_group(self, group: int) -> 'Sheet': 457 | ... 458 | 459 | def transient_view_in_group(self, group: int) -> 'View': 460 | ... 461 | 462 | def layout(self) -> dict[str, Any]: ... 463 | def get_layout(self) -> dict[str, Any]: ... 464 | def set_layout(self, layout: dict[str, Any]) -> None: ... 465 | 466 | def create_output_panel(self, name: str, unlisted: bool = ...) -> 'View': 467 | ... 468 | 469 | def find_output_panel(self, name: str) -> 'Optional[View]': 470 | ... 471 | 472 | def destroy_output_panel(self, name: str) -> None: 473 | ... 474 | 475 | def active_panel(self) -> Optional[str]: 476 | ... 477 | 478 | def panels(self) -> List[str]: 479 | ... 480 | 481 | def get_output_panel(self, name: str) -> 'Optional[View]': 482 | ... 483 | 484 | def show_input_panel(self, caption: str, initial_text: str, on_done: Optional[Callable], 485 | on_change: Optional[Callable], on_cancel: Optional[Callable]) -> 'View': 486 | ... 487 | 488 | def show_quick_panel(self, 489 | items: List[Any], 490 | on_select: Callable, 491 | flags: int = ..., 492 | selected_index: int = ..., 493 | on_highlight: Optional[Callable] = ..., 494 | placeholder: Optional[str] = ...) -> None: 495 | ... 496 | 497 | def is_sidebar_visible(self) -> bool: 498 | ... 499 | 500 | def set_sidebar_visible(self, flag: bool) -> None: 501 | ... 502 | 503 | def is_minimap_visible(self) -> bool: 504 | ... 505 | 506 | def set_minimap_visible(self, flag: bool) -> None: 507 | ... 508 | 509 | def is_status_bar_visible(self) -> bool: 510 | ... 511 | 512 | def set_status_bar_visible(self, flag: bool) -> None: 513 | ... 514 | 515 | def get_tabs_visible(self) -> bool: 516 | ... 517 | 518 | def set_tabs_visible(self, flag: bool) -> None: 519 | ... 520 | 521 | def is_menu_visible(self) -> bool: 522 | ... 523 | 524 | def set_menu_visible(self, flag: bool) -> None: 525 | ... 526 | 527 | def folders(self) -> List[str]: 528 | ... 529 | 530 | def project_file_name(self) -> str: 531 | ... 532 | 533 | def project_data(self) -> Optional[dict]: 534 | ... 535 | 536 | def set_project_data(self, v: Union[dict, None]) -> None: 537 | ... 538 | 539 | def settings(self) -> Settings: 540 | ... 541 | 542 | # def template_settings(self): ... 543 | def lookup_symbol_in_index(self, sym: str) -> List[str]: 544 | ... 545 | 546 | def lookup_symbol_in_open_files(self, sym: str) -> List[str]: 547 | ... 548 | 549 | def extract_variables(self) -> dict: 550 | ... 551 | 552 | def status_message(self, msg: str) -> None: 553 | ... 554 | 555 | 556 | class Edit: 557 | edit_token = ... # type: Any 558 | 559 | def __init__(self, token: Any) -> None: 560 | ... 561 | 562 | 563 | class Region: 564 | a = ... # type: int 565 | b = ... # type: int 566 | xpos = ... # type: int 567 | 568 | def __init__(self, a: int, b: Optional[int] = ..., xpos: int = ...) -> None: 569 | ... 570 | 571 | def __len__(self) -> int: 572 | ... 573 | 574 | def __eq__(self, rhs: object) -> bool: 575 | ... 576 | 577 | def __lt__(self, rhs: object) -> bool: 578 | ... 579 | 580 | def empty(self) -> bool: 581 | ... 582 | 583 | def begin(self) -> int: 584 | ... 585 | 586 | def end(self) -> int: 587 | ... 588 | 589 | def size(self) -> int: 590 | ... 591 | 592 | def contains(self, x: 'Union[Region, int]') -> bool: 593 | ... 594 | 595 | def cover(self, rhs: 'Region') -> 'Region': 596 | ... 597 | 598 | def intersection(self, rhs: 'Region') -> 'Region': 599 | ... 600 | 601 | def intersects(self, rhs: 'Region') -> bool: 602 | ... 603 | 604 | def to_tuple(self) -> Tuple[int, int]: 605 | ... 606 | 607 | 608 | class Selection(Reversible): 609 | view_id = ... # type: Any 610 | 611 | def __init__(self, id: Any) -> None: 612 | ... 613 | 614 | def __reversed__(self) -> Iterator[Region]: 615 | ... 616 | 617 | def __iter__(self) -> Iterator[Region]: 618 | ... 619 | 620 | def __len__(self) -> int: 621 | ... 622 | 623 | def __getitem__(self, index: int) -> Region: 624 | ... 625 | 626 | def __delitem__(self, index: int) -> None: 627 | ... 628 | 629 | def __eq__(self, rhs: Any) -> bool: 630 | ... 631 | 632 | def __lt__(self, rhs: Any) -> bool: 633 | ... 634 | 635 | def __bool__(self) -> bool: 636 | ... 637 | 638 | def is_valid(self) -> bool: 639 | ... 640 | 641 | def clear(self) -> None: 642 | ... 643 | 644 | def add(self, x: Union[Region, int]) -> None: 645 | ... 646 | 647 | def add_all(self, regions: Iterator[Union[Region, int]]) -> None: 648 | ... 649 | 650 | def subtract(self, region: Region) -> None: 651 | ... 652 | 653 | def contains(self, region: Region) -> bool: 654 | ... 655 | 656 | 657 | class Sheet: 658 | sheet_id = ... # type: Any 659 | 660 | def __init__(self, id: Any) -> None: 661 | ... 662 | 663 | def __eq__(self, other: object) -> bool: 664 | ... 665 | 666 | def id(self) -> int: 667 | ... 668 | 669 | def window(self) -> Optional[Window]: 670 | ... 671 | 672 | def group(self) -> int: 673 | ... 674 | 675 | def view(self) -> 'Optional[View]': 676 | ... 677 | 678 | def is_semi_transient(self) -> bool: 679 | ... 680 | 681 | def is_transient(self) -> bool: 682 | ... 683 | 684 | 685 | class HtmlSheet: 686 | sheet_id = ... # type: Any 687 | 688 | def __init__(self, id: Any) -> None: 689 | ... 690 | 691 | def set_name(self, name: str) -> None: 692 | ... 693 | 694 | def set_contents(self, contents: str) -> None: 695 | ... 696 | 697 | 698 | class ContextStackFrame: 699 | context_name = ... # type: str 700 | source_file = ... # type: str 701 | source_location = ... # type: Tuple[int, int] 702 | 703 | 704 | class View: 705 | view_id = ... # type: Any 706 | selection = ... # type: Any 707 | settings_object = ... # type: Any 708 | 709 | def __init__(self, id: Any) -> None: 710 | ... 711 | 712 | def __len__(self) -> int: 713 | ... 714 | 715 | def __eq__(self, other: object) -> bool: 716 | ... 717 | 718 | def __bool__(self) -> bool: 719 | ... 720 | 721 | def sheet(self) -> Sheet: 722 | ... 723 | 724 | def syntax(self) -> Any: 725 | ... 726 | 727 | def element(self) -> Optional[str]: 728 | ... 729 | 730 | def id(self) -> int: 731 | ... 732 | 733 | def buffer(self) -> "Optional[Buffer]": 734 | ... 735 | 736 | def buffer_id(self) -> int: 737 | ... 738 | 739 | def is_valid(self) -> bool: 740 | ... 741 | 742 | def is_primary(self) -> bool: 743 | ... 744 | 745 | def window(self) -> Optional[Window]: 746 | ... 747 | 748 | def file_name(self) -> Optional[str]: 749 | ... 750 | 751 | def close(self) -> None: 752 | ... 753 | 754 | def retarget(self, new_fname: str) -> None: 755 | ... 756 | 757 | def name(self) -> str: 758 | ... 759 | 760 | def set_name(self, name: str) -> None: 761 | ... 762 | 763 | def is_loading(self) -> bool: 764 | ... 765 | 766 | def is_dirty(self) -> bool: 767 | ... 768 | 769 | def is_read_only(self) -> bool: 770 | ... 771 | 772 | def set_read_only(self, read_only: bool) -> None: 773 | ... 774 | 775 | def is_scratch(self) -> bool: 776 | ... 777 | 778 | def set_scratch(self, scratch: bool) -> None: 779 | ... 780 | 781 | def encoding(self) -> str: 782 | ... 783 | 784 | def set_encoding(self, encoding_name: str) -> None: 785 | ... 786 | 787 | def line_endings(self) -> str: 788 | ... 789 | 790 | def set_line_endings(self, line_ending_name: str) -> None: 791 | ... 792 | 793 | def size(self) -> int: 794 | ... 795 | 796 | # def begin_edit(self, edit_token, cmd, args: Optional[Any] = ...) -> Edit: ... 797 | # def end_edit(self, edit: Edit) -> None: ... 798 | def is_in_edit(self) -> bool: 799 | ... 800 | 801 | def insert(self, edit: Edit, pt: int, text: str) -> None: 802 | ... 803 | 804 | def erase(self, edit: Edit, r: Region) -> None: 805 | ... 806 | 807 | def replace(self, edit: Edit, r: Region, text: str) -> None: 808 | ... 809 | 810 | def change_count(self) -> int: 811 | ... 812 | 813 | def run_command(self, cmd: str, args: Optional[Any] = ...) -> None: 814 | ... 815 | 816 | def sel(self) -> Selection: 817 | ... 818 | 819 | def substr(self, x: Union[Region, int]) -> str: 820 | ... 821 | 822 | def find(self, pattern: str, start_pt: int, flags: int = ...) -> Optional[Region]: 823 | ... 824 | 825 | def find_all(self, pattern: str, flags: int = ..., fmt: Optional[Any] = ..., 826 | extractions: Optional[Any] = ...) -> 'List[Region]': 827 | ... 828 | 829 | def settings(self) -> Settings: 830 | ... 831 | 832 | # def meta_info(self, key, pt: int): ... 833 | def extract_scope(self, pt: int) -> Region: 834 | ... 835 | 836 | def scope_name(self, pt: int) -> str: 837 | ... 838 | 839 | def context_backtrace(self, pt: int) -> Union[List[ContextStackFrame], List[str]]: 840 | ... 841 | 842 | def match_selector(self, pt: int, selector: str) -> bool: 843 | ... 844 | 845 | def score_selector(self, pt: int, selector: str) -> int: 846 | ... 847 | 848 | def find_by_selector(self, selector: str) -> List[Region]: 849 | ... 850 | 851 | # def indented_region(self, pt: int): ... 852 | # def indentation_level(self, pt: int): ... 853 | def has_non_empty_selection_region(self) -> bool: 854 | ... 855 | 856 | def lines(self, r: Region) -> List[Region]: 857 | ... 858 | 859 | def split_by_newlines(self, r: Region) -> List[Region]: 860 | ... 861 | 862 | def line(self, x: Union[Region, int]) -> Region: 863 | ... 864 | 865 | def full_line(self, x: Union[Region, int]) -> Region: 866 | ... 867 | 868 | def word(self, x: Union[Region, int]) -> Region: 869 | ... 870 | 871 | def classify(self, pt: int) -> int: 872 | ... 873 | 874 | def find_by_class(self, pt: int, forward: bool, classes: int, separators: str = ...) -> int: 875 | ... 876 | 877 | def expand_by_class(self, x: Union[Region, int], classes: int, separators: str = ...) -> Region: 878 | ... 879 | 880 | def rowcol(self, tp: int) -> Tuple[int, int]: 881 | ... 882 | 883 | def rowcol_utf8(self, tp: int) -> Tuple[int, int]: 884 | ... 885 | 886 | def rowcol_utf16(self, tp: int) -> Tuple[int, int]: 887 | ... 888 | 889 | def text_point(self, row: int, col: int, *, clamp_column: bool = False) -> int: 890 | ... 891 | 892 | def text_point_utf8(self, row: int, col_utf8: int, *, clamp_column: bool = False) -> int: 893 | ... 894 | 895 | def text_point_utf16(self, row: int, col_utf16: int, *, clamp_column: bool = False) -> int: 896 | ... 897 | 898 | def visible_region(self) -> Region: 899 | ... 900 | 901 | def show(self, x: Union[Selection, Region, int], show_surrounds: bool = ...) -> None: 902 | ... 903 | 904 | def show_at_center(self, x: Union[Selection, Region, int], animate: bool = True) -> None: 905 | ... 906 | 907 | def viewport_position(self) -> Tuple[int, int]: 908 | ... 909 | 910 | def set_viewport_position(self, xy: Tuple[int, int], animate: bool = ...) -> None: 911 | ... 912 | 913 | def viewport_extent(self) -> Tuple[int, int]: 914 | ... 915 | 916 | def layout_extent(self) -> Tuple[int, int]: 917 | ... 918 | 919 | def text_to_layout(self, tp: int) -> Tuple[int, int]: 920 | ... 921 | 922 | def layout_to_text(self, xy: Tuple[int, int]) -> int: 923 | ... 924 | 925 | def window_to_layout(self, xy: Tuple[int, int]) -> Tuple[int, int]: 926 | ... 927 | 928 | def window_to_text(self, xy: Tuple[int, int]) -> int: 929 | ... 930 | 931 | def line_height(self) -> float: 932 | ... 933 | 934 | def em_width(self) -> float: 935 | ... 936 | 937 | # def is_folded(self, sr) -> bool: ... 938 | # def folded_regions(self): ... 939 | def fold(self, x: Union[Region, List[Region]]) -> bool: 940 | ... 941 | 942 | def unfold(self, x: Union[Region, List[Region]]) -> List[Region]: 943 | ... 944 | 945 | def add_regions(self, key: str, regions: List[Region], scope: str = ..., icon: str = ..., flags: int = ..., 946 | annotations: List[str] = ..., annotation_color: str = ..., 947 | on_navigate: Callable[[str], None] = ..., on_close: Callable[[], None] = ...) -> None: 948 | ... 949 | 950 | def get_regions(self, key: str) -> List[Region]: 951 | ... 952 | 953 | def erase_regions(self, key: str) -> None: 954 | ... 955 | 956 | # def add_phantom(self, key: str, region: Region, content: str, layout, on_navigate: Optional[Any] = ...): ... 957 | # def erase_phantoms(self, key: str) -> None: ... 958 | # def erase_phantom_by_id(self, pid) -> None: ... 959 | # def query_phantom(self, pid): ... 960 | # def query_phantoms(self, pids): ... 961 | def assign_syntax(self, syntax_file: str) -> None: 962 | ... 963 | 964 | def set_syntax_file(self, syntax_file: str) -> None: 965 | ... 966 | 967 | def symbols(self) -> List[Tuple[Region, str]]: 968 | ... 969 | 970 | # def get_symbols(self): ... 971 | # def indexed_symbols(self): ... 972 | def set_status(self, key: str, value: str) -> None: 973 | ... 974 | 975 | def get_status(self, key: str) -> str: 976 | ... 977 | 978 | def erase_status(self, key: str) -> None: 979 | ... 980 | 981 | # def extract_completions(self, prefix: str, tp: int = ...): ... 982 | # def find_all_results(self): ... 983 | def find_all_results_with_text(self) -> List[Tuple[str, int, int, str]]: 984 | ... 985 | 986 | def command_history(self, delta: int, modifying_only: bool = ...) -> 'Tuple[str, dict, int]': 987 | ... 988 | 989 | def overwrite_status(self) -> bool: 990 | ... 991 | 992 | def set_overwrite_status(self, value: bool) -> None: 993 | ... 994 | 995 | def show_popup_menu(self, items: List[str], on_select: 'Callable', flags: int = ...) -> None: 996 | ... 997 | 998 | def show_popup(self, 999 | content: str, 1000 | flags: int = ..., 1001 | location: int = ..., 1002 | max_width: int = ..., 1003 | max_height: int = ..., 1004 | on_navigate: Optional[Any] = ..., 1005 | on_hide: Optional[Any] = ...) -> None: 1006 | ... 1007 | 1008 | def update_popup(self, content: str) -> None: 1009 | ... 1010 | 1011 | def is_popup_visible(self) -> bool: 1012 | ... 1013 | 1014 | def hide_popup(self) -> None: 1015 | ... 1016 | 1017 | def is_auto_complete_visible(self) -> bool: 1018 | ... 1019 | 1020 | def change_id(self) -> Any: # opaque handle object 1021 | ... 1022 | 1023 | def transform_region_from(self, region: Region, change_id: Any) -> Region: 1024 | ... 1025 | 1026 | def style_for_scope(self, scope: str) -> Dict[str, str]: 1027 | ... 1028 | 1029 | 1030 | class Buffer: 1031 | buffer_id = ... # type: int 1032 | 1033 | def __init__(self, id: int) -> None: 1034 | ... 1035 | 1036 | def views(self) -> Optional[List[View]]: 1037 | ... 1038 | 1039 | def primary_view(self) -> Optional[View]: 1040 | ... 1041 | 1042 | 1043 | class Phantom: 1044 | region = ... # type: Region 1045 | content = ... # type: Any 1046 | layout = ... # type: Any 1047 | on_navigate = ... # type: Any 1048 | id = ... # type: Any 1049 | 1050 | def __init__(self, region: Region, content: str, layout: int, on_navigate: Optional[Any] = ...) -> None: 1051 | ... 1052 | 1053 | def __eq__(self, rhs: object) -> bool: 1054 | ... 1055 | 1056 | 1057 | class PhantomSet: 1058 | view = ... # type: View 1059 | key = ... # type: Any 1060 | phantoms = ... # type: Any 1061 | 1062 | def __init__(self, view: View, key: str = ...) -> None: 1063 | ... 1064 | 1065 | def __del__(self) -> None: 1066 | ... 1067 | 1068 | def update(self, new_phantoms: Sequence[Phantom]) -> None: 1069 | ... 1070 | 1071 | 1072 | class HistoricPosition: 1073 | pt = ... # type: int 1074 | row = ... # type: int 1075 | col = ... # type: int 1076 | col_utf16 = ... # type: int 1077 | col_utf8 = ... # type: int 1078 | 1079 | 1080 | class TextChange: 1081 | a = ... # type: HistoricPosition 1082 | b = ... # type: HistoricPosition 1083 | str = ... # type: str 1084 | len_utf8 = ... # type: int 1085 | len_utf16 = ... # type: int 1086 | 1087 | 1088 | class QuickPanelItem: 1089 | def __init__( 1090 | self, 1091 | trigger: str, 1092 | details: Union[str, List[str]] = "", 1093 | annotation: str = "", 1094 | kind: Tuple[int, str, str] = KIND_AMBIGUOUS 1095 | ) -> None: 1096 | ... 1097 | 1098 | 1099 | class ListInputItem: 1100 | def __init__( 1101 | self, 1102 | text: str, 1103 | value: Any, 1104 | details: Union[str, List[str]] = "", 1105 | annotation: str = "", 1106 | kind: Tuple[int, str, str] = KIND_AMBIGUOUS 1107 | ) -> None: 1108 | ... 1109 | 1110 | 1111 | class Html: 1112 | def __init__( 1113 | self, 1114 | text: str, 1115 | ) -> None: 1116 | ... 1117 | --------------------------------------------------------------------------------