├── .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 | 
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 | 
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 |
--------------------------------------------------------------------------------