├── .python-version ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── Default.sublime-commands ├── GitGutter.sublime-settings ├── LICENSE ├── Main.sublime-menu ├── Preferences.sublime-settings ├── Preferences.sublime-settings-hints ├── README.md ├── VERSION ├── dependencies.json ├── gitgutter_popup.css ├── messages.json ├── messages ├── 1.10.0.txt ├── 1.10.1.txt ├── 1.10.2.txt ├── 1.10.3.txt ├── 1.11.0.txt ├── 1.11.1.txt ├── 1.11.10.txt ├── 1.11.2.txt ├── 1.11.3.txt ├── 1.11.4.txt ├── 1.11.5.txt ├── 1.11.6.txt ├── 1.11.7.txt ├── 1.11.8.txt ├── 1.11.9.txt ├── 1.2.0-pre.txt ├── 1.2.1.txt ├── 1.2.2.txt ├── 1.2.3.txt ├── 1.2.4.txt ├── 1.3.0.txt ├── 1.4.0.txt ├── 1.5.0.txt ├── 1.5.1.txt ├── 1.6.0-rc.1.txt ├── 1.6.0.txt ├── 1.7.0.txt ├── 1.7.1.txt ├── 1.7.10.txt ├── 1.7.11.txt ├── 1.7.12.txt ├── 1.7.13.txt ├── 1.7.14.txt ├── 1.7.15.txt ├── 1.7.2.txt ├── 1.7.3.txt ├── 1.7.4.txt ├── 1.7.5.txt ├── 1.7.6.txt ├── 1.7.7.txt ├── 1.7.8.txt ├── 1.7.9.txt ├── 1.8.0-rc.1.txt ├── 1.8.0-rc.2.txt ├── 1.8.0.txt ├── 1.9.0.txt ├── 1.9.1.txt ├── 1.9.2.txt ├── 1.9.3.txt ├── 1.9.4.txt ├── 1.9.5.txt └── install.txt ├── modules ├── __init__.py ├── annotation.py ├── blame.py ├── commands.py ├── compare.py ├── copy.py ├── events.py ├── goto.py ├── handler.py ├── path.py ├── popup │ ├── __init__.py │ ├── differ.py │ └── factory.py ├── promise.py ├── revert.py ├── settings.py ├── show_diff.py ├── statusbar.py ├── support.py ├── tasks.py ├── temp.py ├── templates.py ├── utils.py └── view.py ├── plugin.py └── themes ├── Bars Thin ├── Bars Thin.gitgutter-theme ├── changed.png ├── changed@2x.png ├── deleted_bottom.png ├── deleted_bottom@2x.png ├── deleted_bottom_arrow.png ├── deleted_bottom_arrow@2x.png ├── deleted_dual.png ├── deleted_dual@2x.png ├── deleted_dual_arrow.png ├── deleted_dual_arrow@2x.png ├── deleted_top.png ├── deleted_top@2x.png ├── deleted_top_arrow.png ├── deleted_top_arrow@2x.png ├── ignored.png ├── ignored@2x.png ├── inserted.png ├── inserted@2x.png ├── untracked.png └── untracked@2x.png ├── Bars ├── Bars.gitgutter-theme ├── changed.png ├── changed@2x.png ├── deleted_bottom.png ├── deleted_bottom@2x.png ├── deleted_bottom_arrow.png ├── deleted_bottom_arrow@2x.png ├── deleted_dual.png ├── deleted_dual@2x.png ├── deleted_dual_arrow.png ├── deleted_dual_arrow@2x.png ├── deleted_top.png ├── deleted_top@2x.png ├── deleted_top_arrow.png ├── deleted_top_arrow@2x.png ├── ignored.png ├── ignored@2x.png ├── inserted.png ├── inserted@2x.png ├── untracked.png └── untracked@2x.png ├── Default ├── Default.gitgutter-theme ├── changed.png ├── changed@2x.png ├── deleted_bottom.png ├── deleted_bottom@2x.png ├── deleted_bottom_arrow.png ├── deleted_bottom_arrow@2x.png ├── deleted_dual.png ├── deleted_dual@2x.png ├── deleted_dual_arrow.png ├── deleted_dual_arrow@2x.png ├── deleted_top.png ├── deleted_top@2x.png ├── deleted_top_arrow.png ├── deleted_top_arrow@2x.png ├── ignored.png ├── ignored@2x.png ├── inserted.png ├── inserted@2x.png ├── untracked.png └── untracked@2x.png └── Monokai Pro ├── Monokai Pro.gitgutter-theme ├── changed.png ├── changed@2x.png ├── changed@3x.png ├── deleted_bottom.png ├── deleted_bottom@2x.png ├── deleted_bottom@3x.png ├── deleted_bottom_arrow.png ├── deleted_bottom_arrow@2x.png ├── deleted_bottom_arrow@3x.png ├── deleted_dual.png ├── deleted_dual@2x.png ├── deleted_dual@3x.png ├── deleted_dual_arrow.png ├── deleted_dual_arrow@2x.png ├── deleted_dual_arrow@3x.png ├── deleted_top.png ├── deleted_top@2x.png ├── deleted_top@3x.png ├── deleted_top_arrow.png ├── deleted_top_arrow@2x.png ├── deleted_top_arrow@3x.png ├── ignored.png ├── ignored@2x.png ├── ignored@3x.png ├── inserted.png ├── inserted@2x.png ├── inserted@3x.png ├── untracked.png ├── untracked@2x.png └── untracked@3x.png /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+shift+c"], "command": "git_gutter_copy_from_commit" }, 3 | { "keys": ["ctrl+shift+alt+j"], "command": "git_gutter_next_change" }, 4 | { "keys": ["ctrl+shift+alt+k"], "command": "git_gutter_prev_change" }, 5 | { "keys": ["ctrl+shift+alt+z"], "command": "git_gutter_revert_change" }, 6 | { "keys": ["ctrl+shift+alt+c", "ctrl+b"], "command": "git_gutter_blame" }, 7 | { "keys": ["ctrl+shift+alt+c", "ctrl+d"], "command": "git_gutter_diff_popup" }, 8 | { "keys": ["ctrl+shift+alt+c", "v"], "command": "git_gutter_show_compare" }, 9 | { "keys": ["ctrl+shift+alt+c", "h"], "command": "git_gutter_compare_head" }, 10 | { "keys": ["ctrl+shift+alt+c", "o"], "command": "git_gutter_compare_origin" }, 11 | { "keys": ["ctrl+shift+alt+c", "c"], "command": "git_gutter_compare_commit" }, 12 | { "keys": ["ctrl+shift+alt+c", "f"], "command": "git_gutter_compare_file_commit" }, 13 | { "keys": ["ctrl+shift+alt+c", "b"], "command": "git_gutter_compare_branch" }, 14 | { "keys": ["ctrl+shift+alt+c", "t"], "command": "git_gutter_compare_tag" } 15 | ] 16 | -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["super+shift+c"], "command": "git_gutter_copy_from_commit" }, 3 | { "keys": ["super+shift+option+j"], "command": "git_gutter_next_change" }, 4 | { "keys": ["super+shift+option+k"], "command": "git_gutter_prev_change" }, 5 | { "keys": ["super+shift+option+z"], "command": "git_gutter_revert_change" }, 6 | { "keys": ["super+shift+option+c", "super+b"], "command": "git_gutter_blame" }, 7 | { "keys": ["super+shift+option+c", "super+d"], "command": "git_gutter_diff_popup" }, 8 | { "keys": ["super+shift+option+c", "v"], "command": "git_gutter_show_compare" }, 9 | { "keys": ["super+shift+option+c", "h"], "command": "git_gutter_compare_head" }, 10 | { "keys": ["super+shift+option+c", "o"], "command": "git_gutter_compare_origin" }, 11 | { "keys": ["super+shift+option+c", "c"], "command": "git_gutter_compare_commit" }, 12 | { "keys": ["super+shift+option+c", "f"], "command": "git_gutter_compare_file_commit" }, 13 | { "keys": ["super+shift+option+c", "b"], "command": "git_gutter_compare_branch" }, 14 | { "keys": ["super+shift+option+c", "t"], "command": "git_gutter_compare_tag" } 15 | ] 16 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+shift+c"], "command": "git_gutter_copy_from_commit" }, 3 | { "keys": ["ctrl+shift+alt+j"], "command": "git_gutter_next_change" }, 4 | { "keys": ["ctrl+shift+alt+k"], "command": "git_gutter_prev_change" }, 5 | { "keys": ["ctrl+shift+alt+z"], "command": "git_gutter_revert_change" }, 6 | { "keys": ["ctrl+shift+alt+c", "ctrl+b"], "command": "git_gutter_blame" }, 7 | { "keys": ["ctrl+shift+alt+c", "ctrl+d"], "command": "git_gutter_diff_popup" }, 8 | { "keys": ["ctrl+shift+alt+c", "v"], "command": "git_gutter_show_compare" }, 9 | { "keys": ["ctrl+shift+alt+c", "h"], "command": "git_gutter_compare_head" }, 10 | { "keys": ["ctrl+shift+alt+c", "o"], "command": "git_gutter_compare_origin" }, 11 | { "keys": ["ctrl+shift+alt+c", "c"], "command": "git_gutter_compare_commit" }, 12 | { "keys": ["ctrl+shift+alt+c", "f"], "command": "git_gutter_compare_file_commit" }, 13 | { "keys": ["ctrl+shift+alt+c", "b"], "command": "git_gutter_compare_branch" }, 14 | { "keys": ["ctrl+shift+alt+c", "t"], "command": "git_gutter_compare_tag" } 15 | ] 16 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "GitGutter: Enable for View", 4 | "command": "git_gutter_enable_view", 5 | "args": { "enabled": true } 6 | }, 7 | { 8 | "caption": "GitGutter: Disable for View", 9 | "command": "git_gutter_enable_view", 10 | "args": { "enabled": false } 11 | }, 12 | { 13 | "caption": "GitGutter: Show Line Annotation", 14 | "command": "git_gutter_blame" 15 | }, 16 | { 17 | "caption": "GitGutter: Show Diff Popup", 18 | "command": "git_gutter_diff_popup" 19 | }, 20 | { 21 | "caption": "GitGutter: Goto Previous Change", 22 | "command": "git_gutter_prev_change" 23 | }, 24 | { 25 | "caption": "GitGutter: Goto Next Change", 26 | "command": "git_gutter_next_change" 27 | }, 28 | { 29 | "caption": "GitGutter: Copy Content from Commit", 30 | "command": "git_gutter_copy_from_commit" 31 | }, 32 | { 33 | "caption": "GitGutter: Revert Change to Commit", 34 | "command": "git_gutter_revert_change" 35 | }, 36 | { 37 | "caption": "GitGutter: Show Comparing Against", 38 | "command": "git_gutter_show_compare" 39 | }, 40 | { 41 | "caption": "GitGutter: Compare Against HEAD", 42 | "command": "git_gutter_compare_head" 43 | }, 44 | { 45 | "caption": "GitGutter: Compare Against Origin", 46 | "command": "git_gutter_compare_origin" 47 | }, 48 | { 49 | "caption": "GitGutter: Compare Against Commit", 50 | "command": "git_gutter_compare_commit" 51 | }, 52 | { 53 | "caption": "GitGutter: Compare Against File Commit", 54 | "command": "git_gutter_compare_file_commit" 55 | }, 56 | { 57 | "caption": "GitGutter: Compare Against Branch", 58 | "command": "git_gutter_compare_branch" 59 | }, 60 | { 61 | "caption": "GitGutter: Compare Against Tag", 62 | "command": "git_gutter_compare_tag" 63 | }, 64 | { 65 | "caption": "GitGutter: Support Info", 66 | "command": "git_gutter_support_info" 67 | }, 68 | { 69 | "caption": "Preferences: GitGutter Settings", 70 | "command": "edit_settings", 71 | "args": { 72 | "base_file": "${packages}/GitGutter/GitGutter.sublime-settings", 73 | "default": "// GitGutter Settings - User\n{\n\t$0\n}\n" 74 | } 75 | }, 76 | { 77 | "caption": "Preferences: GitGutter Key Bindings", 78 | "command": "edit_settings", 79 | "args": { 80 | "base_file": "${packages}/GitGutter/Default (${platform}).sublime-keymap", 81 | "default": "// Key Bindings - User\n[\n\t$0\n]\n" 82 | } 83 | }, 84 | { 85 | "caption": "Preferences: GitGutter Popup Stylesheet", 86 | "command": "edit_settings", 87 | "args": { 88 | "base_file": "${packages}/GitGutter/gitgutter_popup.css", 89 | "default": "/* GitGutter Popup Stylesheet - User */\n\n" 90 | } 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /GitGutter.sublime-settings: -------------------------------------------------------------------------------- 1 | // 2 | // Default values for GitGutter.sublime-settings 3 | // 4 | { 5 | // CUSTOM PATH TO GIT BINARY. 6 | // An empty string will search the PATH environment for "git". 7 | // 8 | // "git_binary": "", 9 | // 10 | // The setting may be a direct string to a git binary. 11 | // An unix like path makes git run via Windows Subsystem for Linux 12 | // on Windows 10. It fails if WSL is not available. 13 | // 14 | // "git_binary": "/usr/bin/git", 15 | // 16 | // Or it may be a dictionary keyed off what sublime.platform() returns, 17 | // so it may be customized on a per-platform basis. 18 | // 19 | // "git_binary": { 20 | // "default": "", 21 | // "windows": "C:/Program Files/Git/cmd/git.exe", 22 | // "linux": "/usr/bin/git", 23 | // "osx": "/usr/bin/git" 24 | // }, 25 | "git_binary": "", 26 | 27 | // Additional environment variables to pass to git. 28 | // This list is merged with the global set of environment variables 29 | // provided by Sublime Text. 30 | // 31 | // Note: 32 | // 1. Keys with value `None` are removed from the global environment. 33 | // 2. If this dictionary is defined per view or project it is used 34 | // exclusively! It won't be merged with the global settings. 35 | "env": { 36 | "GIT_OPTIONAL_LOCKS": 0 37 | }, 38 | 39 | // The commit, branch, tag, or remote to compare against. 40 | // This setting changes the initial compare target and can 41 | // be temporarily overwritten by 'Compare against ...' commands 42 | // Valid constants are: 43 | // "HEAD": Compare against most recent commit 44 | // "master": Compare against master branch 45 | // "master@{upstream}": Compare against remote master branch 46 | "compare_against": "HEAD", 47 | 48 | // The algorithm used by git diff to determine the differences. 49 | // "default": let git decide (don't pass an algorithm) 50 | // "minimal": use minimal diff algorithm 51 | // "patience": use patience diff algorithm 52 | // see: http://bramcohen.livejournal.com/73318.html 53 | // "histogram": use histogram diff algorithm 54 | "diff_algorithm": "patience", 55 | 56 | // Determines whether GitGutter ignores whitespace in modified files. 57 | // Set "none" to ensure whitespace is considered in the diff 58 | // Set "cr" to only ignore the carriage return at the end of lines (git 2.18+) 59 | // Set "eol" to only ignore whitespace at the end of lines 60 | // Set "space" to ignore changes in amount of white space 61 | // Set "all" to ignore all white space 62 | "ignore_whitespace": "none", 63 | 64 | // 65 | // Gutter Area 66 | // 67 | 68 | // Delay update of gutter icons by the following amount (in milliseconds). 69 | "debounce_delay": 1000, 70 | 71 | // Focus Change mode evaluates changes every time a view gets the focus 72 | // Set false to disable evaluation when changing views 73 | "focus_change_mode": true, 74 | 75 | // Live mode evaluates changes every time file is modified, 76 | // Set false to disable evaluation after each input 77 | "live_mode": true, 78 | 79 | // Determines whether the git_gutter_next_change and git_gutter_prev_change 80 | // commands wrap around on reaching the beginning/ending of the file. 81 | "next_prev_change_wrap": true, 82 | 83 | // Do not set GitGutter icons in these regions 84 | // Useful for making sure GitGutter does not override other 85 | // regions, (e.g. Bookmarks, Linter/BracketHighlighter icons.) 86 | "protected_regions": [ 87 | "sublimelinter-warning-gutter-marks", 88 | "sublimelinter-error-gutter-marks", 89 | "sublime_linter.protected_regions", 90 | "bookmarks", 91 | "lsp_error", 92 | "lsp_warning", 93 | "lsp_info", 94 | "ecc" 95 | ], 96 | 97 | // Show GitGutter information in the minimap 98 | // =0: hide markers 99 | // >0: width of markers 100 | // -1: highlight full line 101 | "show_in_minimap": 1, 102 | 103 | // Add a special marker on untracked files 104 | "show_markers_on_untracked_file": true, 105 | 106 | // The gutter theme defines the icons to show for the different events. 107 | "theme": "Default.gitgutter-theme", 108 | 109 | // 110 | // Diff Popup 111 | // 112 | 113 | // (ST3, Build 3119+ only) 114 | // Enable hover popup, which shows a diff of the changed lines. 115 | "enable_hover_diff_popup": true, 116 | 117 | // (ST3 only) The default mode to open the diff popup. This will be 118 | // used for the hover popup, the command palette entry, 119 | // and the default key binding. 120 | // Possible modes are: 121 | // "default": Show the previous git state in the popup 122 | // "diff": Compare the git state to the working state and highlight 123 | // the differences in the popup 124 | "diff_popup_default_mode": "default", 125 | 126 | // Do not show the Diff Popup if a line contains these regions 127 | // Useful for making sure GitGutter does not fight with other popups. 128 | "diff_popup_protected_regions": [ 129 | "sublime_linter.protected_regions" 130 | ], 131 | 132 | // 133 | // Line Annotation 134 | // 135 | 136 | // (ST3, Build 3124+ only) 137 | // Display a phantom text at the end of the active line with information 138 | // about who changed it using the output of `git blame`. 139 | // "auto" -- show line annotation if word wrap is disabled (default) 140 | // "true" -- always show line annotation 141 | // "false" -- never show line annotation 142 | "show_line_annotation": "auto", 143 | 144 | // (ST3, Build 3124+ only) 145 | // The 1 based ruler index to align the line annotation text to. 146 | // 147 | // If no ruler is available the phantom is aligned to the end of line. 148 | // If the selected ruler does not exist the most right one is used. 149 | // 150 | // Valid values are: 151 | // "False" -- align to end of line (default) 152 | // "1" -- align to first ruler 153 | // "2" -- align of second ruler 154 | // ... 155 | "line_annotation_ruler": false, 156 | 157 | // (ST3, Build 3124+ only) 158 | // Whether to ignore whitespace changes when showing line annotations. 159 | // 160 | // Valid values are: 161 | // "false" -- don't ignore whitespace changes (default) 162 | // "true" -- ignore whitespace changes 163 | "line_annotation_ignore_whitespace": false, 164 | 165 | // LINE ANNOTATION TEXT TEMPLATE 166 | // If the value is an array it is joined to a single string and passed to 167 | // jinja2 template engine (if available) to render the blame message text. 168 | // GitGutter provides the following variables: 169 | // {{line_author}} -- the author, who introduced the change 170 | // {{line_author_mail}} -- the e-mail address of the author 171 | // {{line_author_age}} -- the time elapsed since the change 172 | // {{line_author_time}} -- the time string of the change 173 | // {{line_author_tz}} -- the timezone string of the change 174 | // {{line_commit}} -- the hash of the changing committing 175 | // {{line_committer}} -- the committer, who added the change to the repo 176 | // {{line_committer_mail}}-- the e-mail address of the committer 177 | // {{line_committer_age}} -- the time elapsed since the change 178 | // {{line_committer_time} -- the time string of commit 179 | // {{line_committer_tz} -- the timezone string of commit 180 | // {{line_summary}} -- the first line of the commit message 181 | // {{line_previous}} -- the hash of the previous commit 182 | "line_annotation_text": "{% if line_commit and line_commit[:7] != '0000000' %} {{line_commit[:7]}} | {% endif %}{{line_author}} ({{line_author_age}}) · {{line_summary}}", 183 | 184 | // 185 | // Status Bar 186 | // 187 | 188 | // Determines whether GitGutter shows status information in the status bar. 189 | // Set false to disable status information. 190 | // Set true to show information using the "status_bar_text" template. 191 | "show_status_bar_text": true, 192 | 193 | // STATUS BAR TEXT TEMPLATE 194 | // The array is joined to a single string and passed to jinja2 template 195 | // engine to render the status message text. The template can be modified using 196 | // jinja2 supported syntax. GitGutter provides the following variables: 197 | // {{st_git_status}} -- Sublime Text git integration available and enabled 198 | // {{repo}} -- repository name / folder name containing the .git directory 199 | // {{branch}} -- checked out branch you are working on 200 | // {{remote}} -- tracked remote of current branch you are working on or `None` 201 | // {{ahead}} -- number of commits the local branch is ahead of remote 202 | // {{behind}} -- number of commits the local branch is behind remote 203 | // {{added_files}} -- number of untracked files added to working tree 204 | // {{deleted_files}} -- number of files deleted from working tree 205 | // {{modified_files}} -- number of modified files in the working tree 206 | // {{staged_files}} -- number of files in the staging area 207 | // {{compare}} -- commit/branch/HEAD the file is compared to 208 | // {{state}} -- One of committed/modified/ignored/untracked 209 | // {{deleted}} -- number of deleted regions 210 | // {{inserted}} -- number of inserted lines 211 | // {{modified}} -- number of modified lines 212 | // {{line_author}} -- the author, who introduced the change 213 | // {{line_author_mail}} -- the e-mail address of the author 214 | // {{line_author_age}} -- the time elapsed since the change 215 | // {{line_author_time}} -- the time string of the change 216 | // {{line_author_tz}} -- the timezone string of the change 217 | // {{line_commit}} -- the hash of the changing committing 218 | // {{line_committer}} -- the committer, who added the change to the repo 219 | // {{line_committer_mail}}-- the e-mail address of the committer 220 | // {{line_committer_age}} -- the time elapsed since the change 221 | // {{line_committer_time} -- the time string of commit 222 | // {{line_committer_tz} -- the timezone string of commit 223 | // {{line_summary}} -- the first line of the commit message 224 | // {{line_previous}} -- the hash of the previous commit 225 | "status_bar_text": [ 226 | "{% if repo and branch %}", 227 | "{% if not st_git_status %}", 228 | "{{repo}}/{{branch}}", 229 | "{% if added_files + deleted_files + modified_files > 0 %}*{% endif %}, ", 230 | "{% endif %}", 231 | "{% if compare not in ('HEAD', branch, None) %}Comparing against {{compare}}, {% endif %}", 232 | "{% if state %}File is {{state}}{% endif %}", 233 | "{% if deleted > 0 %}, {{deleted}}-{% endif %}", 234 | "{% if inserted > 0 %}, {{inserted}}+{% endif %}", 235 | "{% if modified > 0 %}, {{modified}}≠{% endif %}", 236 | "{% if line_commit and line_commit[:7] != '0000000' %}, ⟢ {{line_commit[:7]}} | {{line_author}} ({{line_author_age}}){% endif %}", 237 | "{% endif %}" 238 | ] 239 | } 240 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 John Isaacks 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Edit", 4 | "mnemonic": "E", 5 | "id": "edit", 6 | "children": 7 | [ 8 | { "caption": "-" }, 9 | { 10 | "caption": "Revert Change to Commit", 11 | "command": "git_gutter_revert_change" 12 | }, 13 | { 14 | "caption": "Copy Content from Commit", 15 | "command": "git_gutter_copy_from_commit" 16 | }, 17 | { "caption": "-" } 18 | ] 19 | }, 20 | { 21 | "caption": "View", 22 | "mnemonic": "V", 23 | "id": "view", 24 | "children": 25 | [ 26 | { 27 | "caption": "GitGutter", 28 | "children": 29 | [ 30 | { 31 | "caption": "Enabled for View", 32 | "command": "git_gutter_enable_view" 33 | }, 34 | { "caption": "-" }, 35 | { 36 | "caption": "Show Line Annotation", 37 | "command": "git_gutter_blame" 38 | }, 39 | { 40 | "caption": "Show Diff Popup", 41 | "command": "git_gutter_diff_popup" 42 | }, 43 | { 44 | "caption": "Show Comparing Against", 45 | "command": "git_gutter_show_compare" 46 | }, 47 | { "caption": "-" }, 48 | { 49 | "caption": "Compare Against HEAD", 50 | "command": "git_gutter_compare_head" 51 | }, 52 | { 53 | "caption": "Compare Against Origin", 54 | "command": "git_gutter_compare_origin" 55 | }, 56 | { 57 | "caption": "Compare Against Commit", 58 | "command": "git_gutter_compare_commit" 59 | }, 60 | { 61 | "caption": "Compare Against File Commit", 62 | "command": "git_gutter_compare_file_commit" 63 | }, 64 | { 65 | "caption": "Compare Against Branch", 66 | "command": "git_gutter_compare_branch" 67 | }, 68 | { 69 | "caption": "Compare Against Tag", 70 | "command": "git_gutter_compare_tag" 71 | } 72 | ] 73 | } 74 | ] 75 | }, 76 | { 77 | "caption": "Goto", 78 | "mnemonic": "G", 79 | "id": "goto", 80 | "children": 81 | [ 82 | { 83 | "caption": "Goto Previous Git Difference", 84 | "command": "git_gutter_prev_change" 85 | }, 86 | { 87 | "caption": "Goto Next Git Difference", 88 | "command": "git_gutter_next_change" 89 | }, 90 | { "caption": "-" } 91 | ] 92 | }, 93 | { 94 | "caption": "Preferences", 95 | "mnemonic": "n", 96 | "id": "preferences", 97 | "children": 98 | [ 99 | { 100 | "caption": "Package Settings", 101 | "mnemonic": "P", 102 | "id": "package-settings", 103 | "children": 104 | [ 105 | { 106 | "caption": "GitGutter", 107 | "children": 108 | [ 109 | { 110 | "caption": "Settings", 111 | "command": "edit_settings", 112 | "args": { 113 | "base_file": "${packages}/GitGutter/GitGutter.sublime-settings", 114 | "default": "// GitGutter Settings - User\n{\n\t$0\n}\n" 115 | } 116 | }, 117 | { "caption": "-" }, 118 | { 119 | "caption": "Key Bindings", 120 | "command": "edit_settings", 121 | "args": { 122 | "base_file": "${packages}/GitGutter/Default (${platform}).sublime-keymap", 123 | "default": "// Key Bindings - User\n[\n\t$0\n]\n" 124 | } 125 | }, 126 | { "caption": "-" }, 127 | { 128 | "caption": "Popup Stylesheet", 129 | "command": "edit_settings", 130 | "args": { 131 | "base_file": "${packages}/GitGutter/gitgutter_popup.css", 132 | "default": "/* GitGutter Popup Stylesheet - User */\n\n" 133 | } 134 | }, 135 | { "caption": "-" }, 136 | { 137 | "caption": "Support Info", 138 | "command": "git_gutter_support_info" 139 | } 140 | ] 141 | } 142 | ] 143 | } 144 | ] 145 | } 146 | ] 147 | -------------------------------------------------------------------------------- /Preferences.sublime-settings: -------------------------------------------------------------------------------- 1 | // 2 | // Default values for Preferences.sublime-settings 3 | // 4 | // The file contains settings, which are available in user preferences, 5 | // project specific settings, view setting only. All settings placed here 6 | // can't be disabled in the Packages/User/GitGutter.sublime-settings! 7 | { 8 | // Enable/Disable GitGutter 9 | // Set false to disable evaluation of changes and hide all gutter icons, 10 | // status message and minimap marker. 11 | "git_gutter_enable": true 12 | } 13 | -------------------------------------------------------------------------------- /Preferences.sublime-settings-hints: -------------------------------------------------------------------------------- 1 | // 2 | // Default values for Preferences.sublime-settings 3 | // 4 | // This file exists to support PackageDev settings completions/linting/tooltips. 5 | // It MUST be an exact copy of GitGutter.sublime-settings with `git_gutter_` being 6 | // prefixed to all settings keys except "git_binary". It contains all the settings 7 | // which can be placed into Preferences or view/project settings. 8 | // 9 | // It MUST NOT be caulled Preferences.sublime-settings as long as 10 | // GitGutter.sublime-settings is used to store package settings as well. 11 | // 12 | { 13 | // CUSTOM PATH TO GIT BINARY. 14 | // An empty string will search the PATH environment for "git". 15 | // 16 | // "git_binary": "", 17 | // 18 | // The setting may be a direct string to a git binary. 19 | // An unix like path makes git run via Windows Subsystem for Linux 20 | // on Windows 10. It fails if WSL is not available. 21 | // 22 | // "git_binary": "/usr/bin/git", 23 | // 24 | // Or it may be a dictionary keyed off what sublime.platform() returns, 25 | // so it may be customized on a per-platform basis. 26 | // 27 | // "git_binary": { 28 | // "default": "", 29 | // "windows": "C:/Program Files/Git/cmd/git.exe", 30 | // "linux": "/usr/bin/git", 31 | // "osx": "/usr/bin/git" 32 | // }, 33 | "git_binary": "", 34 | 35 | // Additional environment variables to pass to git. 36 | // This list is merged with the global set of environment variables 37 | // provided by Sublime Text. 38 | // 39 | // Note: 40 | // 1. Keys with value `None` are removed from the global environment. 41 | // 2. If this dictionary is defined per view or project it is used 42 | // exclusively! It won't be merged with the global settings. 43 | "git_gutter_env": { 44 | "GIT_OPTIONAL_LOCKS": 0 45 | }, 46 | 47 | // The commit, branch, tag, or remote to compare against. 48 | // This setting changes the initial compare target and can 49 | // be temporarily overwritten by 'Compare against ...' commands 50 | // Valid constants are: 51 | // "HEAD": Compare against most recent commit 52 | // "master": Compare against master branch 53 | // "master@{upstream}": Compare against remote master branch 54 | "git_gutter_compare_against": "HEAD", 55 | 56 | // The algorithm used by git diff to determine the differences. 57 | // "default": let git decide (don't pass an algorithm) 58 | // "minimal": use minimal diff algorithm 59 | // "patience": use patience diff algorithm 60 | // see: http://bramcohen.livejournal.com/73318.html 61 | // "histogram": use histogram diff algorithm 62 | "git_gutter_diff_algorithm": "patience", 63 | 64 | // Determines whether GitGutter ignores whitespace in modified files. 65 | // Set "none" to ensure whitespace is considered in the diff 66 | // Set "cr" to only ignore the carriage return at the end of lines (git 2.18+) 67 | // Set "eol" to only ignore whitespace at the end of lines 68 | // Set "space" to ignore changes in amount of white space 69 | // Set "all" to ignore all white space 70 | "git_gutter_ignore_whitespace": "none", 71 | 72 | // 73 | // Gutter Area 74 | // 75 | 76 | // Delay update of gutter icons by the following amount (in milliseconds). 77 | "git_gutter_debounce_delay": 1000, 78 | 79 | // Focus Change mode evaluates changes every time a view gets the focus 80 | // Set false to disable evaluation when changing views 81 | "git_gutter_focus_change_mode": true, 82 | 83 | // Live mode evaluates changes every time file is modified, 84 | // Set false to disable evaluation after each input 85 | "git_gutter_live_mode": true, 86 | 87 | // Determines whether the git_gutter_next_change and git_gutter_prev_change 88 | // commands wrap around on reaching the beginning/ending of the file. 89 | "git_gutter_next_prev_change_wrap": true, 90 | 91 | // Do not set GitGutter icons in these regions 92 | // Useful for making sure GitGutter does not override other 93 | // regions, (e.g. Bookmarks, Linter/BracketHighlighter icons.) 94 | "git_gutter_protected_regions": [ 95 | "sublimelinter-warning-gutter-marks", 96 | "sublimelinter-error-gutter-marks", 97 | "sublime_linter.protected_regions", 98 | "bookmarks", 99 | "lsp_error", 100 | "lsp_warning", 101 | "lsp_info" 102 | ], 103 | 104 | // Show GitGutter information in the minimap 105 | // =0: hide markers 106 | // >0: width of markers 107 | // -1: highlight full line 108 | "git_gutter_show_in_minimap": 1, 109 | 110 | // Add a special marker on untracked files 111 | "git_gutter_show_markers_on_untracked_file": true, 112 | 113 | // The gutter theme defines the icons to show for the different events. 114 | "git_gutter_theme": "Default.gitgutter-theme", 115 | 116 | // 117 | // Diff Popup 118 | // 119 | 120 | // (ST3, Build 3119+ only) 121 | // Enable hover popup, which shows a diff of the changed lines. 122 | "git_gutter_enable_hover_diff_popup": true, 123 | 124 | // (ST3 only) The default mode to open the diff popup. This will be 125 | // used for the hover popup, the command palette entry, 126 | // and the default key binding. 127 | // Possible modes are: 128 | // "default": Show the previous git state in the popup 129 | // "diff": Compare the git state to the working state and highlight 130 | // the differences in the popup 131 | "git_gutter_diff_popup_default_mode": "default", 132 | 133 | // Do not show the Diff Popup if a line contains these regions 134 | // Useful for making sure GitGutter does not fight with other popups. 135 | "git_gutter_diff_popup_protected_regions": [ 136 | "sublime_linter.protected_regions" 137 | ], 138 | 139 | // 140 | // Line Annotation 141 | // 142 | 143 | // (ST3, Build 3124+ only) 144 | // Display a phantom text at the end of the active line with information 145 | // about who changed it using the output of `git blame`. 146 | // "auto" -- show line annotation if word wrap is disabled (default) 147 | // "true" -- always show line annotation 148 | // "false" -- never show line annotation 149 | "git_gutter_show_line_annotation": "auto", 150 | 151 | // (ST3, Build 3124+ only) 152 | // The 1 based ruler index to align the line annotation text to. 153 | // 154 | // If no ruler is available the phantom is aligned to the end of line. 155 | // If the selected ruler does not exist the most right one is used. 156 | // 157 | // Valid values are: 158 | // "False" -- align to end of line (default) 159 | // "1" -- align to first ruler 160 | // "2" -- align of second ruler 161 | // ... 162 | "git_gutter_line_annotation_ruler": false, 163 | 164 | // (ST3, Build 3124+ only) 165 | // Whether to ignore whitespace changes when showing line annotations. 166 | // 167 | // Valid values are: 168 | // "false" -- don't ignore whitespace changes (default) 169 | // "true" -- ignore whitespace changes 170 | "git_gutter_line_annotation_ignore_whitespace": false, 171 | 172 | // LINE ANNOTATION TEXT TEMPLATE 173 | // If the value is an array it is joined to a single string and passed to 174 | // jinja2 template engine (if available) to render the blame message text. 175 | // GitGutter provides the following variables: 176 | // {{line_author}} -- the author, who introduced the change 177 | // {{line_author_mail}} -- the e-mail address of the author 178 | // {{line_author_age}} -- the time elapsed since the change 179 | // {{line_author_time}} -- the time string of the change 180 | // {{line_author_tz}} -- the timezone string of the change 181 | // {{line_commit}} -- the hash of the changing committing 182 | // {{line_committer}} -- the committer, who added the change to the repo 183 | // {{line_committer_mail}}-- the e-mail address of the committer 184 | // {{line_committer_age}} -- the time elapsed since the change 185 | // {{line_committer_time} -- the time string of commit 186 | // {{line_committer_tz} -- the timezone string of commit 187 | // {{line_summary}} -- the first line of the commit message 188 | // {{line_previous}} -- the hash of the previous commit 189 | "git_gutter_line_annotation_text": "{% if line_commit and line_commit[:7] != '0000000' %} {{line_commit[:7]}} | {% endif %}{{line_author}} ({{line_author_age}}) · {{line_summary}}", 190 | 191 | // 192 | // Status Bar 193 | // 194 | 195 | // Determines whether GitGutter shows status information in the status bar. 196 | // Set false to disable status information. 197 | // Set true to show information using the "status_bar_text" template. 198 | "git_gutter_show_status_bar_text": true, 199 | 200 | // STATUS BAR TEXT TEMPLATE 201 | // The array is joined to a single string and passed to jinja2 template 202 | // engine to render the status message text. The template can be modified using 203 | // jinja2 supported syntax. GitGutter provides the following variables: 204 | // {{st_git_status}} -- Sublime Text git integration available and enabled 205 | // {{repo}} -- repository name / folder name containing the .git directory 206 | // {{branch}} -- checked out branch you are working on 207 | // {{remote}} -- tracked remote of current branch you are working on or `None` 208 | // {{ahead}} -- number of commits the local branch is ahead of remote 209 | // {{behind}} -- number of commits the local branch is behind remote 210 | // {{added_files}} -- number of untracked files added to working tree 211 | // {{deleted_files}} -- number of files deleted from working tree 212 | // {{modified_files}} -- number of modified files in the working tree 213 | // {{staged_files}} -- number of files in the staging area 214 | // {{compare}} -- commit/branch/HEAD the file is compared to 215 | // {{state}} -- One of committed/modified/ignored/untracked 216 | // {{deleted}} -- number of deleted regions 217 | // {{inserted}} -- number of inserted lines 218 | // {{modified}} -- number of modified lines 219 | // {{line_author}} -- the author, who introduced the change 220 | // {{line_author_mail}} -- the e-mail address of the author 221 | // {{line_author_age}} -- the time elapsed since the change 222 | // {{line_author_time}} -- the time string of the change 223 | // {{line_author_tz}} -- the timezone string of the change 224 | // {{line_commit}} -- the hash of the changing committing 225 | // {{line_committer}} -- the committer, who added the change to the repo 226 | // {{line_committer_mail}}-- the e-mail address of the committer 227 | // {{line_committer_age}} -- the time elapsed since the change 228 | // {{line_committer_time} -- the time string of commit 229 | // {{line_committer_tz} -- the timezone string of commit 230 | // {{line_summary}} -- the first line of the commit message 231 | // {{line_previous}} -- the hash of the previous commit 232 | "git_gitter_status_bar_text": [ 233 | "{% if repo and branch %}", 234 | "{% if not st_git_status %}", 235 | "{{repo}}/{{branch}}", 236 | "{% if added_files + deleted_files + modified_files > 0 %}*{% endif %}, ", 237 | "{% endif %}", 238 | "{% if compare not in ('HEAD', branch, None) %}Comparing against {{compare}}, {% endif %}", 239 | "{% if state %}File is {{state}}{% endif %}", 240 | "{% if deleted > 0 %}, {{deleted}}-{% endif %}", 241 | "{% if inserted > 0 %}, {{inserted}}+{% endif %}", 242 | "{% if modified > 0 %}, {{modified}}≠{% endif %}", 243 | "{% if line_commit and line_commit[:7] != '0000000' %}, ⟢ {{line_commit[:7]}} | {{line_author}} ({{line_author_age}}){% endif %}", 244 | "{% endif %}" 245 | ] 246 | } 247 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitGutter 2 | 3 | A [Sublime Text](http://www.sublimetext.com) plug-in to show information about files in a git repository: 4 | 5 | 1. _Gutter Icons_ indicating inserted, modified or deleted lines 6 | 2. _Diff Popup_ with details about modified lines 7 | 3. _Status Bar Text_ with information about file and repository 8 | 9 | and provides some commands like: 10 | 11 | 1. _Goto Change_ to navigate between modified lines 12 | 2. _Copy from Commit_ to copy the original content from the commit 13 | 3. _Revert to Commit_ to revert a modified hunk to the original state in a commit 14 | 15 | 16 | ## Gutter Icons & Status Bar Text 17 | 18 | ![screenshot](docs/assets/images/gutter_and_status.gif) 19 | 20 | The icons of the default theme have the following meaning: 21 | 22 | Icon | Description 23 | :-------------:|------------------------- 24 | ![inserted][] | inserted line 25 | ![changed][] | modified line 26 | ![deleted][] | deleted region borders 27 | ![ignored][] | ignored file 28 | ![untracked][] | untracked file 29 | 30 | [changed]: 31 | [deleted]: 32 | [ignored]: 33 | [inserted]: 34 | [untracked]: 35 | 36 | 37 | ## Diff Popup 38 | 39 | The diff popup shows the original content from the commit or the differences between it and the working content. 40 | 41 | ![diff_popup_screenshot](docs/assets/images/diff_popup.gif) 42 | 43 | The toolbar provides some commands to interact with or modify the changes. 44 | 45 | symbol | meaning of the symbol 46 | :-----:| --------------------------------------- 47 | × | close the popup 48 | ⤒ | goto to first change 49 | ↑ | goto to previous change 50 | ↓ | goto to next change 51 | ≈, ≉ | enable/disable difference highlighting 52 | ⎘ | copy the original content from the commit 53 | ⟲ | revert a modified hunk to the original state in a commit 54 | 55 | 56 | ## Documentation 57 | 58 | Please read https://jisaacks.github.io/GitGutter/ for detailed information about 59 | 60 | - [Installation](https://jisaacks.github.io/GitGutter/install) 61 | - [Usage](https://jisaacks.github.io/GitGutter/usage) 62 | - [Settings](https://jisaacks.github.io/GitGutter/settings) 63 | - [Troubleshooting](https://jisaacks.github.io/GitGutter/troubleshooting) 64 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.11.12 -------------------------------------------------------------------------------- /dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "*": { 3 | "*": [ 4 | "mdpopups" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /gitgutter_popup.css: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | 3 | This is the default stylesheet for GitGutter diff popup. 4 | 5 | It defines additional rules to merge with mdpopups' styleshets. 6 | 7 | NOTE: You can use all jinja2 template variables of mdpopups. 8 | 9 | Examples: 10 | var.is_dark 11 | var.is_light 12 | var.sublime_version (mdpopups 2.0.0+) 13 | var.mdpopups_version (mdpopups 2.0.0+) 14 | 15 | The popup's HTML structure: 16 | 17 |
18 |
19 |
20 | 21 | ... 22 | (revert) 23 | ... 24 |
25 |
26 | ... the content goes in here 27 |
28 |
29 |
30 | 31 | Copy the values you want to change into your user directory 32 | and change them there to overwrite the stylesheet. 33 | 34 | *********************************************************************/ 35 | 36 | /** 37 | * POPUP BACKGROUND 38 | * 39 | * Compatibility rules to fix default popup background color 40 | * for light and dark color schemes of Sublime Text < 3132. 41 | **/ 42 | html.dark { 43 | --mdpopups-hl-bg: color(var(--background) blend(white 95%)); 44 | --diffpopup-tb-bg: color(var(--mdpopups-hl-bg) blend(white 95%)); 45 | --diffpopup-ins-bg: color(green blend(black 60%)); 46 | --diffpopup-ins-fg: color(green blend(white 40%)); 47 | --diffpopup-del-bg: color(red blend(black 60%)); 48 | --diffpopup-del-fg: color(red blend(white 40%)); 49 | --diffpopup-chg-ins-bg: color(#769908 blend(black 95%)); 50 | --diffpopup-chg-ins-fg: color(#769908 blend(white 20%)); 51 | --diffpopup-chg-del-bg: color(orangered blend(black 80%)); 52 | --diffpopup-chg-del-fg: color(orangered blend(white 40%)); 53 | } 54 | 55 | html.light { 56 | --mdpopups-hl-bg: color(var(--background) blend(black 95%)); 57 | --diffpopup-tb-bg: color(var(--mdpopups-hl-bg) blend(black 91%)); 58 | --diffpopup-ins-bg: color(green blend(white 25%)); 59 | --diffpopup-ins-fg: color(green blend(black 80%)); 60 | --diffpopup-del-bg: color(red blend(white 25%)); 61 | --diffpopup-del-fg: color(red blend(black 80%)); 62 | --diffpopup-chg-ins-bg: color(#93921b blend(white 20%)); 63 | --diffpopup-chg-ins-fg: color(#93921b blend(black 85%)); /*#b3ca23*/ 64 | --diffpopup-chg-del-bg: color(orangered blend(white 20%)); 65 | --diffpopup-chg-del-fg: color(orangered blend(black 85%)); 66 | } 67 | 68 | /** 69 | * POPUP BORDER 70 | * 71 | * Set padding to the desired border-width. 72 | **/ 73 | div.git-gutter { 74 | margin: 0; 75 | padding: 0; 76 | } 77 | 78 | /** 79 | * TOOLBAR 80 | * 81 | * The navigation toolbar 82 | **/ 83 | .git-gutter div.toolbar { 84 | background-color: var(--diffpopup-tb-bg); 85 | color: color(var(--foreground) blend(var(--diffpopup-tb-bg) 30%)); 86 | margin: 0; 87 | padding: 0.0rem 0.4rem 0.2rem 0.4em; 88 | } 89 | 90 | /** 91 | * ICON BUTTONS 92 | * 93 | * 94 | * 95 | * Unicode chars are rendered smaller than normal text and sometimes ugly 96 | * depending on font-family, so force fonts known to render well. 97 | * This rule is used to fix the font-family and font-size. 98 | **/ 99 | .git-gutter div.toolbar symbol { 100 | font-family: "Roboto Mono", "Segoe UI Symbol", monospace; 101 | font-size: 1.2rem; 102 | margin: 0; 103 | padding: 0.0rem 0.1rem 0.0rem 0.1em; 104 | } 105 | 106 | /** 107 | * LINKS 108 | **/ 109 | .git-gutter a { 110 | color: var(--foreground); 111 | text-decoration: bold; 112 | } 113 | 114 | /** 115 | * TEXT VIEW 116 | * 117 | * Shows the old content or diff. 118 | **/ 119 | .git-gutter div.highlight { 120 | font-family: monospace; 121 | border-style: none; 122 | line-height: 1.3rem; 123 | margin: 0; 124 | padding: 0.7rem 0.7rem 0.6rem 0.7rem; 125 | } 126 | 127 | /** 128 | * TEXT LINE 129 | * 130 | * Each line is wrapped into

tag. 131 | **/ 132 | .git-gutter .highlight p { 133 | margin: 0; 134 | } 135 | 136 | /** 137 | * DIFF HIGHLIGHTING 138 | * 139 | * The following entries apply to the "highlight difference" 140 | * mode of the popup. 141 | **/ 142 | 143 | /* Highlight text, that has been inserted. */ 144 | .git-gutter .hi-ins { 145 | color: var(--diffpopup-ins-fg); 146 | background-color: var(--diffpopup-ins-bg); 147 | } 148 | 149 | /* Highlight text, that has been deleted. */ 150 | .git-gutter .hi-del { 151 | color: var(--diffpopup-del-fg); 152 | background-color: var(--diffpopup-del-bg); 153 | } 154 | 155 | /* Highlight text, that has been inserted and substitutes other text. */ 156 | .git-gutter .hi-chg-ins { 157 | color: var(--diffpopup-chg-ins-fg); 158 | background-color: var(--diffpopup-chg-ins-bg); 159 | } 160 | 161 | /* Highlight text, that has been deleted and is substituted by other text. */ 162 | .git-gutter .hi-chg-del { 163 | color: var(--diffpopup-chg-del-fg); 164 | background-color: var(--diffpopup-chg-del-bg); 165 | } 166 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.10.0": "messages/1.10.0.txt", 3 | "1.10.1": "messages/1.10.1.txt", 4 | "1.10.2": "messages/1.10.2.txt", 5 | "1.10.3": "messages/1.10.3.txt", 6 | "1.11.0": "messages/1.11.0.txt", 7 | "1.11.1": "messages/1.11.1.txt", 8 | "1.11.2": "messages/1.11.2.txt", 9 | "1.11.3": "messages/1.11.3.txt", 10 | "1.11.4": "messages/1.11.4.txt", 11 | "1.11.5": "messages/1.11.5.txt", 12 | "1.11.6": "messages/1.11.6.txt", 13 | "1.11.7": "messages/1.11.7.txt", 14 | "1.11.8": "messages/1.11.8.txt", 15 | "1.11.9": "messages/1.11.9.txt", 16 | "1.11.10": "messages/1.11.10.txt", 17 | "1.2.0-pre": "messages/1.2.0-pre.txt", 18 | "1.2.1": "messages/1.2.1.txt", 19 | "1.2.2": "messages/1.2.2.txt", 20 | "1.2.3": "messages/1.2.3.txt", 21 | "1.2.4": "messages/1.2.4.txt", 22 | "1.3.0": "messages/1.3.0.txt", 23 | "1.4.0": "messages/1.4.0.txt", 24 | "1.5.0": "messages/1.5.0.txt", 25 | "1.5.1": "messages/1.5.1.txt", 26 | "1.6.0": "messages/1.6.0.txt", 27 | "1.6.0-rc.1": "messages/1.6.0-rc.1.txt", 28 | "1.7.0": "messages/1.7.0.txt", 29 | "1.7.1": "messages/1.7.1.txt", 30 | "1.7.10": "messages/1.7.10.txt", 31 | "1.7.11": "messages/1.7.11.txt", 32 | "1.7.12": "messages/1.7.12.txt", 33 | "1.7.13": "messages/1.7.13.txt", 34 | "1.7.14": "messages/1.7.14.txt", 35 | "1.7.15": "messages/1.7.15.txt", 36 | "1.7.2": "messages/1.7.2.txt", 37 | "1.7.3": "messages/1.7.3.txt", 38 | "1.7.4": "messages/1.7.4.txt", 39 | "1.7.5": "messages/1.7.5.txt", 40 | "1.7.6": "messages/1.7.6.txt", 41 | "1.7.7": "messages/1.7.7.txt", 42 | "1.7.8": "messages/1.7.8.txt", 43 | "1.7.9": "messages/1.7.9.txt", 44 | "1.8.0": "messages/1.8.0.txt", 45 | "1.8.0-rc.1": "messages/1.8.0-rc.1.txt", 46 | "1.8.0-rc.2": "messages/1.8.0-rc.2.txt", 47 | "1.9.0": "messages/1.9.0.txt", 48 | "1.9.1": "messages/1.9.1.txt", 49 | "1.9.2": "messages/1.9.2.txt", 50 | "1.9.3": "messages/1.9.3.txt", 51 | "1.9.4": "messages/1.9.4.txt", 52 | "1.9.5": "messages/1.9.5.txt", 53 | "install": "messages/install.txt" 54 | } 55 | -------------------------------------------------------------------------------- /messages/1.10.0.txt: -------------------------------------------------------------------------------- 1 | 1.10.0: 2 | ====== 3 | 4 | A N N O U N C E M E N T 5 | 6 | This release introduces some changes to ensure GitGutter to coexist well 7 | with the new Incremental Diff feature of Sublime Text 3200. 8 | 9 | If `"mini_diff"` is set to `"auto"` or `True` in Preferences.sublime-settings 10 | GitGutter will no longer add icons to the normal gutter. 11 | 12 | Please note: 13 | 14 | 1. GitGutter themes don't have any effect anymore if ST's mini_diff is enabled. 15 | 2. The results of ST's mini_diff and GitGutter's diff popup or the markers 16 | in the minimap may differ a little bit. 17 | 18 | The status bar text template was tweaked to no longer show the branch name 19 | if Sublime Text 3189+ is detected. 20 | 21 | All other features keep untouched so far in order to not break any existing 22 | feature. 23 | 24 | As most of the main features of this plugin have been moved to ST's core, 25 | this plugin seems to become deprecated / obsolete for ST3200+ in its current 26 | form. 27 | 28 | 29 | --------------------------------------------------------------------------- 30 | 31 | C H A N G E L O G 32 | 33 | Fix: 34 | - Invalid key binding templates (#530) 35 | - Temporary files no longer cleaned when using ST 3189 36 | 37 | Other: 38 | - Typo fix 39 | - Make EasyClangComplete regions protected by default 40 | 41 | Enhancement: 42 | - Show line annotation manually via `ctrl+shift+alt+c, ctrl+b` 43 | - Don't add gutter icons, if ST's `mini diff` is enabled 44 | - Add Compara Against support to mini_diff 45 | - Don't show branch in status bar for ST3189+ 46 | -------------------------------------------------------------------------------- /messages/1.10.1.txt: -------------------------------------------------------------------------------- 1 | 1.10.1: 2 | 3 | Other: 4 | - change typo 'inore' to 'ignore' in settings.md 5 | 6 | Enhancement: 7 | - Auto-enable branch name in status bar (#542) 8 | 9 | Fix: 10 | - Gutter icons visible if mini_diff is true (#541) 11 | - Mini diff shows all lines as inserted if file is untracked 12 | -------------------------------------------------------------------------------- /messages/1.10.2.txt: -------------------------------------------------------------------------------- 1 | 1.10.2: 2 | 3 | DOC: 4 | - Update settings and troubleshooting for ST3.2+ 5 | 6 | Fix: 7 | - line height is increased by annotations (#523) 8 | -------------------------------------------------------------------------------- /messages/1.10.3.txt: -------------------------------------------------------------------------------- 1 | 1.10.3: 2 | ======= 3 | 4 | This release can help improving performance in some situations, if the 5 | repository you work with is located on a network share. 6 | 7 | To fix possible performance issues make sure not to use one of the following 8 | variables in the `status_bar_text` setting: 9 | 10 | - {{remote}} 11 | - {{ahead}} 12 | - {{behind}} 13 | - {{added_files}} 14 | - {{deleted_files}} 15 | - {{modified_files}} 16 | - {{staged_files}} 17 | 18 | By avoiding those variables, GitGutter no longer calls `git status -b -s -u` 19 | but only `git rev-parse --abbrev-ref HEAD` to read the branch name which 20 | is much faster. 21 | 22 | The following examples shows a status_bar_text template, which the expensive 23 | line was removed from. 24 | 25 | "{% if added_files + deleted_files + modified_files > 0 %}*{% endif %}, ", 26 | 27 | Example: 28 | 29 | "status_bar_text": [ 30 | "{% if repo and branch %}", 31 | "{% if not st_git_status %}", 32 | "{{repo}}/{{branch}}", 33 | 34 | "{% endif %}", 35 | "{% if compare not in ('HEAD', branch, None) %}Comparing against {{compare}}, {% endif %}", 36 | "{% if state %}File is {{state}}{% endif %}", 37 | "{% if deleted > 0 %}, {{deleted}}-{% endif %}", 38 | "{% if inserted > 0 %}, {{inserted}}+{% endif %}", 39 | "{% if modified > 0 %}, {{modified}}≠{% endif %}", 40 | "{% if line_author and line_author_age %}, ⟢ {{line_author}} ({{line_author_age}}){% endif %}", 41 | "{% endif %}" 42 | ] 43 | 44 | --------------------------------------------------------------------------- 45 | 46 | C H A N G E L O G 47 | 48 | Enhancement: 49 | - Read branch statistics on demand (#458) 50 | -------------------------------------------------------------------------------- /messages/1.11.0.txt: -------------------------------------------------------------------------------- 1 | 1.11.0: 2 | ======= 3 | 4 | This release drops support for ST2 and ST3 before build 3176. 5 | 6 | The release 1.10.3 was archived and will be available for older releases 7 | of Sublime Text, but it won't receive any updates anymore. 8 | 9 | The main functional change which comes with this release is a dedicated 10 | worker thread to run all git commands in. This change makes sure not to 11 | cause GUI locks if a git command takes a little longer. 12 | 13 | --------------------------------------------------------------------------- 14 | 15 | C H A N G E L O G 16 | 17 | Enhancement: 18 | - Use dedicated worker thread for async tasks 19 | - Better submodule reloading 20 | 21 | Internal: 22 | - Drop support for Sublime Text ST2 and ST3 pre-releases 23 | -------------------------------------------------------------------------------- /messages/1.11.1.txt: -------------------------------------------------------------------------------- 1 | 1.11.1 2 | ====== 3 | 4 | Fix: 5 | - A saved as.. file is compared against the original one (#549) 6 | -------------------------------------------------------------------------------- /messages/1.11.10.txt: -------------------------------------------------------------------------------- 1 | 1.11.10: 2 | ------- 3 | 4 | Fix: 5 | - Re-create temporary directories on demand 6 | 7 | Other: 8 | - Drop advertisement from install message 9 | - Drop obsolete dependencies 10 | -------------------------------------------------------------------------------- /messages/1.11.2.txt: -------------------------------------------------------------------------------- 1 | 1.11.2: 2 | 3 | Fix: 4 | - Status bar text template key name 5 | 6 | Enhancement: 7 | - Show commit hash in blame annotations 8 | - Use new line annotations of ST4 9 | -------------------------------------------------------------------------------- /messages/1.11.3.txt: -------------------------------------------------------------------------------- 1 | 1.11.3: 2 | ====== 3 | 4 | Fix: 5 | - line annotation background (#552) 6 | -------------------------------------------------------------------------------- /messages/1.11.4.txt: -------------------------------------------------------------------------------- 1 | 1.11.4: 2 | ====== 3 | 4 | Fix: 5 | - Empty compare target (#560) 6 | -------------------------------------------------------------------------------- /messages/1.11.5.txt: -------------------------------------------------------------------------------- 1 | 1.11.5: 2 | 3 | Enhancement: 4 | - Use system date template in annotations 5 | 6 | Fix: 7 | - Support string values in line annotation settings 8 | -------------------------------------------------------------------------------- /messages/1.11.6.txt: -------------------------------------------------------------------------------- 1 | 1.11.6: 2 | 3 | Other: 4 | - Update dependencies.json 5 | 6 | Enhancement: 7 | - Cleanup cache on_exit() 8 | -------------------------------------------------------------------------------- /messages/1.11.7.txt: -------------------------------------------------------------------------------- 1 | 1.11.7: 2 | 3 | Fix: 4 | - Add missing `markupsafe` dependency 5 | - SimpleLineAnnotationTemplate no longer raises errors 6 | - Fix annotation background 7 | -------------------------------------------------------------------------------- /messages/1.11.8.txt: -------------------------------------------------------------------------------- 1 | 1.11.8: 2 | ------- 3 | 4 | Fix: 5 | - Support info displaying no longer needed deps 6 | - Don't use extenral diff tool when performing diff (#573) 7 | - Broken link in troubleshooting.md (#574) 8 | - WSL path translation 9 | - Compatibility issue with python 3.8 10 | -------------------------------------------------------------------------------- /messages/1.11.9.txt: -------------------------------------------------------------------------------- 1 | 1.11.9: 2 | ------- 3 | 4 | Fix: 5 | - GitGutter not working 6 | 7 | Other: 8 | - Drop advertisement 9 | -------------------------------------------------------------------------------- /messages/1.2.0-pre.txt: -------------------------------------------------------------------------------- 1 | * Fixes a bug dealing with UTF-8 with BOM 2 | 3 | * Fixes ST3 not working with Cygwin git 4 | 5 | * Make non_blocking mode work in ST2 6 | -------------------------------------------------------------------------------- /messages/1.2.1.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /messages/1.2.2.txt: -------------------------------------------------------------------------------- 1 | * Adds Protected Regions 2 | 3 | - Option to specify regions that should not be overridden. 4 | 5 | - This fixes GitGutter hiding Linters and other Gutter icons. 6 | 7 | - You must manually specify what regions are protected in the settings. 8 | 9 | * Adds Minimap Support 10 | 11 | - Can be turned off in settings. 12 | -------------------------------------------------------------------------------- /messages/1.2.3.txt: -------------------------------------------------------------------------------- 1 | * Better cleanup of tempfiles 2 | 3 | * Make Sublime Linter regions protected by default 4 | 5 | * Enhance git_binary setting 6 | 7 | - Allow git_binary to be customized on a per-platform basis. 8 | 9 | - Add support for environment variables in git_binary setting 10 | -------------------------------------------------------------------------------- /messages/1.2.4.txt: -------------------------------------------------------------------------------- 1 | * Fix error dialog for missing git binary from reopening 2 | in an endless loop. Should only show once per session now. 3 | 4 | * Fix an bug that was introduced that broke GitGutter in ST2 5 | -------------------------------------------------------------------------------- /messages/1.3.0.txt: -------------------------------------------------------------------------------- 1 | * Diff Popup 2 | - You can now get a popup to see what the code was before it was changed 3 | - You can do so via a command pallete, keyboard shortcut, and after 4 | Sublime Text 3116 is released it will auto show when hovering the gutter. 5 | (you can turn that off.) See the READ ME for more info. 6 | 7 | * Protected Regions 8 | - Better support for protected regions and auto set bookmarks as protected. 9 | 10 | * Bugfix 11 | - Fix a bug that was incorrectly warning ST2 users that it could not find git 12 | -------------------------------------------------------------------------------- /messages/1.4.0.txt: -------------------------------------------------------------------------------- 1 | = Several Major Internal Changes 2 | - Several parts of GitGutter have been rewritten to make it perform 3 | better, and more able to make changes going forward including 4 | the following specific improvements: 5 | 6 | * Diff Popup 7 | - Add a mode to toggle the highlight mode of the diff popup between 8 | "syntax highlighting" and "differences highlighting". 9 | 10 | * Async Invocations 11 | - GitGutter now runs in a separate thread using promises to update 12 | the gutter when finished. 13 | - non_blocking setting is gone, it is always non blocking now. 14 | 15 | * Temporary Files 16 | - Make sure temporary files are deleted after view is closed. 17 | -------------------------------------------------------------------------------- /messages/1.5.0.txt: -------------------------------------------------------------------------------- 1 | 1.5.0 2 | ----- 3 | 4 | ------------------------------------------------------------------------ 5 | !!! WHAT HAPPEND TO GITGUTTER-EDGE !!! 6 | 7 | We decided to replace GitGutter-Edge by pre-releases ... 8 | 9 | 1. to avoid issues with functions which depend on the package name. 10 | 2. because branch based packages are deprecated by Package Control. 11 | 3. to have more control about when to publish new features for testing. 12 | 13 | For more details and information how to get access to pre-releases, 14 | please have a look into the README on 15 | 16 | https://packagecontrol.io/packages/GitGutter 17 | 18 | ------------------------------------------------------------------------ 19 | 20 | Enhancement: 21 | - Avoid creation of temporary files, if view does not show a file from a valid git repository. 22 | - Optimize number of git and diff calls. 23 | - Determine, whether a view's file really exists on disk. 24 | - Make "compare to" functionality work per git directory (Issue #327) 25 | - Optimize some encoding/decoding stuff. 26 | - Use jinja2 template engine to render status bar messages if library is available. 27 | - Read file from git index only, if the compare target has changed. 28 | - Event driven tracking of changed compare target replaces polling. 29 | - Update the temporary buffer file only, if the view has changed. 30 | - Run git diff only if one of the temporary files changed. 31 | - Query git for compare commit hash only if not comparing against commit. 32 | - Don't call git for diff popup at all. 33 | - Refactoring the EventListener to best fit the current structure. 34 | - Add some idle time between git calls to reduce lacks on ST2 35 | 36 | Feature: 37 | - Add support for custom icon packages (Issue #256) 38 | - Add 'Compare against file commit' and enable filtering by commit id (Issue #142, #281) 39 | - Add Side-By-Side settings support and show in command pallet 40 | - Add support to set marker size in minimap (Issue #263) 41 | - Show file status (committed/modified/untracked/ignored) on status bar (Issue #226) 42 | 43 | Fix: 44 | - Disable GitGutter, if git binary was not found (Issue #94, #352) 45 | - Create only one instance of GitGutterSettings (Issue #334) 46 | - error invoking git on Windows systems (Issue #244, #357) 47 | - Fix issue with all untracked and ignored files being marked incorrectly (Issue #267, #279, #353). 48 | - Show the correct branch or tag name in the 'compare against' status message. 49 | - Avoid exception due to missing dependencies. (Issue #366) 50 | - Fall back to simple status bar text if jinja2 is not available (Issue #366) 51 | - Run all git commands from within working tree (Issue #239, #248, #290) 52 | - Follow symlinks correctly to the source file in the working tree (Issue #373). 53 | - diff popup doesn't show modifications on first line. 54 | - Noticeable flicker/delay in icons when working with files (Issue #340) 55 | - Problem with smudge/clean filters (Issue #74) 56 | - Use cached git diff result for 'Goto next/prev git difference' command. 57 | - Ensure not to start more than one evaluation at a time. 58 | - Disable GitGutter for binary files and scratch views. (Issue #189) 59 | - GitGutter ignores on_modified() event in ST2, because view visibility check fails. (Issue #349) 60 | - Securing git execution. (Issue #348) 61 | - Show markers on new, ignored and untracked files only, if show_markers_on_untracked_file is true. 62 | 63 | README: 64 | - Refactoring 65 | -------------------------------------------------------------------------------- /messages/1.5.1.txt: -------------------------------------------------------------------------------- 1 | 1.5.1 2 | ----- 3 | 4 | Fix: 5 | - Files are randomly marked as modified (Issue #381) 6 | - Print error message and abort evaluation on failed git call. 7 | - Improve error handling on temporary file creation (Issue #384) 8 | 9 | Enhancement: 10 | - Hide 'Compare against ... ' current branch in status message. 11 | 12 | Docs: 13 | - Add Boxy Theme to the list of supported 14 | -------------------------------------------------------------------------------- /messages/1.6.0-rc.1.txt: -------------------------------------------------------------------------------- 1 | 1.6.0-rc.1 2 | ---------- 3 | 4 | Feature: 5 | - Add option to enable/disable GitGutter (Issue #161) 6 | - Add gutter icon themes 'Bars' and 'Bars Thin' (Issue #392) 7 | - Add ignore_whitespace: "space" option. 8 | 9 | Fix: 10 | - Per-Project settings don't work at all 11 | - Avoid double loading of python modules. (Issue #396) 12 | - Settings won't apply without restart of ST (Issue #184) 13 | - Compare Against quick panels are broken by multiline commit messages 14 | - Bug in protected region handling for ignored/untracked files 15 | - Compatibility issue with ST3 on Windows XP due to symlinks (PR #374) 16 | 17 | Enhancement: 18 | - Reduce package loading time by 50% 19 | - Update gutter icons of all visible views after selecting new compare target 20 | 21 | README: 22 | - Update Per-Project paragraph to reflect latest changes 23 | -------------------------------------------------------------------------------- /messages/1.6.0.txt: -------------------------------------------------------------------------------- 1 | 1.6.0: 2 | ------ 3 | 4 | Fix: 5 | - Possible import issue. 6 | - Remove git diff command line argument '--indent-heuristic'. (Issue #403) 7 | - Typo in GitGutter.sublime-settings 8 | 9 | README: 10 | - Fix minimum requirements for diff popup to ST 3080+ 11 | -------------------------------------------------------------------------------- /messages/1.7.0.txt: -------------------------------------------------------------------------------- 1 | 1.7.0: 2 | ------ 3 | 4 | A T T E N T I O N 5 | 6 | Due to a change in module structure you must restart Sublime Text to make 7 | sure GitGutter works properly! 8 | 9 | This release includes a major update to Diff Popup to ... 10 | 11 | 1. ensure compatibility with upcoming mdpopups 2.0.0 12 | 2. improve its overall look & feel 13 | 3. clean up the code base a little bit 14 | 15 | Diff Popup requires 16 | 17 | - Sublime Text 3119 + 18 | - mdpopups 1.9.0 + 19 | 20 | Diff Popup CSS 21 | 22 | If you are using a customized `gitgutter_popup.css` you need to update it 23 | due to some required internal changes. To do so please 24 | 25 | 1. Open Command Palette 26 | 2. Type Preferences: GitGutter Popup Stylesheet 27 | 28 | Some class names changed to keep as short as possible. 29 | 30 | Jinja Templates are now handled by mdpopups. Thus some of the variables 31 | defined by Diff Popup might no longer work. But you can use all template 32 | variables being supported by mdpopups now. 33 | 34 | 35 | C H A N G E S 36 | 37 | Enhancement: 38 | - Reduce package size (ignore unnecessary in the repository) 39 | - Use git version to check if git works properly (Issue #411) 40 | - Avoid diff popup flickering if diff highlighting is toggled 41 | - Prepare for mdpopups 2.0.0 and add code wrapping support 42 | - Diff popup adapts its colors to active color scheme 43 | - Diff popup uses background colors to highlight diff 44 | - Diff popup distinguishes inserted/deleted/replaced text in diff 45 | - Diff popup uses python's Differ to generate human readable diff 46 | with better results in most situations 47 | 48 | Feature: 49 | - Add support information module and ISSUE_TEMPLATE. 50 | 51 | Fix: 52 | - Correct resolution of gutter icon image resources 53 | - Line height to switch gutter icons for deleted regions too small 54 | - All export-ignored files are marked as new files (Issue #409) 55 | 56 | Other: 57 | - Remove support for text buttons from diff popup 58 | - Let mdpopups handle jinja templates in gitgutter_popup.css 59 | 60 | README: 61 | - Add some information to the troubleshooting section 62 | - Update requirements for diff popup 63 | -------------------------------------------------------------------------------- /messages/1.7.1.txt: -------------------------------------------------------------------------------- 1 | 1.7.1: 2 | ------ 3 | 4 | Sorry for the trouble. 5 | 6 | Fix: 7 | - git cat-file --filters argument is supported on git 2.11.0+ only (Issue #416) 8 | - Support Info is unable to read package version and git version 9 | -------------------------------------------------------------------------------- /messages/1.7.10.txt: -------------------------------------------------------------------------------- 1 | 1.7.10: 2 | ------- 3 | 4 | Enhancement: 5 | - Add new SublimeLinter protected regions key 6 | - Add support for custom environment variables 7 | - Add protected regions for diff popup (Issue #476) 8 | 9 | Fix: 10 | - mangled {{branch}} text when `git status` output is colored (Issue #474) 11 | 12 | README: 13 | - Fix some typos 14 | - Add description of "diff_popup_protected_regions" setting 15 | - Add description of "env" setting 16 | -------------------------------------------------------------------------------- /messages/1.7.11.txt: -------------------------------------------------------------------------------- 1 | 1.7.11: 2 | ------- 3 | 4 | Fix: 5 | - Settings diff algorithm getter fix 6 | - Linter typo in diff popups factory 7 | - Clear import caches upon module reloading 8 | 9 | Internal: 10 | - Remove deprecated mdpopups allow_code_wrap argument 11 | -------------------------------------------------------------------------------- /messages/1.7.12.txt: -------------------------------------------------------------------------------- 1 | 1.7.12: 2 | ------- 3 | 4 | README: 5 | - Add a reason for GitGutter to keep quiet 6 | - Fix documentation for `diff_algorithm` 7 | 8 | Enhancement: 9 | - Place all temporary files into a dedicated sub directory 10 | - Use XDG_RUNTIME_DIR for temporary files if available 11 | - Delete old temporary files upon startup 12 | -------------------------------------------------------------------------------- /messages/1.7.13.txt: -------------------------------------------------------------------------------- 1 | 1.7.13: 2 | ------- 3 | 4 | Fix: 5 | - New `tempfile` module conflicts with stdlib in ST2 6 | - A typo in the recent release message of 1.7.12 7 | -------------------------------------------------------------------------------- /messages/1.7.14.txt: -------------------------------------------------------------------------------- 1 | 1.7.14: 2 | ------- 3 | 4 | Fix: 5 | - Diff Popup's content looks ugly due to Courier font 6 | - Use Segoe UI Symbol for popup toolbars 7 | - ST2 compatibility issue in TempFile class 8 | -------------------------------------------------------------------------------- /messages/1.7.15.txt: -------------------------------------------------------------------------------- 1 | 1.7.15: 2 | ------- 3 | 4 | Fix: 5 | - Compatibility issue of GitGutter with Snap on Ubuntu (#495) 6 | - Write access regression when creating temp dir (#498) 7 | 8 | Enhancement: 9 | - Reduce number of git --version calls (#493) 10 | -------------------------------------------------------------------------------- /messages/1.7.2.txt: -------------------------------------------------------------------------------- 1 | 1.7.2: 2 | ------ 3 | 4 | N O T E 5 | 6 | 1) Diff Algrorithm 7 | 8 | All people who modified the "patience" setting may need to update their user settings. 9 | 10 | The "patience" setting is replaced by "diff_algorithm" to allow to set any of the known 11 | diff algorithms ("default", "minimal", "patience", "histogram") to be used by git. 12 | 13 | The default value remains "patience". 14 | 15 | 2) Diff Popup 16 | 17 | Please note that mdpopups 2.0.0 was released which requires at least Sublime Text 3124. 18 | Therefore diff popup will no longer work on earlier builts if 2.0.0+ is detected. 19 | 20 | 21 | Fix: 22 | - Diff popup cuts off start of text (Issue #419) 23 | - Diff popup is not displayed if view is scrolled horizontal. 24 | - Copy text from diff popup fails 25 | - Limit mdpopups usage required for 2.0.0+ 26 | 27 | Enhancement: 28 | - Call popup creation synchrounous to reduce delay and help avoid fighting with others 29 | - Scroll to beginning of reverted hunk if not in visible region. 30 | - Add support for git diff --histogram algorithm (Issue #422) 31 | -------------------------------------------------------------------------------- /messages/1.7.3.txt: -------------------------------------------------------------------------------- 1 | 1.7.3: 2 | ------ 3 | 4 | Enhancement: 5 | - Add support for PackageDev settings linting & completion 6 | 7 | Fix: 8 | - Don't use Consolas font in diff popup 9 | - Correctly decode empty git output 10 | - IndexError exception in console (Issue #429) 11 | - Reduce gutter icon jumping while editing text 12 | 13 | README: 14 | - Fix some smaller graphical glitches 15 | - Use animated screenshots captured with latest Sublime Text 16 | - Add trouble shooting section for working tree 17 | -------------------------------------------------------------------------------- /messages/1.7.4.txt: -------------------------------------------------------------------------------- 1 | 1.7.4: 2 | ------ 3 | 4 | README: 5 | - Add troubleshooting for GitGutter keeping disabled 6 | 7 | Internal: 8 | - Cache global settings as function attribute 9 | - Log the reason for GitGutter to be disabled ("debug": true) 10 | 11 | Fix: 12 | - Gutter icons are not displayed in Sublime Text 2 13 | -------------------------------------------------------------------------------- /messages/1.7.5.txt: -------------------------------------------------------------------------------- 1 | 1.7.5: 2 | ------ 3 | 4 | A N N O U N C E M E N T 5 | 6 | In manner of consistency all functions are now available via 7 | 8 | * command pallet 9 | * main menu 10 | * key bindings 11 | 12 | The command pallet is probably the best and most quickly available cheat 13 | sheet for key bindings which is the reason to add all commands there. 14 | 15 | The following main menu entries are available: 16 | 17 | Edit 18 | +- Revert Change to Commit (ctrl+shift+alt+z) 19 | +- Copy Content from Commit (ctrl+shift+c) 20 | 21 | View 22 | +- GitGutter 23 | +- Enable 24 | +- Show Diff Popup (ctrl+shift+alt+c, ctrl+d) 25 | +- ... 26 | 27 | Goto 28 | +- Goto Previous Git Difference (ctrl+shift+alt+k) 29 | +- Goto Next Git Difference (ctrl+shift+alt+j) 30 | 31 | Preferences 32 | +- Package Settings 33 | +- GitGutter 34 | +- ... 35 | 36 | --------------------------------------------------------------------------- 37 | 38 | N O T E 39 | 40 | The command "GitGutter: Enable/Disable" was added to provide the opportunity 41 | to disable GitGutter for single views. 42 | 43 | As this command uses ST's "toggle_setting" the setting "enable" 44 | in GitGutter.sublime-settings moved to Preferences.sublime-settings. 45 | 46 | If you need to disable GitGutter globally, you'll need to do that by adding 47 | 48 | "git_gutter_enable": false 49 | 50 | to the Preferences.sublime-settings. 51 | 52 | This setting works in Project settings as well. 53 | 54 | --------------------------------------------------------------------------- 55 | 56 | C H A N G E L O G 57 | 58 | Enhancement: 59 | - Add a text command with key binding to revert changes 60 | - Add a text command with key binding to copy content from commit 61 | - Add main menu entry/command pallet entry/key binding for all commands 62 | - Toggle GitGutter via main menu and command pallet 63 | - Flatten the 'Compare against' main menu structure 64 | 65 | Fix: 66 | - Compare against Origin uses @{upstream} (Issue #371) 67 | - Compare against… panels don't show immediately. (#446) 68 | 69 | README: 70 | - Update Compare against paragraph 71 | - Add Goto/Copy/Revert Change 72 | -------------------------------------------------------------------------------- /messages/1.7.6.txt: -------------------------------------------------------------------------------- 1 | 1.7.6: 2 | ------ 3 | 4 | C H A N G E L O G 5 | 6 | Fix: 7 | - Enable GitGuter incase "git_gutter_enable" is not present. 8 | - Support Info module provides no GitGutter version. 9 | - Calling git_gutter_diff_popup with missing mdpopups causes exception. 10 | 11 | README: 12 | - Update Troubleshooting for MacOS Xcode license. 13 | - Added Monokai Pro to supported color schemes list. 14 | 15 | Enhancement: 16 | - Improve diff popup visibility on light color schemes 17 | - Show commit details in Compare against branch/tag 18 | -------------------------------------------------------------------------------- /messages/1.7.7.txt: -------------------------------------------------------------------------------- 1 | 1.7.7: 2 | ------ 3 | 4 | A N N O U N C E M E N T 5 | 6 | This release adds support for the following template variables to be used 7 | to customize the status bar text. 8 | 9 | {{remote}} -- tracked remote of current branch or `None` 10 | {{ahead}} -- number of commits the local branch is ahead of remote 11 | {{behind}} -- number of commits the local branch is behind remote 12 | {{added_files}} -- number of untracked files added to working tree 13 | {{deleted_files}} -- number of files deleted from working tree 14 | {{modified_files}} -- number of modified files in the working tree 15 | {{staged_files}} -- number of files in the staging area 16 | 17 | --------------------------------------------------------------------------- 18 | 19 | C H A N G E L O G 20 | 21 | Internal: 22 | - Add setup.cfg to configure linters 23 | - Drop support for mdpopups 1.x (2.0+ required) 24 | 25 | Fix: 26 | - Forwared arguments from commands to git_gutter 27 | 28 | Enhancement: 29 | - Add count and wrap arguments to goto commands. (Issue #458) 30 | - Use own command to enable/disable GitGutter per view 31 | - Add more status bar variables (Issue #457) 32 | -------------------------------------------------------------------------------- /messages/1.7.8.txt: -------------------------------------------------------------------------------- 1 | 1.7.8: 2 | ------ 3 | 4 | Fix: 5 | - Branch info for status bar not detected on macOS (Issue #462) 6 | -------------------------------------------------------------------------------- /messages/1.7.9.txt: -------------------------------------------------------------------------------- 1 | 1.7.9: 2 | ------ 3 | 4 | Internal: 5 | - Update json_test 6 | - Ignore .ropeproject folder 7 | 8 | Fix: 9 | - Branch name with dot is truncated on status bar (Issue #468) 10 | -------------------------------------------------------------------------------- /messages/1.8.0-rc.1.txt: -------------------------------------------------------------------------------- 1 | 1.8.0-rc.1: 2 | =========== 3 | 4 | A N N O U N C E M E N T 5 | 6 | This pre-release adds enables usage of Windows Subsystem for Linux (WSL). 7 | 8 | To enable WSL just setup a unix-like path in the "git_binary" setting. 9 | 10 | "git_binary": "/usr/bin/git" 11 | 12 | Known Issues: 13 | 1. Git via WSL runs 2 to 3 times slower than Git for Windows, which may 14 | cause some reasonable delayes here and there. 15 | 2. The WSL seems to have some issues with passing long texts through the 16 | stdio interface. The content is simply truncated sometimes. This issue 17 | is worked around by reading the commited file content directly into the 18 | cache file, but the result of a `git diff` may still be truncated on 19 | large files. So GitGutter may not see some of the changes in a file. 20 | 21 | This WSL issue which can't be fixed by GitGutter at the moment. 22 | 23 | --------------------------------------------------------------------------- 24 | 25 | C H A N G E L O G 26 | 27 | Enhancement: 28 | - Sort settings by categories 29 | - Directly write git cache file to disk 30 | - Improve status bar text rendering 31 | 32 | Internal: 33 | - Directly start asynchronous processes 34 | - Create GitGutterViewCache class 35 | - some others ... 36 | 37 | Fix: 38 | - Check git version after settings change. 39 | - Always checkout files with LF on any OS. 40 | - Ensure not to leave zombie event listeners 41 | - Don't overwrite global settings in EventListener 42 | - Enable GitGutter via Preferences only. 43 | 44 | README: 45 | - Update git_binary settings section 46 | 47 | Feature: 48 | - Add WSL support (#500) 49 | -------------------------------------------------------------------------------- /messages/1.8.0-rc.2.txt: -------------------------------------------------------------------------------- 1 | 1.8.0-rc.2: 2 | =========== 3 | 4 | Fix: 5 | - Regression causing an exception on untracked/ignored files 6 | -------------------------------------------------------------------------------- /messages/1.8.0.txt: -------------------------------------------------------------------------------- 1 | 1.8.0: 2 | ====== 3 | 4 | A N N O U N C E M E N T 5 | 6 | This pre-release adds enables usage of Windows Subsystem for Linux (WSL). 7 | 8 | To enable WSL just setup a unix-like path in the "git_binary" setting. 9 | 10 | "git_binary": "/usr/bin/git" 11 | 12 | Known Issues: 13 | 1. Git via WSL runs 2 to 3 times slower than Git for Windows, which may 14 | cause some reasonable delayes here and there. 15 | 2. The WSL seems to have some issues with passing long texts through the 16 | stdio interface. The content is simply truncated sometimes. This issue 17 | is worked around by reading the commited file content directly into the 18 | cache file, but the result of a `git diff` may still be truncated on 19 | large files. So GitGutter may not see some of the changes in a file. 20 | 21 | This WSL issue which can't be fixed by GitGutter at the moment. 22 | 23 | --------------------------------------------------------------------------- 24 | 25 | C H A N G E L O G 26 | 27 | Enhancement: 28 | - Sort settings by categories 29 | - Directly write git cache file to disk 30 | - Improve status bar text rendering 31 | 32 | Internal: 33 | - Directly start asynchronous processes 34 | - Create GitGutterViewCache class 35 | - some others ... 36 | 37 | Fix: 38 | - Check git version after settings change. 39 | - Always checkout files with LF on any OS. 40 | - Ensure not to leave zombie event listeners 41 | - Don't overwrite global settings in EventListener 42 | - Enable GitGutter via Preferences only. 43 | 44 | README: 45 | - Update git_binary settings section 46 | 47 | Feature: 48 | - Add WSL support (#500) 49 | -------------------------------------------------------------------------------- /messages/1.9.0.txt: -------------------------------------------------------------------------------- 1 | 1.9.0: 2 | ====== 3 | A N N O U N C E M E N T 4 | 5 | This release introduces git blame annotations so quickly see who changed 6 | a line. If you don't like it at all, you can disable line annotations 7 | in GitGutter.sublime-settings: 8 | 9 | "show_line_annotation": false 10 | 11 | or in Preferences.sublime-settings: 12 | 13 | "git_gutter_show_line_annotation": false 14 | 15 | The line annotation is displayed if word wrap is disabled by default due to some 16 | rendering issues caused by the underlying phantom text. 17 | 18 | The blame message is also displayed in the status bar by default. 19 | 20 | Both the status bar text and the line annotations can be customized via jinja2 21 | templates. GitGutter therefore provides a couple more variables. 22 | 23 | For a more detailed description of the settings, please refer to the documentation. 24 | 25 | http://jisaacks.github.io/GitGutter/settings/#line-annotation 26 | 27 | --------------------------------------------------------------------------- 28 | 29 | C H A N G E L O G 30 | 31 | README: 32 | - Move Documentation to GitHub Pages 33 | 34 | Fix: 35 | - Skip evaluation if rebase is active (Issue #508) 36 | - Increase timeout for git version (#510) 37 | 38 | Feature: 39 | - Display git blame message of active line (#487) 40 | 41 | Enhancement: 42 | - Print more debugging info 43 | - Add support for diff --ignore-cr-at-eol (requires git 2.18+) 44 | -------------------------------------------------------------------------------- /messages/1.9.1.txt: -------------------------------------------------------------------------------- 1 | 1.9.1: 2 | 3 | Fix: 4 | - word wrap detection for line annotations 5 | -------------------------------------------------------------------------------- /messages/1.9.2.txt: -------------------------------------------------------------------------------- 1 | 1.9.2: 2 | 3 | Fix: 4 | - ST2 compatibility issue (#515) 5 | - Disable line annotations in distraction free mode (#514) 6 | -------------------------------------------------------------------------------- /messages/1.9.3.txt: -------------------------------------------------------------------------------- 1 | 1.9.3: 2 | 3 | Enhancement: 4 | - Disable line annotation for uncommitted content (#517) 5 | 6 | Fix: 7 | - Disable line annotations if content is centered (#514) 8 | -------------------------------------------------------------------------------- /messages/1.9.4.txt: -------------------------------------------------------------------------------- 1 | 1.9.4: 2 | 3 | Fix: 4 | - avoid line height shifting by displaying annotations (#518) 5 | -------------------------------------------------------------------------------- /messages/1.9.5.txt: -------------------------------------------------------------------------------- 1 | 1.9.5: 2 | ====== 3 | 4 | Enhancement: 5 | - Add line_annotation_ignore_whitespace setting (#526) 6 | - Disable line annotation if multiple cursors are visible 7 | - Disable line annotation if selection is not empty 8 | - Hide line annotation if caret moves horizontally 9 | - Debounce all evaluations which decide whether line annotation is displayed 10 | 11 | Fix: 12 | - IndexError thrown every time ST starts (#528) 13 | 14 | README: 15 | - Fix typos 16 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | Thanks for installing Git Gutter! -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- 1 | """GitGutter modules. 2 | 3 | Define all API classes here, which need to be exported to Sublime Text. 4 | """ 5 | from .events import BlameEventListener 6 | from .events import EventListener 7 | from .commands import GitGutterBlameCommand 8 | from .commands import GitGutterCommand 9 | from .commands import GitGutterCompareBranchCommand 10 | from .commands import GitGutterCompareCommitCommand 11 | from .commands import GitGutterCompareFileCommitCommand 12 | from .commands import GitGutterCompareHeadCommand 13 | from .commands import GitGutterCompareOriginCommand 14 | from .commands import GitGutterCompareTagCommand 15 | from .commands import GitGutterCopyFromCommitCommand 16 | from .commands import GitGutterDiffPopupCommand 17 | from .commands import GitGutterEnableViewCommand 18 | from .commands import GitGutterNextChangeCommand 19 | from .commands import GitGutterPrevChangeCommand 20 | from .commands import GitGutterReplaceTextCommand 21 | from .commands import GitGutterRevertChangeCommand 22 | from .commands import GitGutterShowCompareCommand 23 | from .support import GitGutterSupportInfoCommand 24 | -------------------------------------------------------------------------------- /modules/annotation.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from . import templates 4 | 5 | __all__ = [ 6 | "erase_line_annotation", 7 | "GitGutterLineAnnotation" 8 | ] 9 | 10 | 11 | class SimpleLineAnnotationTemplate(object): 12 | """A simple template class with the same interface as jinja2's one.""" 13 | 14 | TEMPLATE = '⟢ {line_author} ({line_author_age}) · {line_summary}' 15 | 16 | # a list of variables used by this template 17 | variables = frozenset(['line_author', 'line_author_age', 'line_summary']) 18 | 19 | @classmethod 20 | def render(cls, kwargs): 21 | """Render line annotation using a static template. 22 | 23 | Arguments: 24 | kwargs (dict): 25 | The dictionary with the information about the blame, which are 26 | provided as variables for the message template. 27 | 28 | Returns: 29 | string: The formatted annotation message. 30 | """ 31 | return cls.TEMPLATE.format(**kwargs) 32 | 33 | 34 | class GitGutterLineAnnotationST3(object): 35 | 36 | # the html template to use to render the blame phantom 37 | HTML_TEMPLATE = """ 38 | 39 | 56 | {text} 57 | 58 | """ 59 | 60 | def __init__(self, view, settings): 61 | """Initialize GitGutterLineAnnotation object.""" 62 | # the sublime.View the status bar is attached to 63 | self.view = view 64 | # the settings.ViewSettings object which stores GitGutter' settings 65 | self.settings = settings 66 | # initialize the jinja2 template 67 | self.template = None 68 | 69 | def is_enabled(self): 70 | """Check if blame phantom text is enabled. 71 | 72 | Note: 73 | Keep disabled if `draw_centered` is True because ST3176 does not 74 | place the phantom to the correct place then. 75 | 76 | Returns: 77 | bool: True if blame phantom text is enabled, False otherwise. 78 | """ 79 | if self.view.settings().get('draw_centered'): 80 | return False 81 | show_phantoms = self.settings.get('show_line_annotation', 'auto') 82 | if show_phantoms == 'auto': 83 | word_wrap = self.view.settings().get('word_wrap') 84 | show_phantoms = word_wrap is False or \ 85 | word_wrap == 'auto' and self.view.match_selector(0, 'source') 86 | if show_phantoms in (True, 'true'): 87 | return True 88 | self.template = None 89 | return False 90 | 91 | def update(self, row, **kwargs): 92 | """Add a git blame phantom text to the end of the current line. 93 | 94 | Arguments: 95 | row (int): 96 | The text row to add the phantom text to. 97 | kwargs (dict): 98 | The dictionary with the information about the blame, which are 99 | provided as variables for the message template. 100 | """ 101 | # blame message is useful for committed content only 102 | if kwargs['line_summary'] == 'not committed yet': 103 | return 104 | 105 | font_style, padding = 'normal', '5rem' 106 | 107 | try: 108 | style = self.view.style_for_scope('comment.line.annotation.git_gutter') 109 | foreground = style['foreground'] 110 | if style['bold']: 111 | if style['italic']: 112 | font_style = 'bold,italic' 113 | else: 114 | font_style = 'bold' 115 | elif style['italic']: 116 | font_style = 'italic' 117 | except: 118 | foreground = 'color(var(--foreground) blend(var(--background) 30%))' 119 | 120 | # the end of line 121 | point = self.view.line(self.view.text_point(row, 0)).end() 122 | 123 | # set up phantom text position 124 | align_to = self.settings.get('line_annotation_ruler', False) 125 | if align_to > 0: 126 | rulers = self.view.settings().get('rulers') 127 | if rulers: 128 | _, col = self.view.rowcol(point) 129 | # at least 5em or align to last available ruler 130 | padding = max( 131 | 1, 1 + rulers[min(align_to, len(rulers)) - 1] - col 132 | ) * self.view.em_width() 133 | 134 | # validate the template 135 | if not self.template: 136 | self.template = templates.create( 137 | self.settings, 'line_annotation_text', SimpleLineAnnotationTemplate) 138 | 139 | # update the phantom 140 | self.view.erase_phantoms('git_gutter_line_annotation') 141 | text = self.template.render(kwargs) 142 | if text: 143 | self.view.add_phantom( 144 | key='git_gutter_line_annotation', 145 | region=sublime.Region(point, point + 1), 146 | content=self.HTML_TEMPLATE.format( 147 | foreground=foreground, 148 | font_style=font_style, 149 | padding=padding, 150 | text=text), 151 | layout=sublime.LAYOUT_INLINE 152 | ) 153 | 154 | 155 | class GitGutterLineAnnotationST4(object): 156 | 157 | # the html template to use to render the blame phantom 158 | HTML_TEMPLATE = """ 159 | 160 | 176 | {text} 177 | 178 | """ 179 | 180 | def __init__(self, view, settings): 181 | """Initialize GitGutterLineAnnotation object.""" 182 | # the sublime.View the status bar is attached to 183 | self.view = view 184 | # the settings.ViewSettings object which stores GitGutter' settings 185 | self.settings = settings 186 | # initialize the jinja2 template 187 | self.template = None 188 | 189 | def is_enabled(self): 190 | """Check if blame phantom text is enabled. 191 | 192 | Note: 193 | Keep disabled if `draw_centered` is True because ST3176 does not 194 | place the phantom to the correct place then. 195 | 196 | Returns: 197 | bool: True if blame phantom text is enabled, False otherwise. 198 | """ 199 | show_phantoms = self.settings.get('show_line_annotation', True) 200 | if show_phantoms in (True, 'true', 'auto'): 201 | return True 202 | self.template = None 203 | return False 204 | 205 | def update(self, row, **kwargs): 206 | """Add a git blame phantom text to the end of the current line. 207 | 208 | Arguments: 209 | row (int): 210 | The text row to add the phantom text to. 211 | kwargs (dict): 212 | The dictionary with the information about the blame, which are 213 | provided as variables for the message template. 214 | """ 215 | # blame message is useful for committed content only 216 | if kwargs['line_summary'] == 'not committed yet': 217 | return 218 | 219 | font_style = 'normal' 220 | 221 | try: 222 | style = self.view.style_for_scope('comment.line.annotation.git_gutter') 223 | foreground = style['foreground'] 224 | if style['bold']: 225 | if style['italic']: 226 | font_style = 'bold,italic' 227 | else: 228 | font_style = 'bold' 229 | elif style['italic']: 230 | font_style = 'italic' 231 | except: 232 | foreground = 'color(var(--foreground) blend(var(--background) 30%))' 233 | 234 | # the end of line 235 | point = self.view.text_point(row, 0) 236 | 237 | # validate the template 238 | if not self.template: 239 | self.template = templates.create( 240 | self.settings, 'line_annotation_text', SimpleLineAnnotationTemplate) 241 | 242 | # update the phantom 243 | self.view.erase_regions('git_gutter_line_annotation') 244 | text = self.template.render(kwargs) 245 | if text: 246 | flags = sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.HIDE_ON_MINIMAP 247 | if hasattr(sublime, 'NO_UNDO'): 248 | # supported as of ST4160 249 | flags |= sublime.NO_UNDO 250 | 251 | self.view.add_regions( 252 | key='git_gutter_line_annotation', 253 | regions=[sublime.Region(point, point + 1)], 254 | scope="markup.changed", 255 | annotations=[ 256 | self.HTML_TEMPLATE.format( 257 | foreground=foreground, 258 | font_style=font_style, 259 | text=text) 260 | ], 261 | annotation_color=foreground, 262 | flags=flags 263 | ) 264 | 265 | if int(sublime.version()) >= 4000: 266 | def erase_line_annotation(view): 267 | view.erase_regions('git_gutter_line_annotation') 268 | 269 | class GitGutterLineAnnotation(GitGutterLineAnnotationST4): 270 | pass 271 | 272 | else: 273 | def erase_line_annotation(view): 274 | view.erase_phantoms('git_gutter_line_annotation') 275 | 276 | class GitGutterLineAnnotation(GitGutterLineAnnotationST3): 277 | pass 278 | -------------------------------------------------------------------------------- /modules/blame.py: -------------------------------------------------------------------------------- 1 | import time 2 | from functools import partial 3 | 4 | from .utils import line_from_kwargs 5 | 6 | # A set of all supported variables 7 | BLAME_VARIABLES = frozenset([ 8 | 'line_commit', 9 | 'line_previous', 10 | 'line_summary', 11 | 'line_author', 12 | 'line_author_mail', 13 | 'line_author_age', 14 | 'line_author_time', 15 | 'line_author_tz', 16 | 'line_committer', 17 | 'line_committer_mail', 18 | 'line_committer_age', 19 | 'line_committer_time', 20 | 'line_committer_tz' 21 | ]) 22 | 23 | 24 | def run_blame(git_gutter, **kwargs): 25 | """Call git blame for the requested or active row and add phantom. 26 | 27 | Arguments: 28 | git_gutter (GitGutterCommand): 29 | The main command object, which represents GitGutter. 30 | kwargs (dict): 31 | The arguments received from the `run_command`. 32 | This argument is declared to create a common interface being used 33 | by the GitGutterCommand object. 34 | 35 | Valid kwargs are: 36 | line (int): 37 | The zero based line number to run the git blame command for. 38 | point (int): 39 | The text point to use in order to calculate the line to run the 40 | git blame command for. 41 | """ 42 | # check if feature is enabled 43 | is_command = not kwargs.get('is_event') 44 | show_inline = is_command or git_gutter.line_annotation.is_enabled() 45 | status_bar = git_gutter.status_bar 46 | show_status = status_bar.is_enabled() and status_bar.has(BLAME_VARIABLES) 47 | if not show_inline and not show_status: 48 | return None 49 | 50 | # ignore empty lines as cursor jumps off 51 | view = git_gutter.view 52 | line = line_from_kwargs(view, kwargs) 53 | if not view.line(view.text_point(line, 0)): 54 | return None 55 | 56 | # run git blame and print its output to the desired targets 57 | return git_gutter.git_handler.git_blame(line).then( 58 | partial(_render_blame, git_gutter, show_inline, show_status)) 59 | 60 | 61 | def _render_blame(git_gutter, show_inline, show_status, contents): 62 | """Decode the git blame output and update status bar and phantoms.""" 63 | if not contents: 64 | return 65 | contents = contents.split('\n') 66 | # decode first line 67 | tokens = contents[0].split(' ') 68 | # use if second row entry if exists, the first one otherwise 69 | row = int(tokens[2] if len(tokens) > 2 else tokens[1]) - 1 70 | 71 | blame = {'line_commit': tokens[0]} 72 | # decode the contents excluding the first line, which contains 73 | # the current commit hash and blamed row, only. 74 | for content in contents[1:]: 75 | if ' ' in content: 76 | key, value = content.split(' ', 1) 77 | key = 'line_' + key.replace('-', '_').strip() 78 | if key in git_gutter.status_bar.vars: 79 | blame[key] = value 80 | 81 | # modify some fields if line contains uncommitted content 82 | if not tokens[0] != '0' * len(tokens[0]): 83 | blame['line_author'] = 'You' 84 | blame['line_committer'] = 'Nobody' 85 | blame['line_summary'] = 'not committed yet' 86 | 87 | # prepare some extra information 88 | author_time = int(blame['line_author_time']) 89 | blame['line_author_age'] = format_ago(author_time) 90 | blame['line_author_time'] = format_time(author_time) 91 | committer_time = int(blame['line_committer_time']) 92 | blame['line_committer_age'] = format_ago(committer_time) 93 | blame['line_committer_time'] = format_time(committer_time) 94 | 95 | # print the statusbar text if enabled 96 | if show_status: 97 | git_gutter.status_bar.update(**blame) 98 | 99 | # print the inline text if enabled 100 | if show_inline: 101 | git_gutter.line_annotation.update(row, **blame) 102 | 103 | 104 | def format_ago(timestamp): 105 | """Return the human readable time elapsed since `timestamp`.""" 106 | st = time.gmtime(time.time() - timestamp) 107 | if st.tm_year - 1970 > 1: 108 | return '{0} years ago'.format(st.tm_year - 1970) 109 | if st.tm_year - 1970 == 1: 110 | return 'a year ago' 111 | if st.tm_mon - 1 > 1: 112 | return '{0} months ago'.format(st.tm_mon - 1) 113 | if st.tm_mon - 1 == 1: 114 | return 'a month ago' 115 | if st.tm_mday - 1 > 1: 116 | return '{0} days ago'.format(st.tm_mday - 1) 117 | if st.tm_mday - 1 == 1: 118 | return 'a day ago' 119 | if st.tm_hour > 1: 120 | return '{0} hours ago'.format(st.tm_hour) 121 | if st.tm_hour == 1: 122 | return 'an hour ago' 123 | if st.tm_min > 1: 124 | return '{0} minutes ago'.format(st.tm_min) 125 | if st.tm_min == 1: 126 | return 'a minute ago' 127 | return 'just now' 128 | 129 | 130 | def format_time(timestamp): 131 | """Return the human readable time of `timestamp`.""" 132 | return time.strftime("%x %H:%M", time.localtime(timestamp)) 133 | -------------------------------------------------------------------------------- /modules/compare.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | 4 | def set_against_commit(git_gutter, **kwargs): 5 | """Show a quick panel with commits to be chosen from as compare against. 6 | 7 | Arguments: 8 | git_gutter (GitGutterCommand): The main command object, which 9 | represents GitGutter. 10 | kwargs (dict): The arguments received from the `run_command`. 11 | This argument is declared to create a common interface being used 12 | by the GitGutterCommand object. 13 | """ 14 | def show_quick_panel(output): 15 | """Parse git output and present the quick panel. 16 | 17 | Arguments: 18 | output (string): The output of git with the list of commits. 19 | """ 20 | if not output: 21 | return sublime.message_dialog('No commits found in repository.') 22 | 23 | # Create the list of commits to show in the quick panel 24 | items = [r[1:-1].split('\a') for r in output.split('\n')] 25 | 26 | def on_done(index): 27 | """Select new compare target according to user selection.""" 28 | if index > -1: 29 | git_gutter.git_handler.set_compare_against( 30 | items[index][0].split(' ')[0]) 31 | 32 | git_gutter.view.window().show_quick_panel(items, on_done) 33 | 34 | git_gutter.git_handler.git_commits().then(show_quick_panel) 35 | 36 | 37 | def set_against_file_commit(git_gutter, **kwargs): 38 | """Show a quick panel with commits to be chosen from as compare against. 39 | 40 | Arguments: 41 | git_gutter (GitGutterCommand): The main command object, which 42 | represents GitGutter. 43 | kwargs (dict): The arguments received from the `run_command`. 44 | This argument is declared to create a common interface being used 45 | by the GitGutterCommand object. 46 | """ 47 | def show_quick_panel(output): 48 | """Parse git output and present the quick panel. 49 | 50 | Arguments: 51 | output (string): The output of git with the list of commits. 52 | """ 53 | if not output: 54 | return sublime.message_dialog( 55 | 'No commits of this file found in repository.') 56 | 57 | # Sort items by author date in reversed order, 58 | # split each line by \a and strip time stamp from beginning 59 | items = [ 60 | r[1:-1].split('\a')[1:] 61 | for r in sorted(output.split('\n'), reverse=True) 62 | ] 63 | 64 | def on_done(index): 65 | """Select new compare target according to user selection.""" 66 | if index > -1: 67 | git_gutter.git_handler.set_compare_against( 68 | items[index][0].split(' ')[0]) 69 | 70 | git_gutter.view.window().show_quick_panel(items, on_done) 71 | 72 | git_gutter.git_handler.git_file_commits().then(show_quick_panel) 73 | 74 | 75 | def set_against_branch(git_gutter, **kwargs): 76 | """Show a quick panel with branches to be chosen from as compare against. 77 | 78 | Arguments: 79 | git_gutter (GitGutterCommand): The main command object, which 80 | represents GitGutter. 81 | kwargs (dict): The arguments received from the `run_command`. 82 | This argument is declared to create a common interface being used 83 | by the GitGutterCommand object. 84 | """ 85 | def show_quick_panel(output): 86 | """Parse git output and present the quick panel. 87 | 88 | Arguments: 89 | output (string): The output of git with the list of branches. 90 | """ 91 | if not output: 92 | return sublime.message_dialog('No branches found in repository.') 93 | 94 | def parse_result(result): 95 | """Create a quick panel item for one line of git's output.""" 96 | branch, commit, name, date = result[1:-1].split('\a') 97 | branch = branch[11:] # skip 'refs/heads/' 98 | return [branch, commit, name, date] 99 | 100 | # Create the list of branches to show in the quick panel 101 | items = [parse_result(r) for r in output.split('\n')] 102 | 103 | def on_done(index): 104 | """Select new compare target according to user selection.""" 105 | if index > -1: 106 | git_gutter.git_handler.set_compare_against( 107 | 'refs/heads/%s' % items[index][0]) 108 | 109 | git_gutter.view.window().show_quick_panel(items, on_done) 110 | 111 | git_gutter.git_handler.git_branches().then(show_quick_panel) 112 | 113 | 114 | def set_against_tag(git_gutter, **kwargs): 115 | """Show a quick panel with tags to be chosen from as compare against. 116 | 117 | Arguments: 118 | git_gutter (GitGutterCommand): The main command object, which 119 | represents GitGutter. 120 | kwargs (dict): The arguments received from the `run_command`. 121 | This argument is declared to create a common interface being used 122 | by the GitGutterCommand object. 123 | """ 124 | def show_quick_panel(output): 125 | """Parse git output and present the quick panel. 126 | 127 | Arguments: 128 | output (string): The output of git with the list of tags. 129 | """ 130 | if not output: 131 | return sublime.message_dialog('No tags found in repository.') 132 | 133 | def parse_result(result): 134 | """Create a quick panel item for one line of git's output.""" 135 | tag, commit, tname, tdate, cname, cdate = result[1:-1].split('\a') 136 | tag = tag[10:] # skip 'refs/heads/' 137 | return [tag, commit, tname.strip() or cname, tdate.strip() or cdate] 138 | 139 | # Create the list of tags to show in the quick panel 140 | items = [parse_result(r) for r in output.split('\n')] 141 | 142 | def on_done(index): 143 | """Select new compare target according to user selection.""" 144 | if index > -1: 145 | git_gutter.git_handler.set_compare_against( 146 | 'refs/tags/%s' % items[index][0]) 147 | 148 | git_gutter.view.window().show_quick_panel(items, on_done) 149 | 150 | git_gutter.git_handler.git_tags().then(show_quick_panel) 151 | 152 | 153 | def set_against_head(git_gutter, **kwargs): 154 | """Set HEAD as compare target. 155 | 156 | Arguments: 157 | git_gutter (GitGutterCommand): The main command object, which 158 | represents GitGutter. 159 | kwargs (dict): The arguments received from the `run_command`. 160 | This argument is declared to create a common interface being used 161 | by the GitGutterCommand object. 162 | """ 163 | git_gutter.git_handler.set_compare_against('HEAD', True) 164 | 165 | 166 | def set_against_origin(git_gutter, **kwargs): 167 | """Set origin as compare target. 168 | 169 | Arguments: 170 | git_gutter (GitGutterCommand): The main command object, which 171 | represents GitGutter. 172 | kwargs (dict): The arguments received from the `run_command`. 173 | This argument is declared to create a common interface being used 174 | by the GitGutterCommand object. 175 | """ 176 | def on_branch_name(status): 177 | remote = status.get('remote') if status else None 178 | if remote: 179 | git_gutter.git_handler.set_compare_against(remote, True) 180 | sublime.message_dialog('Current branch has no tracking remote!') 181 | 182 | git_gutter.git_handler.git_branch_status().then(on_branch_name) 183 | 184 | 185 | def show_compare(git_gutter, **kwargs): 186 | """Show a dialog to display current compare target. 187 | 188 | Arguments: 189 | git_gutter (GitGutterCommand): The main command object, which 190 | represents GitGutter. 191 | kwargs (dict): The arguments received from the `run_command`. 192 | This argument is declared to create a common interface being used 193 | by the GitGutterCommand object. 194 | """ 195 | comparing = git_gutter.git_handler.format_compare_against() 196 | sublime.message_dialog( 197 | 'GitGutter is comparing against: %s' % comparing) 198 | -------------------------------------------------------------------------------- /modules/copy.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from .utils import line_from_kwargs 4 | 5 | 6 | def copy_from_commit(git_gutter, **kwargs): 7 | """Copy the content from commit. 8 | 9 | Arguments: 10 | git_gutter (GitGutterCommand): 11 | The main command object, which represents GitGutter 12 | and called this functiond. 13 | kwargs (dict): 14 | The arguments passed from GitGutterRevertChangesCommand 15 | to GitGutterCommand. 16 | 17 | valid kwargs are: 18 | line (int): zero-based line number within the hunk to copy 19 | point (int): zero based text position within the hunk to copy 20 | """ 21 | line = line_from_kwargs(git_gutter.view, kwargs) 22 | del_lines, _, _, _ = git_gutter.git_handler.diff_line_change(line + 1) 23 | del_text = '\n'.join(del_lines or '') 24 | sublime.set_clipboard(del_text) 25 | sublime.status_message('Copied: {0} characters'.format(len(del_text))) 26 | -------------------------------------------------------------------------------- /modules/events.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import sublime 4 | import sublime_plugin 5 | 6 | from . import settings 7 | from .annotation import erase_line_annotation 8 | from .temp import cleanup 9 | 10 | # binary representation of all ST events 11 | NEW = 1 12 | CLONE = 2 13 | LOAD = 4 14 | PRE_SAVE = 8 15 | POST_SAVE = 16 16 | MODIFIED = 32 17 | SELECTION_MODIFIED = 64 18 | ACTIVATED = 128 19 | DEACTIVATED = 256 20 | 21 | 22 | class EventListener(sublime_plugin.EventListener): 23 | """The EventListener invokes evaluation of changes on certain events. 24 | 25 | GitGutter mainly operates in the background by listening to events sent 26 | from Sublime Text. This event listener is the interface to get informed 27 | about user interaction to decide when to invoke an evaluation of changes 28 | by calling the main `git_gutter` command. 29 | """ 30 | 31 | def __init__(self): 32 | """Initialize GitGutterEvents object.""" 33 | self.view_events = {} 34 | 35 | def on_exit(self): 36 | cleanup() 37 | 38 | def on_load(self, view): 39 | """Run git_gutter after loading, if view is valid. 40 | 41 | Arguments: 42 | view (View): The view which received the event. 43 | """ 44 | self.debounce(view, LOAD) 45 | 46 | def on_close(self, view): 47 | """Clean up the debounce dictionary. 48 | 49 | Arguments: 50 | view (View): The view which received the event. 51 | """ 52 | try: 53 | del self.view_events[view.id()] 54 | except KeyError: 55 | pass 56 | 57 | def on_modified(self, view): 58 | """Run git_gutter for modified visible view. 59 | 60 | The `on_modified()` is called when typing into an active view and 61 | might be called for inactive views if the file changes on disk. 62 | 63 | Arguments: 64 | view (View): The view which received the event. 65 | """ 66 | self.debounce(view, MODIFIED) 67 | 68 | def on_clone(self, view): 69 | """Run git_gutter for a cloned view. 70 | 71 | Arguments: 72 | view (View): The view which received the event. 73 | """ 74 | self.debounce(view, CLONE) 75 | 76 | def on_post_save(self, view): 77 | """Run git_gutter after saving. 78 | 79 | Arguments: 80 | view (View): The view which received the event. 81 | """ 82 | self.debounce(view, POST_SAVE) 83 | 84 | def on_activated(self, view): 85 | """Run git_gutter if the view gets activated. 86 | 87 | Arguments: 88 | view (View): The view which received the event. 89 | """ 90 | self.debounce(view, ACTIVATED) 91 | 92 | def on_hover(self, view, point, hover_zone): 93 | """Open diff popup if user hovers the mouse over the gutter area. 94 | 95 | Arguments: 96 | view (View): The view which received the event. 97 | point (Point): The text position where the mouse hovered 98 | hover_zone (int): The context the event was triggered in 99 | """ 100 | if hover_zone != sublime.HOVER_GUTTER: 101 | return 102 | # don't let the popup flicker / fight with other packages 103 | if view.is_popup_visible(): 104 | return 105 | try: 106 | view_settings = self.view_events[view.id()].settings 107 | except KeyError: 108 | return 109 | # check if hover is enabled 110 | if not view_settings.get('enable_hover_diff_popup'): 111 | return 112 | # check protected regions 113 | keys = tuple(view_settings.get('diff_popup_protected_regions')) 114 | points = (view.line(reg).a for key in keys for reg in view.get_regions(key)) 115 | if point in points: 116 | return 117 | # finally show the popup 118 | view.run_command('git_gutter_diff_popup', { 119 | 'point': point, 'flags': sublime.HIDE_ON_MOUSE_MOVE_AWAY}) 120 | 121 | def debounce(self, view, event_id): 122 | """Invoke evaluation of changes after some idle time. 123 | 124 | Arguments: 125 | view (View): The view to perform evaluation for 126 | event_id (int): The event identifier 127 | """ 128 | key = view.id() 129 | try: 130 | self.view_events[key].push(event_id) 131 | except KeyError: 132 | if view.buffer_id(): 133 | new_listener = ViewEventListener(view) 134 | new_listener.push(event_id) 135 | self.view_events[key] = new_listener 136 | # do garbage connection 137 | for vid in [vid for vid, listener in self.view_events.items() 138 | if listener.view.buffer_id() == 0]: 139 | del self.view_events[vid] 140 | 141 | 142 | class ViewEventListener(object): 143 | """The class queues and forwards view events to GitGutterCommand. 144 | 145 | A ViewEventListener object queues all events received from a view and 146 | starts a single sublime timer to forward the event to GitGutterCommand 147 | after some idle time. This ensures not to bloat sublime API due to dozens 148 | of timers running for debouncing events. 149 | """ 150 | 151 | def __init__(self, view): 152 | """Initialize ViewEventListener object. 153 | 154 | Arguments: 155 | view (View): The view the object is created for. 156 | """ 157 | self.view = view 158 | # view aware git gutter settings 159 | self.settings = settings.ViewSettings(view) 160 | # timer is running flag 161 | self.busy = False 162 | # a binary combination of above events 163 | self.events = 0 164 | # latest time of append() call 165 | self.latest_time = 0.0 166 | # debounce delay in milliseconds 167 | self.delay = 0 168 | 169 | def push(self, event_id): 170 | """Push the event to the queue and start idle timer. 171 | 172 | Add the event identifier to 'events' and update the 'latest_time'. 173 | This marks an event to be received rather than counting the number 174 | of received events. The idle timer is started only, if no other one 175 | is already in flight. 176 | 177 | Arguments: 178 | event_id (int): One of the event identifiers. 179 | """ 180 | if event_id & ACTIVATED: 181 | if not (self.settings.get('live_mode') or 182 | self.settings.get('focus_change_mode')): 183 | return 184 | elif event_id & MODIFIED: 185 | if not self.settings.get('live_mode'): 186 | return 187 | self.latest_time = time.time() 188 | self.events |= event_id 189 | if not self.busy: 190 | self.delay = max(200, self.settings.get('debounce_delay', 1000)) 191 | self.start_timer(200) 192 | 193 | def start_timer(self, delay): 194 | """Run GitGutterCommand after some idle time. 195 | 196 | Check if no more events were received during idle time and run 197 | GitGutterCommand if not. Restart timer to check later, otherwise. 198 | 199 | Timer is stopped without calling GitGutterCommand, if a view is not 200 | visible to save some resources. Evaluation will be triggered by 201 | activating the view next time. 202 | 203 | Arguments: 204 | delay (int): The delay in milliseconds to wait until probably 205 | forward the events, if no other event was received in the 206 | meanwhile. 207 | """ 208 | start_time = self.latest_time 209 | 210 | def worker(): 211 | """The function called after some idle time.""" 212 | if start_time < self.latest_time: 213 | self.start_timer(self.delay) 214 | return 215 | self.busy = False 216 | if not self.is_view_visible(): 217 | return 218 | self.view.run_command('git_gutter', {'events': self.events}) 219 | self.events = 0 220 | 221 | self.busy = True 222 | sublime.set_timeout(worker, delay) 223 | 224 | def is_view_visible(self): 225 | """Determine if the view is visible. 226 | 227 | Only an active view of a group is visible. 228 | 229 | Returns: 230 | bool: True if the view is visible in any window. 231 | """ 232 | window = self.view.window() 233 | if window: 234 | view_id = self.view.id() 235 | for group in range(window.num_groups()): 236 | active_view = window.active_view_in_group(group) 237 | if active_view and active_view.id() == view_id: 238 | return True 239 | return False 240 | 241 | 242 | class BlameEventListener(sublime_plugin.EventListener): 243 | """An EventListener to track caret movement and update blame messages.""" 244 | 245 | def __init__(self): 246 | """Initialize n BlameEventListener object.""" 247 | # last blame call 248 | self.latest_blame_time = 0.0 249 | # a dictionary with active rows of each view 250 | self.last_blame_row = {} 251 | 252 | def on_close(self, view): 253 | """Clean up the debounce dictionary. 254 | 255 | Arguments: 256 | view (View): The view which received the event. 257 | """ 258 | key = view.id() 259 | if key in self.last_blame_row: 260 | del self.last_blame_row[key] 261 | 262 | def on_activated(self, view): 263 | """Run blame if the view is activated.""" 264 | self._run_blame(view, True) 265 | 266 | def on_deactivated(self, view): 267 | """Remove inline blame text if view is deactivated.""" 268 | erase_line_annotation(view) 269 | 270 | def on_modified(self, view): 271 | """Remove inline blame text if user starts typing.""" 272 | erase_line_annotation(view) 273 | 274 | def on_selection_modified(self, view): 275 | """Run blame if the caret moves to another row.""" 276 | self._run_blame(view, False) 277 | 278 | def _run_blame(self, view, force): 279 | """Run blame if the caret moves to another row.""" 280 | 281 | # remove existing phantoms 282 | erase_line_annotation(view) 283 | 284 | # initiate debounce timer 285 | blame_time = time.time() 286 | self.latest_blame_time = blame_time 287 | 288 | def on_time(): 289 | if self.latest_blame_time != blame_time: 290 | return 291 | try: 292 | sel = view.sel() 293 | # do nothing if not exactly one cursor is visible 294 | if len(sel) != 1: 295 | return 296 | sel = sel[0] 297 | # do nothing is selection not empty or line is empty 298 | if not sel.empty() or not view.line(sel.b): 299 | return 300 | row, _ = view.rowcol(sel.b) 301 | # do nothing if row didn't change 302 | if not force and row == self.last_blame_row.get(view.id(), -1): 303 | return 304 | self.last_blame_row[view.id()] = row 305 | except (AttributeError, IndexError, TypeError): 306 | pass 307 | else: 308 | view.run_command('git_gutter_blame', {'is_event': True, 'line': row}) 309 | 310 | # don't call blame if selected row changes too quickly 311 | sublime.set_timeout(on_time, 400) 312 | -------------------------------------------------------------------------------- /modules/goto.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def next_change(git_gutter, **kwargs): 4 | """Move cursor to the beginning of the next changed hunk. 5 | 6 | Arguments: 7 | git_gutter (GitGutterCommand): 8 | The main command object, which represents GitGutter. 9 | kwargs (dict): 10 | The arguments received from the `run_command`. 11 | This argument is declared to create a common interface being used 12 | by the GitGutterCommand object. 13 | 14 | Valid kwargs are: 15 | count (int): 16 | The number of calls to jump_func to determine the destination. 17 | Defaults to 1 if the argument is missing or invalid. 18 | wrap (bool): 19 | If False, stop searching for the next hunk at document boundaries, 20 | continue otherwise. If wrap is None, the 'next_prev_change_wrap' 21 | setting is used. 22 | """ 23 | _goto_change(git_gutter, _find_next_change, 24 | kwargs.get('count'), kwargs.get('wrap')) 25 | 26 | 27 | def prev_change(git_gutter, **kwargs): 28 | """Move cursor to the beginning of the previous changed hunk. 29 | 30 | Arguments: 31 | git_gutter (GitGutterCommand): 32 | The main command object, which represents GitGutter. 33 | kwargs (dict): 34 | The arguments received from the `run_command`. 35 | This argument is declared to create a common interface being used 36 | by the GitGutterCommand object. 37 | 38 | Valid kwargs are: 39 | count (int): 40 | The number of calls to jump_func to determine the destination. 41 | Defaults to 1 if the argument is missing or invalid. 42 | wrap (bool): 43 | If False, stop searching for the next hunk at document boundaries, 44 | continue otherwise. If wrap is None, the `next_prev_change_wrap` 45 | setting is used. 46 | """ 47 | _goto_change(git_gutter, _find_prev_change, 48 | kwargs.get('count'), kwargs.get('wrap')) 49 | 50 | 51 | def _goto_change(git_gutter, jump_func, count, wrap): 52 | """Get a tuple of changes and goto the next one found by jump_func. 53 | 54 | Arguments: 55 | git_gutter (GitGutterCommand): 56 | The main command object, which represents GitGutter. 57 | jump_func (function): 58 | The function to use to select the next hunk from the list of changes. 59 | count (int): 60 | The number of calls to jump_func to determine the destination. 61 | Defaults to 1 if the argument is missing or invalid. 62 | wrap (bool): 63 | If False, stop searching for the next hunk at document boundaries, 64 | continue otherwise. If wrap is None, the `next_prev_change_wrap` 65 | setting is used. 66 | """ 67 | selections = git_gutter.view.sel() 68 | if not selections: 69 | return 70 | changes = git_gutter.git_handler.diff_changed_blocks() 71 | if not changes: 72 | return 73 | if not isinstance(wrap, bool): 74 | wrap = git_gutter.settings.get('next_prev_change_wrap', True) 75 | if not isinstance(count, int) or count < 1: 76 | count = 1 77 | line = git_gutter.view.rowcol(selections[0].begin())[0] + 1 78 | for i in range(count): 79 | line = jump_func(changes, line, wrap) 80 | git_gutter.view.run_command("goto_line", {"line": line}) 81 | 82 | 83 | def _find_next_change(changes, current_row, wrap): 84 | """Find the next line after current row in changes. 85 | 86 | Arguments: 87 | changes (list): 88 | The list of first lines of changed hunks. 89 | current_row (int): 90 | The row to start searching from. 91 | wrap (bool): 92 | If True continue with first change after end of file. 93 | 94 | Returns: 95 | int: The next line in changes. 96 | """ 97 | return next( 98 | (change for change in changes if change > current_row), 99 | changes[0] if wrap else changes[-1]) 100 | 101 | 102 | def _find_prev_change(changes, current_row, wrap): 103 | """Find the previous line before current row in changes. 104 | 105 | Arguments: 106 | changes (list): 107 | The list of first lines of changed hunks. 108 | current_row (int): 109 | The row to start searching from. 110 | wrap (bool): 111 | If True continue with first change after end of file. 112 | 113 | Returns: 114 | int: The previous line in changes. 115 | """ 116 | return next( 117 | (change for change in reversed(changes) if change < current_row), 118 | changes[-1] if wrap else changes[0]) 119 | -------------------------------------------------------------------------------- /modules/path.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import re 3 | 4 | try: 5 | from nt import _getfinalpathname 6 | from sys import getwindowsversion 7 | assert getwindowsversion().major >= 6 8 | 9 | def realpath(path): 10 | """Resolve symlinks and return real path to file. 11 | 12 | Note: 13 | This is a fix for the issue of `os.path.realpath()` not to resolve 14 | symlinks on Windows as it is an alias to `os.path.abspath()` only. 15 | see: http://bugs.python.org/issue9949 16 | 17 | This fix applies to local paths only as symlinks are not resolved 18 | by _getfinalpathname on network drives anyway. 19 | 20 | Also note that _getfinalpathname in Python 3.3 throws 21 | `NotImplementedError` on Windows versions prior to Windows Vista, 22 | hence we fallback to `os.path.abspath()` on these platforms. 23 | 24 | Arguments: 25 | path (string): The path to resolve. 26 | 27 | Returns: 28 | string: The resolved absolute path if exists or path as provided 29 | otherwise. 30 | """ 31 | try: 32 | if path: 33 | real_path = _getfinalpathname(path) 34 | if real_path[5] == ':': 35 | # Remove \\?\ from beginning of resolved path 36 | return real_path[4:] 37 | return os.path.abspath(path) 38 | except FileNotFoundError: 39 | pass 40 | return path 41 | 42 | except (AttributeError, ImportError, AssertionError): 43 | def realpath(path): 44 | """Resolve symlinks and return real path to file. 45 | 46 | Arguments: 47 | path (string): The path to resolve. 48 | 49 | Returns: 50 | string: The resolved absolute path. 51 | """ 52 | return os.path.realpath(path) if path else None 53 | 54 | 55 | def is_work_tree(path): 56 | """Check if 'path' is a valid git working tree. 57 | 58 | A working tree contains a `.git` directory or file. 59 | 60 | Arguments: 61 | path (string): The path to check. 62 | 63 | Returns: 64 | bool: True if path contains a '.git' 65 | """ 66 | return path and os.path.exists(os.path.join(path, '.git')) 67 | 68 | 69 | def split_work_tree(file_path): 70 | """Split the 'file_path' into working tree and relative path. 71 | 72 | The file_path is converted to a absolute real path and split into 73 | the working tree part and relative path part. 74 | 75 | Note: 76 | This is a local alternative to calling the git command: 77 | 78 | git rev-parse --show-toplevel 79 | 80 | Arguments: 81 | file_path (string): Absolute path to a file. 82 | 83 | Returns: 84 | A tuple of two the elements (working tree, file path). 85 | """ 86 | if file_path: 87 | path, name = os.path.split(file_path) 88 | # files within '.git' path are not part of a work tree 89 | while path and name and name != '.git': 90 | if is_work_tree(path): 91 | return (path, os.path.relpath( 92 | file_path, path).replace('\\', '/')) 93 | path, name = os.path.split(path) 94 | return (None, None) 95 | 96 | 97 | def is_translatable_to_wsl(path): 98 | return path and ( 99 | path.startswith('\\\\wsl.localhost\\') 100 | or not path.startswith('\\\\') 101 | ) 102 | 103 | 104 | def translate_to_wsl(path): 105 | """Translate a windows path to unix path for WSL. 106 | 107 | WSL stands for Windows Subsystem for Linux and allows to run native linux 108 | programs on Windows. In order to access Windows' filesystem the local 109 | drives are mounted to /mnt/ within the WSL shell. This little helper 110 | function translates a windows path to such a WSL compatible path. 111 | 112 | Note: 113 | This function works for local files only at the moment. 114 | 115 | Arguments: 116 | path (string): the windows path to translate to unix path 117 | 118 | Returns: 119 | string: the unix path to be used by calls to programs running on WSL 120 | 121 | Raises: 122 | FileNotFoundError: if path is an UNC path. 123 | """ 124 | # remove UNC prefix for paths pointing to WSL environment 125 | if path.startswith('\\\\wsl.localhost\\'): 126 | wsl_path = re.sub(r"\\\\wsl\.localhost\\[^\\]*", "", path) 127 | elif path.startswith('\\\\'): 128 | raise FileNotFoundError('UNC paths are not supported by WSL!') 129 | wsl_path = path.replace('\\', '/') 130 | if wsl_path[1:3] == ':/': 131 | return ''.join(('/mnt/', wsl_path[0].lower(), wsl_path[2:])) 132 | return wsl_path 133 | -------------------------------------------------------------------------------- /modules/popup/__init__.py: -------------------------------------------------------------------------------- 1 | """Diff Popup Module initialization. 2 | 3 | Check dependencies and enable/disable diff popup. 4 | Declare public API functions. 5 | """ 6 | try: 7 | # mdpopups 2.0.0+ is required 8 | import mdpopups 9 | if mdpopups.version() < (2, 0, 0): 10 | raise ImportError('mdpopups 2.0.0+ required.') 11 | 12 | # public function 13 | from .factory import show_diff_popup 14 | except ImportError: 15 | show_diff_popup = None 16 | -------------------------------------------------------------------------------- /modules/popup/differ.py: -------------------------------------------------------------------------------- 1 | """Differ module. 2 | 3 | This module is inspired by difflib.Differ which creates human readable diff 4 | output. Instead of text it outputs html which can be displayed by diff popup 5 | well. 6 | """ 7 | import difflib 8 | import html 9 | 10 | 11 | def highlight_diff(old_lines, new_lines): 12 | """Generate the html string with highlighted diff.""" 13 | return ''.join(_highlight_diff(old_lines, new_lines)) 14 | 15 | 16 | def _highlight_diff(a, b): 17 | # begin of mdpopups code view 18 | yield '

' 19 | cruncher = difflib.SequenceMatcher(None, a, b, False) 20 | for tag, alo, ahi, blo, bhi in cruncher.get_opcodes(): 21 | if tag == 'replace': 22 | g = _fancy_replace(a, alo, ahi, b, blo, bhi) 23 | elif tag == 'delete': 24 | g = _dump_lines('del', a, alo, ahi) 25 | elif tag == 'insert': 26 | g = _dump_lines('ins', b, blo, bhi) 27 | elif tag == 'equal': 28 | g = _dump_lines(None, a, alo, ahi) 29 | else: 30 | raise ValueError('unknown tag {0}'.format(tag)) 31 | yield from g 32 | # end of mdpopups code view 33 | yield '
' 34 | 35 | 36 | def _plain_replace(a, alo, ahi, b, blo, bhi): 37 | assert alo < ahi and blo < bhi 38 | # dump the shorter block first -- reduces the burden on short-term 39 | # memory if the blocks are of very different sizes 40 | if bhi - blo < ahi - alo: 41 | first = _dump_lines('chg-ins', b, blo, bhi) 42 | second = _dump_lines('chg-del', a, alo, ahi) 43 | else: 44 | first = _dump_lines('chg-del', a, alo, ahi) 45 | second = _dump_lines('chg-ins', b, blo, bhi) 46 | 47 | for g in first, second: 48 | yield from g 49 | 50 | 51 | def _fancy_replace(a, alo, ahi, b, blo, bhi): 52 | """Generate comparison results for a same-tagged range.""" 53 | 54 | # don't synch up unless the lines have a similarity score of at 55 | # least cutoff; best_ratio tracks the best score seen so far 56 | best_ratio, cutoff = 0.54, 0.55 57 | cruncher = difflib.SequenceMatcher() 58 | eqi, eqj = None, None # 1st indices of equal lines (if any) 59 | 60 | # search for the pair that matches best without being identical 61 | # (identical lines must be junk lines, & we don't want to synch up 62 | # on junk -- unless we have to) 63 | for j in range(blo, bhi): 64 | bj = b[j] 65 | cruncher.set_seq2(bj) 66 | for i in range(alo, ahi): 67 | ai = a[i] 68 | if ai == bj: 69 | if eqi is None: 70 | eqi, eqj = i, j 71 | continue 72 | cruncher.set_seq1(ai) 73 | # computing similarity is expensive, so use the quick 74 | # upper bounds first -- have seen this speed up messy 75 | # compares by a factor of 3. 76 | # note that ratio() is only expensive to compute the first 77 | # time it's called on a sequence pair; the expensive part 78 | # of the computation is cached by cruncher 79 | if cruncher.real_quick_ratio() > best_ratio and \ 80 | cruncher.quick_ratio() > best_ratio and \ 81 | cruncher.ratio() > best_ratio: 82 | best_ratio, best_i, best_j = cruncher.ratio(), i, j 83 | if best_ratio < cutoff: 84 | # no non-identical "pretty close" pair 85 | if eqi is None: 86 | # no identical pair either -- treat it as a straight replace 87 | yield from _plain_replace(a, alo, ahi, b, blo, bhi) 88 | return 89 | # no close pair, but an identical pair -- synch up on that 90 | best_i, best_j, best_ratio = eqi, eqj, 1.0 91 | else: 92 | # there's a close pair, so forget the identical pair (if any) 93 | eqi = None 94 | 95 | # a[best_i] very similar to b[best_j]; eqi is None if they're not 96 | # identical 97 | 98 | # pump out diffs from before the synch point 99 | yield from _fancy_helper(a, alo, best_i, b, blo, best_j) 100 | 101 | # pump out the synch point 102 | yield '

' 103 | if eqi is None: 104 | aelt, belt = a[best_i], b[best_j] 105 | cruncher.set_seqs(aelt, belt) 106 | for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes(): 107 | if tag == 'replace': 108 | yield from _dump_chunk('chg-del', aelt[ai1:ai2]) 109 | yield from _dump_chunk('chg-ins', belt[bj1:bj2]) 110 | elif tag == 'delete': 111 | yield from _dump_chunk('del', aelt[ai1:ai2]) 112 | elif tag == 'insert': 113 | yield from _dump_chunk('ins', belt[bj1:bj2]) 114 | elif tag == 'equal': 115 | yield from _dump_chunk(None, belt[bj1:bj2]) 116 | else: 117 | raise ValueError('unknown tag {0}'.format(tag)) 118 | else: 119 | yield from _dump_chunk(None, a[best_i]) 120 | yield '

' 121 | 122 | # pump out diffs from after the synch point 123 | yield from _fancy_helper(a, best_i + 1, ahi, b, best_j + 1, bhi) 124 | 125 | 126 | def _fancy_helper(a, alo, ahi, b, blo, bhi): 127 | if alo < ahi: 128 | if blo < bhi: 129 | g = _fancy_replace(a, alo, ahi, b, blo, bhi) 130 | else: 131 | g = _dump_lines('del', a, alo, ahi) 132 | elif blo < bhi: 133 | g = _dump_lines('ins', b, blo, bhi) 134 | else: 135 | g = [] 136 | 137 | yield from g 138 | 139 | 140 | def _dump_lines(tag, x, lo, hi): 141 | """Generate comparison results for a same-tagged range.""" 142 | for l in x[lo:hi]: 143 | yield from _dump_line(tag, l) 144 | 145 | 146 | def _dump_line(tag, x): 147 | """Generate comparison results for a same-tagged range.""" 148 | yield '

' 149 | yield from _dump_chunk(tag, x) 150 | yield '

' 151 | 152 | 153 | def _dump_chunk(tag, x): 154 | """Generate comparison results for a same-tagged range.""" 155 | if tag: 156 | yield ''.join(('')) 157 | yield from _to_html(x) 158 | if tag: 159 | yield '' 160 | 161 | 162 | def _to_html(text): 163 | yield html.escape(text, quote=False).replace(' ', '  ') or '↵' 164 | -------------------------------------------------------------------------------- /modules/popup/factory.py: -------------------------------------------------------------------------------- 1 | """Diff Popup Factory Module. 2 | 3 | Contains all functions required to built the diff popup and its content. 4 | """ 5 | import sublime 6 | import mdpopups 7 | from . import differ 8 | from .. import revert 9 | 10 | 11 | def show_diff_popup(git_gutter, **kwargs): 12 | """Show the diff popup. 13 | 14 | Arguments: 15 | git_gutter (GitGutterCommand): 16 | The main command object, which represents GitGutter. 17 | kwargs (dict): 18 | The arguments passed from GitGutterDiffPopupCommand 19 | to GitGutterCommand. 20 | """ 21 | if not git_gutter.git_handler.in_repo(): 22 | return 23 | # validate highlighting argument 24 | highlight_diff = kwargs.get('highlight_diff') 25 | if highlight_diff is None: 26 | mode = git_gutter.settings.get('diff_popup_default_mode', '') 27 | highlight_diff = mode == 'diff' 28 | # validate point argument 29 | point = kwargs.get('point') 30 | if point is None: 31 | selection = git_gutter.view.sel() 32 | if not selection: 33 | return 34 | point = selection[0].end() 35 | # get line number from text point 36 | line = git_gutter.view.rowcol(point)[0] + 1 37 | # create popup asynchronously in case it takes several 100ms 38 | _show_diff_popup_impl( 39 | git_gutter, line, highlight_diff, kwargs.get('flags', 0), 40 | git_gutter.git_handler.diff_line_change(line)) 41 | 42 | 43 | def _show_diff_popup_impl(git_gutter, line, highlight_diff, flags, diff_info): 44 | """Show and update the diff popup. 45 | 46 | Arguments: 47 | git_gutter (GitGutterCommand): 48 | The main command object, which represents GitGutter. 49 | line (int): 50 | The line number the diff popup is requested for 51 | highlight_diff (bool): 52 | If True to the diff is displayed instead of the old revision. 53 | flags (int): 54 | Sublime Text popup flags. 55 | diff_info (tuple): 56 | All the information required to display the diff popup. 57 | """ 58 | del_lines, start, size, meta = diff_info 59 | if start == -1: 60 | return 61 | 62 | view = git_gutter.view 63 | 64 | # extract the type of the hunk: removed, modified, (x)or added 65 | is_removed = size == 0 66 | is_modified = not is_removed and bool(del_lines) 67 | is_added = not is_removed and not is_modified 68 | 69 | def navigate(href): 70 | # allow navigate() to manipulate the outer variables 71 | nonlocal highlight_diff 72 | 73 | if href == 'hide': 74 | view.hide_popup() 75 | elif href == 'copy': 76 | del_text = '\n'.join(del_lines) 77 | sublime.set_clipboard(del_text) 78 | sublime.status_message( 79 | 'Copied: {0} characters'.format(len(del_text))) 80 | elif href == 'revert': 81 | # hide the popup and update the view 82 | view.hide_popup() 83 | revert.revert_change_impl(view, diff_info) 84 | elif href == 'disable_hl_diff': 85 | # show a diff popup with the same diff info (previous revision) 86 | highlight_diff = False 87 | _show_diff_popup_impl( 88 | git_gutter, line, highlight_diff, flags, diff_info) 89 | elif href == 'enable_hl_diff': 90 | # show a diff popup with the same diff info (highlight diff) 91 | highlight_diff = True 92 | _show_diff_popup_impl( 93 | git_gutter, line, highlight_diff, flags, diff_info) 94 | elif href in ('first_change', 'next_change', 'prev_change'): 95 | next_line = meta.get(href, line) 96 | point = view.text_point(next_line - 1, 0) 97 | 98 | def show_new_popup(): 99 | # wait until scrolling has completed 100 | if not view.visible_region().contains(point): 101 | return sublime.set_timeout_async(show_new_popup, 20) 102 | # show a diff popup with new diff info 103 | _show_diff_popup_impl( 104 | git_gutter, next_line, highlight_diff, 0, 105 | git_gutter.git_handler.diff_line_change(next_line)) 106 | view.hide_popup() 107 | view.show_at_center(point) 108 | show_new_popup() 109 | 110 | # write the symbols/text for each button 111 | buttons = _built_toolbar_buttons(start, meta) 112 | location = _visible_text_point(view, line - 1, 0) 113 | code_wrap = view.settings().get('word_wrap') 114 | if code_wrap == 'auto': 115 | code_wrap = view.match_selector(location, 'source') 116 | 117 | if highlight_diff: 118 | # (*) show a highlighted diff of the merged git and editor content 119 | new_lines = meta['added_lines'] 120 | tab_width = view.settings().get('tab_width', 4) 121 | min_indent = _get_min_indent(del_lines + new_lines, tab_width) 122 | content = ( 123 | '

' 124 | '{hide} ' 125 | '{first_change} {prev_change} {next_change} ' 126 | '{disable_hl_diff} {revert}' 127 | '
' 128 | .format(**buttons) 129 | ) + differ.highlight_diff( 130 | [line.expandtabs(tab_width)[min_indent:] for line in del_lines], 131 | [line.expandtabs(tab_width)[min_indent:] for line in new_lines]) 132 | 133 | elif not is_added: 134 | # (modified/removed) show content from git database 135 | tab_width = view.settings().get('tab_width', 4) 136 | min_indent = _get_min_indent(del_lines, tab_width) 137 | source_content = '\n'.join( 138 | (line.expandtabs(tab_width)[min_indent:] for line in del_lines)) 139 | content = ( 140 | '
' 141 | '{hide} ' 142 | '{first_change} {prev_change} {next_change} ' 143 | '{enable_hl_diff} {copy} {revert}' 144 | '
' 145 | .format(**buttons) 146 | ) + mdpopups.syntax_highlight( 147 | view, 148 | source_content, 149 | language=mdpopups.get_language_from_view(view) or '', 150 | allow_code_wrap=code_wrap 151 | ) 152 | 153 | else: 154 | # (added) only show the button line without the copy button 155 | # (there is nothing to show or copy) 156 | content = ( 157 | '
' 158 | '{hide} ' 159 | '{first_change} {prev_change} {next_change} ' 160 | '{enable_hl_diff} {revert}' 161 | '
' 162 | .format(**buttons) 163 | ) 164 | 165 | # common arguments used to create or update the popup 166 | popup_kwargs = { 167 | 'view': view, 168 | 'content': content, 169 | 'md': False, 170 | 'css': _load_popup_css(git_gutter.settings.theme_path), 171 | 'wrapper_class': 'git-gutter' 172 | } 173 | # update visible popup 174 | if view.is_popup_visible(): 175 | return mdpopups.update_popup(**popup_kwargs) 176 | # calculate optimal popup width to apply desired wrapping 177 | popup_width = int(view.viewport_extent()[0]) 178 | if code_wrap: 179 | line_length = view.settings().get('wrap_width', 0) 180 | if line_length > 0: 181 | popup_width = (line_length + 5) * view.em_width() 182 | # create new popup 183 | return mdpopups.show_popup( 184 | location=location, max_width=popup_width, flags=flags, 185 | on_navigate=navigate, **popup_kwargs) 186 | 187 | 188 | def _built_toolbar_buttons(start, meta): 189 | """Built the toolbar buttons with icon/text as link/label. 190 | 191 | Each toolbar button needs to be rendered using the unicode icon character 192 | or a text label for those who don't like icons. If enabled each button is 193 | attached to a link. 194 | 195 | Arguments: 196 | start (int): 197 | First line of the current hunk. 198 | meta (dict): 199 | The dictionay containing additional hunk information. 200 | 201 | Returns: 202 | dict: The dictionay with all buttons where `key` is used as `href` 203 | and value as link caption. 204 | 205 | active icon button: 206 | active text button: (revert) 207 | inactive icon button: 208 | inactive text button: (revert) 209 | """ 210 | # The format to render disabled/enabled buttons 211 | button_format = ( 212 | '{0}', 213 | '{0}' 214 | ) 215 | # The buttons as a map from the href to the caption/icon 216 | button_caption = { 217 | 'hide': '×', 218 | 'copy': '⎘', 219 | 'revert': '⟲', 220 | 'disable_hl_diff': '≉', 221 | 'enable_hl_diff': '≈', 222 | 'first_change': '⤒', 223 | 'prev_change': '↑', 224 | 'next_change': '↓' 225 | } 226 | return { 227 | key: button_format[ 228 | not key.endswith('_change') or meta.get(key, start) != start 229 | ].format(value, key) for key, value in button_caption.items() 230 | } 231 | 232 | 233 | def _load_popup_css(theme_path): 234 | """Load and join popup stylesheets. 235 | 236 | Arguments: 237 | theme_path (string): 238 | The path to the active gitgutter-theme file. 239 | """ 240 | css_lines = [] 241 | for path in ('Packages/GitGutter', theme_path, 'Packages/User'): 242 | try: 243 | css_path = path + '/gitgutter_popup.css' 244 | css_lines.append(sublime.load_resource(css_path)) 245 | except IOError: 246 | pass 247 | return ''.join(css_lines) 248 | 249 | 250 | def _get_min_indent(lines, tab_width=4): 251 | """Find the minimum count of indenting whitespaces in lines. 252 | 253 | Arguments: 254 | lines (tuple): 255 | The content to search the minimum indention for. 256 | tab_width (int): 257 | The number of spaces expand tabs before searching for indention by. 258 | """ 259 | min_indent = 2**32 260 | for line in lines: 261 | i = 0 262 | for c in line: 263 | if c == ' ': 264 | i += 1 265 | elif c == '\t': 266 | i += tab_width - (i % tab_width) 267 | else: 268 | break 269 | 270 | if min_indent > i: 271 | min_indent = i 272 | if not min_indent: 273 | break 274 | return min_indent 275 | 276 | 277 | def _visible_text_point(view, row, col): 278 | """Return the text_point of row,col clipped to the visible viewport. 279 | 280 | Arguments: 281 | view (sublime.View): 282 | the view to return the text_point for 283 | row (int): 284 | the row to use for text_point calculation 285 | col (int): 286 | the column relative to the first visible column of the viewport 287 | which is defined by the horizontal scroll position. 288 | Returns: 289 | int: The text_point of row & col within the viewport. 290 | """ 291 | viewport = view.visible_region() 292 | _, vp_col = view.rowcol(viewport.begin()) 293 | return view.text_point(row, vp_col + col) 294 | -------------------------------------------------------------------------------- /modules/promise.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import threading 3 | 4 | 5 | class PromiseError(Exception): 6 | """A failed Promise is to be resolved with this PromiseError. 7 | 8 | Normal errors and exceptions can't be catched by the applicaiton due to 9 | multithreading. Therefore a function should return PromiseError to indicate 10 | a failure to be handled by `.then()` 11 | """ 12 | pass 13 | 14 | 15 | class Promise(object): 16 | """A simple implementation of the Promise specification. 17 | 18 | See: https://promisesaplus.com 19 | 20 | Promise is in essence a syntactic sugar for callbacks. Simplifies passing 21 | values from functions that might do work in asynchronous manner. 22 | 23 | Example usage: 24 | 25 | * Passing return value of one function to another: 26 | 27 | def do_work_async(resolve): 28 | # "resolve" is a function that, when called with a value, resolves 29 | # the promise with provided value and passes the value to the next 30 | # chained promise. 31 | resolve(111) # Can be invoked asynchronously. 32 | 33 | def process_value(value): 34 | assert value === 111 35 | 36 | Promise(do_work_async).then(process_value) 37 | 38 | * Returning Promise from chained promise: 39 | 40 | def do_work_async_1(resolve): 41 | # Compute value asynchronously. 42 | resolve(111) 43 | 44 | def do_work_async_2(resolve): 45 | # Compute value asynchronously. 46 | resolve(222) 47 | 48 | def do_more_work_async(value): 49 | # Do more work with the value asynchronously. For the sake of this 50 | # example, we don't use 'value' for anything. 51 | assert value === 111 52 | return Promise(do_work_async_2) 53 | 54 | def process_value(value): 55 | assert value === 222 56 | 57 | Promise(do_work_async_1).then(do_more_work_async).then(process_value) 58 | """ 59 | 60 | def __init__(self, executor): 61 | """Initialize Promise object. 62 | 63 | Arguments: 64 | executor: A function that is executed immediately by this Promise. 65 | It gets passed a "resolve" function. The "resolve" function, when 66 | called, resolves the Promise with the value passed to it. 67 | """ 68 | self.value = None 69 | self.resolved = False 70 | self.mutex = threading.Lock() 71 | self.callbacks = [] 72 | self._invoke_executor(executor) 73 | 74 | @classmethod 75 | def resolve(cls, resolve_value=None): 76 | """Immediatelly resolve a Promise. 77 | 78 | Convenience function for creating a Promise that gets immediately 79 | resolved with the specified value. 80 | 81 | Arguments: 82 | resolve_value: The value to resolve the promise with. 83 | """ 84 | def executor(resolve_fn): 85 | return resolve_fn(resolve_value) 86 | 87 | return cls(executor) 88 | 89 | def then(self, callback): 90 | """Create a new promise and chain it with this promise. 91 | 92 | When this promise gets resolved, the callback will be called with the 93 | value that this promise resolved with. 94 | 95 | Returns a new promise that can be used to do further chaining. 96 | 97 | Arguments: 98 | callback: The callback to call when this promise gets resolved. 99 | """ 100 | def callback_wrapper(resolve_fn, resolve_value): 101 | """A wrapper called when this promise resolves. 102 | 103 | Arguments: 104 | resolve_fn: A resolve function of newly created promise. 105 | resolve_value: The value with which this promise resolved. 106 | """ 107 | result = callback(resolve_value) 108 | # If returned value is a promise then this promise needs to be 109 | # resolved with the value of returned promise. 110 | if isinstance(result, Promise): 111 | result.then(resolve_fn) 112 | else: 113 | resolve_fn(result) 114 | 115 | def sync_executor(resolve_fn): 116 | """Call resolve_fn immediately with the resolved value. 117 | 118 | An executor that will immediately resolve resolve_fn with the 119 | resolved value of this promise. 120 | """ 121 | callback_wrapper(resolve_fn, self._get_value()) 122 | 123 | def async_executor(resolve_fn): 124 | """Queue resolve_fn to be called after this promise resolves later. 125 | 126 | An executor that will resolve received resolve_fn when this promise 127 | resolves later. 128 | """ 129 | self._add_callback(functools.partial(callback_wrapper, resolve_fn)) 130 | 131 | if self._is_resolved(): 132 | return Promise(sync_executor) 133 | return Promise(async_executor) 134 | 135 | def _invoke_executor(self, executor): 136 | def resolve_fn(new_value): 137 | self._do_resolve(new_value) 138 | 139 | executor(resolve_fn) 140 | 141 | def _do_resolve(self, new_value): 142 | # No need to block as we can't change from resolved to unresolved. 143 | if self.resolved: 144 | raise RuntimeError( 145 | "cannot set the value of an already resolved promise") 146 | with self.mutex: 147 | self.value = new_value 148 | for callback in self.callbacks: 149 | callback(new_value) 150 | self.resolved = True 151 | 152 | def _add_callback(self, callback): 153 | with self.mutex: 154 | self.callbacks.append(callback) 155 | 156 | def _is_resolved(self): 157 | with self.mutex: 158 | return self.resolved 159 | 160 | def _get_value(self): 161 | with self.mutex: 162 | return self.value 163 | -------------------------------------------------------------------------------- /modules/revert.py: -------------------------------------------------------------------------------- 1 | from .utils import line_from_kwargs 2 | 3 | 4 | def revert_change(git_gutter, **kwargs): 5 | """Revert changed hunk under the cursor. 6 | 7 | Arguments: 8 | git_gutter (GitGutterCommand): 9 | The main command object, which represents GitGutter 10 | and called this functiond. 11 | kwargs (dict): 12 | The arguments passed from GitGutterRevertChangesCommand 13 | to GitGutterCommand. 14 | 15 | valid kwargs are: 16 | line (int): zero-based line number within the hunk to copy 17 | point (int): zero based text position within the hunk to copy 18 | """ 19 | line = line_from_kwargs(git_gutter.view, kwargs) 20 | revert_change_impl( 21 | git_gutter.view, 22 | git_gutter.git_handler.diff_line_change(line + 1)) 23 | 24 | 25 | def revert_change_impl(view, diff_info): 26 | """Revert changes defined by diff_info. 27 | 28 | Arguments: 29 | view (sublime.View): 30 | The view in which the changes are to revert. 31 | diff_info (tuple): 32 | All the information required to revert the changes. 33 | """ 34 | del_lines, start, size, meta = diff_info 35 | if start == -1: 36 | return 37 | 38 | # extract the type of the hunk: removed, modified, (x)or added 39 | is_removed = size == 0 40 | is_modified = not is_removed and bool(del_lines) 41 | 42 | new_text = '\n'.join(del_lines) 43 | # (removed) if there is no text to remove, set the 44 | # region to the end of the line, where the hunk starts 45 | # and add a new line to the start of the text 46 | if is_removed: 47 | if start != 0: 48 | # set the start and the end to the end of the start line 49 | start_point = end_point = view.text_point(start, 0) - 1 50 | # add a leading newline before inserting the text 51 | new_text = '\n' + new_text 52 | else: 53 | # (special handling for deleted at the start of the file) 54 | # if we are before the start we need to set the start 55 | # to 0 and add the newline behind the text 56 | start_point = end_point = 0 57 | new_text = new_text + '\n' 58 | # (modified/added) 59 | # set the start point to the start of the hunk 60 | # and the end point to the end of the hunk 61 | else: 62 | start_point = view.text_point(start - 1, 0) 63 | end_point = view.text_point(start + size - 1, 0) 64 | # (modified) if there is text to insert, we 65 | # don't want to capture the trailing newline, 66 | # because we insert lines without a trailing newline 67 | if is_modified and end_point != view.size(): 68 | end_point -= 1 69 | view.run_command('git_gutter_replace_text', { 70 | 'start': start_point, 'end': end_point, 'text': new_text}) 71 | -------------------------------------------------------------------------------- /modules/settings.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sublime 3 | 4 | from .utils import PLATFORM 5 | 6 | 7 | def get(key, default=None): 8 | """Get a value from GitGutter.sublime-settings. 9 | 10 | This function provides secure access to the package settings by loading 11 | the settings file on demand. 12 | 13 | Arguments: 14 | key (string): The setting to read. 15 | default (any): The value to return if 'key' is not available. 16 | 17 | Returns: 18 | any: The value from settings file if loaded and key exists or the 19 | default value provided from the caller. 20 | """ 21 | try: 22 | settings = get.settings 23 | except AttributeError: 24 | settings = sublime.load_settings('GitGutter.sublime-settings') 25 | get.settings = settings 26 | return settings.get(key, default) 27 | 28 | 29 | class ViewSettings(object): 30 | """The class provides a layer to merge view and package settings. 31 | 32 | The ViewSettings class provides a common interface to support 33 | view or project based GitGutter settings. All settings found in the 34 | GitGutter.sublime-settings file can be placed in Preferences or the 35 | settings of a *.sublime-project file as well by simply prepending the 36 | 'git_gutter_'. This even allows temporary changes for single views. 37 | 38 | Note: 39 | The only exception is 'git_binary' setting, which is NOT prefixed. 40 | It is searched in all settings files as 'git_binary'. 41 | 42 | Example: 43 | GitGutter.sublime-settings 44 | { 45 | show_in_minimap: 3 46 | } 47 | 48 | Preferences.sublime-settings 49 | { 50 | git_gutter_show_in_minimap: 3 51 | } 52 | 53 | Project.sublime-project 54 | { 55 | ... 56 | settings: 57 | { 58 | ... 59 | git_gutter_show_in_minimap: 3 60 | ... 61 | } 62 | } 63 | """ 64 | 65 | # The built in themes path 66 | _PACKAGE_THEMES = 'Packages/GitGutter/themes' 67 | # A map to translate between settings and git arguments 68 | _IGNORE_WHITESPACE = { 69 | 'none': '', 70 | 'cr': '--ignore-cr-at-eol', 71 | 'eol': '--ignore-space-at-eol', 72 | 'space': '-b', 73 | 'all': '-w' 74 | } 75 | # A map to translate between settings and git arguments 76 | _DIFF_ALGORITHM = { 77 | 'minimal': '--minimal', 78 | 'patience': '--patience', 79 | 'histogram': '--histogram' 80 | } 81 | 82 | def __init__(self, view): 83 | """Initialize a ViewSettings object. 84 | 85 | Arguments: 86 | view (View): The view object whose settings to attach to 87 | the created ViewSettings object. 88 | """ 89 | # view settings object 90 | self._settings = view.settings() 91 | # cached theme path to reduce calls of find_resources 92 | self._theme_path = '' 93 | 94 | def get(self, key, default=None): 95 | """Get a setting from attached view or GitGutter settings. 96 | 97 | Arguments: 98 | key (string): The setting to read. 99 | default (any): The default value to return if the setting does 100 | not exist in the view or GitGutter settings. 101 | 102 | Returns: 103 | any: The read value or default. 104 | """ 105 | result = self._settings.get('git_gutter_' + key) 106 | if result is not None: 107 | return result 108 | return get(key, default) 109 | 110 | @property 111 | def show_in_minimap(self): 112 | """The appropiatly limited show_in_minimap setting.""" 113 | width = self.get('show_in_minimap', 1) 114 | return width if width >= 0 else 100000 115 | 116 | @property 117 | def theme_path(self): 118 | """Read 'theme' setting and return path to gutter icons.""" 119 | theme = self.get('theme') 120 | if not theme: 121 | theme = 'Default.gitgutter-theme' 122 | # rebuilt path if setting changed 123 | if theme != os.path.basename(self._theme_path): 124 | themes = sublime.find_resources(theme) 125 | self._theme_path = ( 126 | os.path.dirname(themes[-1]) 127 | if themes else self._PACKAGE_THEMES + '/Default') 128 | return self._theme_path 129 | 130 | @property 131 | def git_binary(self): 132 | """Return the git executable path from settings or just 'git'. 133 | 134 | Try to get the absolute git executable path from any of the settings 135 | files (view/project/user/gitgutter). If none is set just return 'git' 136 | and let subprocess.POpen use the PATH environment variable to find the 137 | executable path on its own. 138 | 139 | Returns: 140 | string: Absolute path of the git executable from settings or 'git'. 141 | """ 142 | value = self._settings.get('git_binary') 143 | if value is None: 144 | value = get('git_binary') 145 | if isinstance(value, dict): 146 | git_binary = value.get(PLATFORM) or value.get('default') 147 | else: 148 | git_binary = value 149 | return os.path.expandvars(git_binary) if git_binary else 'git' 150 | 151 | @property 152 | def ignore_whitespace(self): 153 | """The git ignore whitespace command line argument from settings.""" 154 | try: 155 | return self._IGNORE_WHITESPACE[self.get('ignore_whitespace')] 156 | except KeyError: 157 | return None 158 | 159 | @property 160 | def diff_algorithm(self): 161 | """The git diff algorithm command line argument from settings. 162 | 163 | Returns: 164 | string: 165 | One of (--minimal, --patience, --histogram) 166 | or None if setting is invalid. 167 | """ 168 | return self._DIFF_ALGORITHM.get(self.get('diff_algorithm')) 169 | -------------------------------------------------------------------------------- /modules/statusbar.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from . import blame 4 | from . import templates 5 | 6 | 7 | class SimpleStatusBarTemplate(object): 8 | """A simple template class with the same interface as jinja2's one.""" 9 | 10 | # a list of variables used by this template 11 | variables = frozenset([ 12 | 'repo', 'branch', 'compare', 'inserted', 'deleted', 'modified', 13 | 'line_author', 'line_author_age' 14 | ]) 15 | 16 | @staticmethod 17 | def render(repo=None, branch=None, compare=None, inserted=0, deleted=0, 18 | modified=0, line_author=None, line_author_age=None, **kwargs): 19 | """Format the status bar text using a static set of rules. 20 | 21 | Arguments: 22 | repo (string): The repository name 23 | branch (string): The branch name. 24 | compare (string): The compared branch/tag/commit 25 | inserted (int): The amount of inserted lines 26 | deleted (int): The amount of deleted lines 27 | modified (int): The amount of modified lines 28 | line_author (string): The author of the active line 29 | line_author_age (string): The age of the active line's change 30 | 31 | Returns: 32 | string: The formatted message to display in the status bar. 33 | """ 34 | if not repo or not branch: 35 | return '' 36 | 37 | parts = ['{repo}/{branch}'] 38 | 39 | # Compare against 40 | if compare not in ('HEAD', branch, None): 41 | parts.append('Comparing against {compare}') 42 | 43 | # File statistics 44 | if inserted: 45 | parts.append('{inserted}+') 46 | if deleted: 47 | parts.append('{deleted}-') 48 | if modified: 49 | parts.append(u'{modified}≠') 50 | 51 | # blame message 52 | if line_author and line_author_age: 53 | parts.append(u'⟢ {line_author} ({line_author_age})') 54 | 55 | # join template and fill with locals 56 | return ', '.join(parts).format(**locals()) 57 | 58 | 59 | class GitGutterStatusBar(object): 60 | """The class manages status bar text rendering. 61 | 62 | It stores all information, which might get displayed in the status bar 63 | and provides functions to partially update them. 64 | """ 65 | 66 | def __init__(self, view, settings): 67 | """Initialize object.""" 68 | # the sublime.View the status bar is attached to 69 | self.view = view 70 | # the settings.ViewSettings object which stores GitGutter' settings 71 | self.settings = settings 72 | # initialize the jinja2 template 73 | self._template = None 74 | 75 | # the variables to use to render the status bar 76 | self.vars = { 77 | # sublime text git integration enabled 78 | 'st_git_status': view.settings().get('show_git_status', False), 79 | # the repository name 80 | 'repo': None, 81 | # the active branch name 82 | 'branch': None, 83 | # the branch we compare against 84 | 'compare': None, 85 | # the upstream branch name 86 | 'remote': None, 87 | # the commits the local is ahead of upstream 88 | 'ahead': 0, 89 | # the commits the local is behind of upstream 90 | 'behind': 0, 91 | 92 | # repository statistics 93 | 'added_files': 0, 94 | 'deleted_files': 0, 95 | 'modified_files': 0, 96 | 'staged_files': 0, 97 | 98 | # file statistics 99 | 'state': None, 100 | 'deleted': 0, 101 | 'inserted': 0, 102 | 'modified': 0, 103 | } 104 | # declare all blame variables 105 | for var in blame.BLAME_VARIABLES: 106 | self.vars[var] = None 107 | 108 | @property 109 | def template(self): 110 | """"Create the template on demand and return it.""" 111 | if not self._template: 112 | self._template = templates.create( 113 | self.settings, 'status_bar_text', SimpleStatusBarTemplate) 114 | return self._template 115 | 116 | def is_enabled(self): 117 | """Return whether status bar text is enabled in settings or not.""" 118 | enabled = self.settings.get('show_status_bar_text', False) 119 | if self._template and not enabled: 120 | self._template = None 121 | self.vars['repo'] = None 122 | self.erase() 123 | return enabled 124 | 125 | def has(self, variables): 126 | """Check if a set of variables is used by the user defined template. 127 | 128 | Arguments: 129 | variables (iter): 130 | An iterateable object with all the variables to check for 131 | existence within the active template. 132 | Returns: 133 | bool: 134 | True - if at least one variable is used by the template. 135 | False - if no variable is used by the template. 136 | """ 137 | try: 138 | return any(var in self.template.variables for var in variables) 139 | except: 140 | return False 141 | 142 | def erase(self): 143 | """Erase status bar text.""" 144 | self.view.erase_status('00_git_gutter') 145 | 146 | def update(self, **kwargs): 147 | """Update a set of variables and redraw the status bar text. 148 | 149 | Arguments: 150 | kwargs (dict): 151 | The dictionary of possibly changed variables to update the 152 | status bar text with. 153 | Raises: 154 | KeyError, if `kwargs` contains unknown variables. 155 | """ 156 | want_update = False 157 | for key, value in kwargs.items(): 158 | if self.vars[key] != value: 159 | self.vars[key] = value 160 | want_update = True 161 | 162 | if want_update: 163 | self.view.set_status( 164 | '00_git_gutter', self.template.render(**self.vars)) 165 | -------------------------------------------------------------------------------- /modules/support.py: -------------------------------------------------------------------------------- 1 | """Support Information module. 2 | 3 | The module provides functions to gain information to be included in issues. 4 | It neither contains normal functionality nor is it used by GitGutter. 5 | """ 6 | import os 7 | import subprocess 8 | import textwrap 9 | 10 | import sublime 11 | import sublime_plugin 12 | 13 | # get absolute path of the package 14 | PACKAGE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 15 | if os.path.isfile(PACKAGE_PATH): 16 | # Package is a PACKAGE.sublime-package so get its filename 17 | PACKAGE, _ = os.path.splitext(os.path.basename(PACKAGE_PATH)) 18 | elif os.path.isdir(PACKAGE_PATH): 19 | # Package is a directory, so get its basename 20 | PACKAGE = os.path.basename(PACKAGE_PATH) 21 | else: 22 | raise ValueError('Package is no file and no directory!') 23 | 24 | 25 | def git(*args): 26 | """Read version of git binary.""" 27 | if os.name == 'nt': 28 | startupinfo = subprocess.STARTUPINFO() 29 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 30 | else: 31 | startupinfo = None 32 | proc = subprocess.Popen( 33 | args=['git'] + [arg for arg in args], startupinfo=startupinfo, 34 | stdout=subprocess.PIPE, stdin=subprocess.PIPE, 35 | # run command in package directory if exists. 36 | cwd=PACKAGE_PATH if os.path.isdir(PACKAGE_PATH) else None) 37 | stdout, _ = proc.communicate() 38 | return stdout.decode('utf-8').strip() if stdout else None 39 | 40 | 41 | def git_version(): 42 | """Read version of git binary.""" 43 | try: 44 | return git('--version') 45 | except Exception as exception: 46 | print('%s: %s' % (PACKAGE, exception)) 47 | return 'git version could not be acquired!' 48 | 49 | 50 | def gitgutter_version(): 51 | """Read commit hash or version of GitGutter.""" 52 | try: 53 | return git('rev-parse', 'HEAD')[:7] 54 | except: 55 | try: 56 | return sublime.load_resource( 57 | 'Packages/%s/VERSION' % PACKAGE).strip() 58 | except Exception as exception: 59 | print('%s: %s' % (PACKAGE, exception)) 60 | return 'Version could not be acquired!' 61 | 62 | 63 | def module_version(module, attr): 64 | """Format the module version.""" 65 | try: 66 | version = getattr(module, attr) 67 | if callable(version): 68 | version = version() 69 | except Exception as exception: 70 | print('%s: %s' % (PACKAGE, exception)) 71 | version = 'version could not be acquired!' 72 | 73 | if not isinstance(version, str): 74 | version = '.'.join((str(x) for x in version)) 75 | return version 76 | 77 | 78 | def is_installed_by_package_control(): 79 | """Check if installed by package control.""" 80 | settings = sublime.load_settings('Package Control.sublime-settings') 81 | return str(PACKAGE in set(settings.get('installed_packages', []))) 82 | 83 | 84 | class GitGutterSupportInfoCommand(sublime_plugin.ApplicationCommand): 85 | """Support Information Command.""" 86 | 87 | @staticmethod 88 | def run(): 89 | """Run command.""" 90 | info = { 91 | 'platform': sublime.platform(), 92 | 'st_version': sublime.version(), 93 | 'arch': sublime.arch(), 94 | 'package_version': gitgutter_version(), 95 | 'pc_install': is_installed_by_package_control(), 96 | 'git_version': git_version() 97 | } 98 | 99 | try: 100 | import mdpopups 101 | info['mdpopups'] = module_version(mdpopups, 'version') 102 | except ImportError: 103 | info['mdpopups'] = 'not installed!' 104 | 105 | try: 106 | from mdpopups import jinja2 107 | info['jinja'] = module_version(jinja2, '__version__') 108 | except ImportError: 109 | info['jinja'] = 'not installed!' 110 | 111 | msg = textwrap.dedent( 112 | """\ 113 | - Sublime Text %(st_version)s 114 | - Platform: %(platform)s 115 | - Arch: %(arch)s 116 | - GitGutter %(package_version)s 117 | - Install via PC: %(pc_install)s 118 | - %(git_version)s 119 | - mdpopups %(mdpopups)s 120 | - jinja2 %(jinja)s 121 | """ % info 122 | ) 123 | 124 | sublime.message_dialog(msg + '\nInfo has been copied to clipboard.') 125 | sublime.set_clipboard(msg) 126 | -------------------------------------------------------------------------------- /modules/tasks.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | from queue import Empty 4 | from queue import Queue 5 | from threading import Thread 6 | 7 | from .promise import Promise 8 | 9 | 10 | class Task(object): 11 | 12 | """ 13 | Task runs a python function `target` when called. 14 | """ 15 | 16 | def __init__(self, target, *args, **kwargs): 17 | """Initialize the Task object.""" 18 | self.target = target 19 | self.args = args 20 | self.kwargs = kwargs 21 | 22 | def run(self): 23 | self.target(*self.args, **self.kwargs) 24 | 25 | 26 | class TaskQueue(Thread): 27 | 28 | """ 29 | A background thread to start all queued processes one after another. 30 | """ 31 | 32 | def __init__(self): 33 | super().__init__(daemon=True) 34 | self.queue = Queue() 35 | self.active_task = None 36 | self.running = False 37 | 38 | def __del__(self): 39 | self.running = False 40 | 41 | def execute(self, task): 42 | self.queue.put(task) 43 | 44 | def cancel_all(self): 45 | try: 46 | while not self.Empty(): 47 | self.queue.get_nowait() 48 | self.queue.task_done() 49 | except Empty: 50 | pass 51 | 52 | def busy(self): 53 | return self.active_task is not None 54 | 55 | def run(self): 56 | self.running = True 57 | while self.running: 58 | task = self.queue.get() 59 | self.active_task = task 60 | try: 61 | task.run() 62 | except: 63 | traceback.print_exc() 64 | finally: 65 | self.queue.task_done() 66 | self.active_task = None 67 | 68 | 69 | _tasks = TaskQueue() 70 | _tasks.start() 71 | 72 | 73 | def busy(): 74 | return _tasks.busy() 75 | 76 | 77 | def execute_async(func, *args, **kwargs): 78 | return Promise(lambda resolve_fn: _tasks.execute( 79 | Task(func, resolve_fn, *args, **kwargs))) 80 | 81 | 82 | def cancel_all(): 83 | _tasks.cancel_all() 84 | -------------------------------------------------------------------------------- /modules/temp.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import os 3 | import tempfile 4 | import time 5 | import uuid 6 | 7 | # The folder to place all temporary files into. 8 | TEMP_DIR = os.environ.get('XDG_RUNTIME_DIR') 9 | if TEMP_DIR: 10 | # If available use the per user XDS_RUNTIME_DIR on unix based OSs. 11 | TEMP_DIR = os.path.join(TEMP_DIR, 'GitGutter') 12 | else: 13 | # Otherwise fallback to the default TEMP folder. 14 | TEMP_DIR = os.path.join(tempfile.gettempdir(), 'GitGutter') 15 | if hasattr(os, 'getuid'): 16 | TEMP_DIR += '.%s' % os.getuid() 17 | 18 | """ 19 | Remove all temporary files older than 2 days. Looks like in some cases some 20 | temporary files are not deleted, if ST is closed. So try to delete too old 21 | ones upon startup. Wait 2 days to reduce the chance of deleting temporary 22 | files of another open ST instance. 23 | """ 24 | now = time.time() 25 | max_age = 48 * 60 * 60 26 | try: 27 | files = os.listdir(TEMP_DIR) 28 | except FileNotFoundError: 29 | # tempfolder does not exist 30 | pass 31 | else: 32 | for name in files: 33 | try: 34 | path = os.path.join(TEMP_DIR, name) 35 | if now - os.path.getatime(path) > max_age: 36 | os.remove(path) 37 | except OSError: 38 | pass 39 | 40 | 41 | def cleanup(): 42 | try: 43 | files = os.listdir(TEMP_DIR) 44 | except FileNotFoundError: 45 | # tempfolder does not exist 46 | pass 47 | else: 48 | for name in files: 49 | try: 50 | os.remove(os.path.join(TEMP_DIR, name)) 51 | except OSError: 52 | pass 53 | os.rmdir(TEMP_DIR) 54 | 55 | 56 | class TempFile(object): 57 | """A temporary file object which allows shared reading. 58 | 59 | All the temporary files created by the `tempfile` module's functions are 60 | accessible by the calling process only on some operating systems. 61 | Therefore this class represents a file which is deleted as soon as the 62 | object is destroyed but without keeping the file open all the time. 63 | """ 64 | 65 | def __init__(self, mode='r'): 66 | """Initialize TempFile object.""" 67 | self.name = None 68 | while self.name is None: 69 | candidate = os.path.join(TEMP_DIR, str(uuid.uuid1())) 70 | if not os.path.exists(candidate): 71 | self.name = candidate 72 | self._file = None 73 | self._mode = mode 74 | # Cache unlink to keep it available even though the 'os' module is 75 | # already None'd out whenever __del__() is called. 76 | # See python stdlib's tempfile.py for details. 77 | self._unlink = os.unlink 78 | 79 | def __del__(self): 80 | """Destroy the TempFile object and remove the file from disk.""" 81 | try: 82 | self.close() 83 | self._unlink(self.name) 84 | except OSError: 85 | pass 86 | 87 | def __enter__(self): 88 | """`With` statement support.""" 89 | return self.open() 90 | 91 | def __exit__(self, exc, value, tb): 92 | """`With` statement support.""" 93 | self.close() 94 | 95 | def open(self): 96 | """Open temporary file.""" 97 | if self._file is None: 98 | # ensure cache directory exists with write permissions 99 | os.makedirs(TEMP_DIR, 0o700, exist_ok=True) 100 | self._file = open( 101 | file=self.name, 102 | mode=self._mode, 103 | opener=lambda file, flags: os.open(file, flags, 0o600) 104 | ) 105 | return self._file 106 | 107 | def close(self): 108 | """Close temporary file.""" 109 | if self._file is not None: 110 | self._file.close() 111 | self._file = None 112 | 113 | def tell(self): 114 | return self._file.tell() 115 | -------------------------------------------------------------------------------- /modules/templates.py: -------------------------------------------------------------------------------- 1 | """JINJA2 Template Cache Manager module. 2 | 3 | The module acts as intermediate layer to make use of jinja2 library if it is 4 | available but keep running smoothly if not. 5 | """ 6 | try: 7 | # avoid exceptions if dependency is not yet satisfied 8 | from mdpopups.jinja2 import meta 9 | from mdpopups.jinja2 import Environment 10 | from mdpopups.jinja2 import TemplateSyntaxError 11 | from weakref import WeakValueDictionary 12 | 13 | from .utils import log_message 14 | 15 | # jinja environment to use to create templates 16 | _jinja_env = Environment() 17 | # template cache to reuse existing templates 18 | _templates_cache = WeakValueDictionary() 19 | 20 | def create(settings, key, simple_template): 21 | """Create a template from source and store a weak reference as cache. 22 | 23 | Instead of creating a `Template` per view, the source of the template 24 | is used to identify the template and reuse one `Template` object for 25 | each matching source read from the view's settings. 26 | 27 | If no custom (view-, syntax-, project-specific) template is set 28 | up anywhere this dictionary holds only one `Template` normally. 29 | 30 | Arguments: 31 | settings (Settings): 32 | An settings object which can be queried via `get` to read the 33 | source of the template. 34 | key (string): 35 | The settings key to use to read the template source. 36 | simple_template (class): 37 | The class to instantiate, if jinja2 failed to compile the 38 | template source. 39 | 40 | Returns: 41 | jinja2.Template: 42 | if jinja2 is available and a valid template is defined 43 | SimpleTemplate: 44 | if jinnja2 is not present or failed loading the Template 45 | """ 46 | # read the template from settings 47 | source = settings.get(key) 48 | if not source: 49 | return simple_template() 50 | # join a list of lines to a single source. 51 | if isinstance(source, list): 52 | source = ''.join(source) 53 | 54 | key = hash(source) 55 | try: 56 | # try the cached template 57 | return _templates_cache[key] 58 | except KeyError: 59 | try: 60 | # create the template from source string 61 | template = _jinja_env.from_string(source) 62 | # generate a list of all variables being in use by the template 63 | setattr(template, 'variables', set( 64 | meta.find_undeclared_variables(_jinja_env.parse(source)))) 65 | _templates_cache[key] = template 66 | return template 67 | except TemplateSyntaxError: 68 | log_message('"{}" contains malformed template!'.format(key)) 69 | return simple_template() 70 | 71 | except ImportError: 72 | 73 | def create(settings, key, simple_template): 74 | """Always return a simple template. 75 | 76 | Arguments: 77 | settings (Settings): 78 | not used 79 | key (string): 80 | not used 81 | simple_template (class): 82 | The class to instantiate 83 | 84 | Returns: 85 | SimpleTemplate: 86 | Always returns the instantiated simple template. 87 | """ 88 | return simple_template() 89 | -------------------------------------------------------------------------------- /modules/utils.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | PLATFORM = sublime.platform() 4 | WIN32 = PLATFORM == 'windows' 5 | 6 | 7 | def log_message(msg): 8 | """Print a message to statusbar and log in console.""" 9 | msg = 'GitGutter: {0}'.format(msg) 10 | print(msg) 11 | sublime.status_message(msg) 12 | 13 | 14 | def line_from_kwargs(view, kwargs): 15 | """Parse kwargs for line or point key and return line number.""" 16 | line = kwargs.get('line') 17 | if line is None: 18 | point = kwargs.get('point') 19 | if point is None: 20 | selection = view.sel() 21 | if not selection: 22 | return 23 | point = selection[0].end() 24 | # get line number from text point 25 | line = view.rowcol(point)[0] 26 | return line 27 | -------------------------------------------------------------------------------- /modules/view.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | 3 | import sublime 4 | 5 | from .temp import TempFile 6 | 7 | 8 | ENCODING_MAP = { 9 | 'UTF-8': 'utf-8', 10 | 'UTF-8 with BOM': 'utf-8-sig', 11 | 'UTF-16 LE': 'utf-16-le', 12 | 'UTF-16 LE with BOM': 'utf-16', 13 | 'UTF-16 BE': 'utf-16-be', 14 | 'UTF-16 BE with BOM': 'utf-16', 15 | 'Western (Windows 1252)': 'cp1252', 16 | 'Western (ISO 8859-1)': 'iso8859-1', 17 | 'Western (ISO 8859-3)': 'iso8859-3', 18 | 'Western (ISO 8859-15)': 'iso8859-15', 19 | 'Western (Mac Roman)': 'mac-roman', 20 | 'DOS (CP 437)': 'cp437', 21 | 'Arabic (Windows 1256)': 'cp1256', 22 | 'Arabic (ISO 8859-6)': 'iso8859-6', 23 | 'Baltic (Windows 1257)': 'cp1257', 24 | 'Baltic (ISO 8859-4)': 'iso8859-4', 25 | 'Celtic (ISO 8859-14)': 'iso8859-14', 26 | 'Central European (Windows 1250)': 'cp1250', 27 | 'Central European (ISO 8859-2)': 'iso8859-2', 28 | 'Cyrillic (Windows 1251)': 'cp1251', 29 | 'Cyrillic (Windows 866)': 'cp866', 30 | 'Cyrillic (ISO 8859-5)': 'iso8859-5', 31 | 'Cyrillic (KOI8-R)': 'koi8-r', 32 | 'Cyrillic (KOI8-U)': 'koi8-u', 33 | 'Estonian (ISO 8859-13)': 'iso8859-13', 34 | 'Greek (Windows 1253)': 'cp1253', 35 | 'Greek (ISO 8859-7)': 'iso8859-7', 36 | 'Hebrew (Windows 1255)': 'cp1255', 37 | 'Hebrew (ISO 8859-8)': 'iso8859-8', 38 | 'Nordic (ISO 8859-10)': 'iso8859-10', 39 | 'Romanian (ISO 8859-16)': 'iso8859-16', 40 | 'Turkish (Windows 1254)': 'cp1254', 41 | 'Turkish (ISO 8859-9)': 'iso8859-9', 42 | 'Vietnamese (Windows 1258)': 'cp1258', 43 | } 44 | 45 | 46 | class GitGutterViewCache(TempFile): 47 | 48 | def __init__(self, view): 49 | """Initialize a GitGutterViewCache object.""" 50 | TempFile.__init__(self, mode='wb') 51 | # tha attached view 52 | self.view = view 53 | # last view change count 54 | self._change_count = -1 55 | # the text size 56 | self._size = None 57 | # the text content 58 | self._text = None 59 | 60 | def __getitem__(self, arg): 61 | if isinstance(arg, sublime.Region): 62 | return self.text[arg.begin():arg.end()] 63 | else: 64 | return self.text[arg] 65 | 66 | @property 67 | def size(self): 68 | """Return the number of characters in the view.""" 69 | if self._size is None: 70 | self._size = self.view.size() 71 | return self._size 72 | 73 | @property 74 | def text(self): 75 | """Return the text content of the view.""" 76 | if self._text is None: 77 | self._text = self.view.substr(sublime.Region(0, self.size)) 78 | return self._text 79 | 80 | def invalidate(self): 81 | """Reset change_count and force writing the view cache file. 82 | 83 | The view content is written to a temporary file for use with git diff, 84 | if the view.change_count() has changed. This method forces the update 85 | on the next call of update_view_file(). 86 | """ 87 | self._change_count = -1 88 | self._size = None 89 | self._text = None 90 | 91 | def is_changed(self): 92 | """Check whether the content of the view changed.""" 93 | return self._change_count != self.view.change_count() 94 | 95 | def update(self): 96 | """Write view's content to a temporary file as source for git diff. 97 | 98 | The file is updated only if the view.change_count() has changed to 99 | reduce the number of required disk writes. 100 | 101 | Returns: 102 | bool: True indicates updated file. 103 | False is returned if file is up to date. 104 | """ 105 | # write view buffer to file only, if changed 106 | change_count = self.view.change_count() 107 | if self._change_count == change_count: 108 | return False 109 | 110 | # invalidate internal cache 111 | self.invalidate() 112 | 113 | # Try conversion 114 | encoding = self.python_friendly_encoding() 115 | try: 116 | encoded = self.text.encode(encoding) 117 | except (LookupError, UnicodeError): 118 | # Fallback to utf8-encoding 119 | encoded = self.text.encode('utf-8') 120 | 121 | # Write the encoded content to file 122 | try: 123 | with self as file: 124 | if encoding == 'utf-8-sig': 125 | file.write(codecs.BOM_UTF8) 126 | file.write(encoded) 127 | except OSError as error: 128 | print('GitGutter failed to create view cache: %s' % error) 129 | return False 130 | 131 | # Update internal change counter after job is done 132 | self._change_count = change_count 133 | return True 134 | 135 | def python_friendly_encoding(self): 136 | """Read view encoding and transform it for use with python. 137 | 138 | This method reads `origin_encoding` used by ConvertToUTF8 plugin and 139 | goes on with ST's encoding setting if required. The encoding is 140 | transformed to work with python's `codecs` module. 141 | 142 | Returns: 143 | string: python compatible view encoding 144 | """ 145 | encoding = self.view.settings().get('origin_encoding') 146 | if not encoding: 147 | encoding = self.view.encoding() 148 | if encoding == 'Undefined': 149 | encoding = self.view.settings().get('default_encoding', '') 150 | return ENCODING_MAP.get(encoding, 'utf-8') 151 | return encoding.replace(' ', '') 152 | -------------------------------------------------------------------------------- /plugin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Load and Unload all GitGutter modules. 3 | 4 | This module exports __all__ modules, which Sublime Text needs to know about. 5 | The list of __all__ exported symbols is defined in modules/__init__.py. 6 | """ 7 | import sublime 8 | 9 | if int(sublime.version()) < 3176: 10 | print('GitGutter requires ST3 3176+') 11 | else: 12 | import sys 13 | 14 | prefix = __package__ + '.' # don't clear the base package 15 | for module_name in [ 16 | module_name for module_name in sys.modules 17 | if module_name.startswith(prefix) and module_name != __name__]: 18 | del sys.modules[module_name] 19 | prefix = None 20 | 21 | from .modules import * 22 | -------------------------------------------------------------------------------- /themes/Bars Thin/Bars Thin.gitgutter-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/Bars Thin.gitgutter-theme -------------------------------------------------------------------------------- /themes/Bars Thin/changed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/changed.png -------------------------------------------------------------------------------- /themes/Bars Thin/changed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/changed@2x.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_bottom.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_bottom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_bottom@2x.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_bottom_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_bottom_arrow.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_bottom_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_bottom_arrow@2x.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_dual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_dual.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_dual@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_dual@2x.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_dual_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_dual_arrow.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_dual_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_dual_arrow@2x.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_top.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_top@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_top@2x.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_top_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_top_arrow.png -------------------------------------------------------------------------------- /themes/Bars Thin/deleted_top_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/deleted_top_arrow@2x.png -------------------------------------------------------------------------------- /themes/Bars Thin/ignored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/ignored.png -------------------------------------------------------------------------------- /themes/Bars Thin/ignored@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/ignored@2x.png -------------------------------------------------------------------------------- /themes/Bars Thin/inserted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/inserted.png -------------------------------------------------------------------------------- /themes/Bars Thin/inserted@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/inserted@2x.png -------------------------------------------------------------------------------- /themes/Bars Thin/untracked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/untracked.png -------------------------------------------------------------------------------- /themes/Bars Thin/untracked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars Thin/untracked@2x.png -------------------------------------------------------------------------------- /themes/Bars/Bars.gitgutter-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/Bars.gitgutter-theme -------------------------------------------------------------------------------- /themes/Bars/changed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/changed.png -------------------------------------------------------------------------------- /themes/Bars/changed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/changed@2x.png -------------------------------------------------------------------------------- /themes/Bars/deleted_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_bottom.png -------------------------------------------------------------------------------- /themes/Bars/deleted_bottom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_bottom@2x.png -------------------------------------------------------------------------------- /themes/Bars/deleted_bottom_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_bottom_arrow.png -------------------------------------------------------------------------------- /themes/Bars/deleted_bottom_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_bottom_arrow@2x.png -------------------------------------------------------------------------------- /themes/Bars/deleted_dual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_dual.png -------------------------------------------------------------------------------- /themes/Bars/deleted_dual@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_dual@2x.png -------------------------------------------------------------------------------- /themes/Bars/deleted_dual_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_dual_arrow.png -------------------------------------------------------------------------------- /themes/Bars/deleted_dual_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_dual_arrow@2x.png -------------------------------------------------------------------------------- /themes/Bars/deleted_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_top.png -------------------------------------------------------------------------------- /themes/Bars/deleted_top@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_top@2x.png -------------------------------------------------------------------------------- /themes/Bars/deleted_top_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_top_arrow.png -------------------------------------------------------------------------------- /themes/Bars/deleted_top_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/deleted_top_arrow@2x.png -------------------------------------------------------------------------------- /themes/Bars/ignored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/ignored.png -------------------------------------------------------------------------------- /themes/Bars/ignored@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/ignored@2x.png -------------------------------------------------------------------------------- /themes/Bars/inserted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/inserted.png -------------------------------------------------------------------------------- /themes/Bars/inserted@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/inserted@2x.png -------------------------------------------------------------------------------- /themes/Bars/untracked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/untracked.png -------------------------------------------------------------------------------- /themes/Bars/untracked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Bars/untracked@2x.png -------------------------------------------------------------------------------- /themes/Default/Default.gitgutter-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/Default.gitgutter-theme -------------------------------------------------------------------------------- /themes/Default/changed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/changed.png -------------------------------------------------------------------------------- /themes/Default/changed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/changed@2x.png -------------------------------------------------------------------------------- /themes/Default/deleted_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_bottom.png -------------------------------------------------------------------------------- /themes/Default/deleted_bottom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_bottom@2x.png -------------------------------------------------------------------------------- /themes/Default/deleted_bottom_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_bottom_arrow.png -------------------------------------------------------------------------------- /themes/Default/deleted_bottom_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_bottom_arrow@2x.png -------------------------------------------------------------------------------- /themes/Default/deleted_dual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_dual.png -------------------------------------------------------------------------------- /themes/Default/deleted_dual@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_dual@2x.png -------------------------------------------------------------------------------- /themes/Default/deleted_dual_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_dual_arrow.png -------------------------------------------------------------------------------- /themes/Default/deleted_dual_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_dual_arrow@2x.png -------------------------------------------------------------------------------- /themes/Default/deleted_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_top.png -------------------------------------------------------------------------------- /themes/Default/deleted_top@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_top@2x.png -------------------------------------------------------------------------------- /themes/Default/deleted_top_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_top_arrow.png -------------------------------------------------------------------------------- /themes/Default/deleted_top_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/deleted_top_arrow@2x.png -------------------------------------------------------------------------------- /themes/Default/ignored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/ignored.png -------------------------------------------------------------------------------- /themes/Default/ignored@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/ignored@2x.png -------------------------------------------------------------------------------- /themes/Default/inserted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/inserted.png -------------------------------------------------------------------------------- /themes/Default/inserted@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/inserted@2x.png -------------------------------------------------------------------------------- /themes/Default/untracked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/untracked.png -------------------------------------------------------------------------------- /themes/Default/untracked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Default/untracked@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/Monokai Pro.gitgutter-theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/Monokai Pro.gitgutter-theme -------------------------------------------------------------------------------- /themes/Monokai Pro/changed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/changed.png -------------------------------------------------------------------------------- /themes/Monokai Pro/changed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/changed@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/changed@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/changed@3x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_bottom.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_bottom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_bottom@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_bottom@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_bottom@3x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_bottom_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_bottom_arrow.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_bottom_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_bottom_arrow@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_bottom_arrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_bottom_arrow@3x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_dual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_dual.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_dual@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_dual@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_dual@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_dual@3x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_dual_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_dual_arrow.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_dual_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_dual_arrow@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_dual_arrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_dual_arrow@3x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_top.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_top@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_top@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_top@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_top@3x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_top_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_top_arrow.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_top_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_top_arrow@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/deleted_top_arrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/deleted_top_arrow@3x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/ignored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/ignored.png -------------------------------------------------------------------------------- /themes/Monokai Pro/ignored@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/ignored@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/ignored@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/ignored@3x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/inserted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/inserted.png -------------------------------------------------------------------------------- /themes/Monokai Pro/inserted@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/inserted@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/inserted@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/inserted@3x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/untracked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/untracked.png -------------------------------------------------------------------------------- /themes/Monokai Pro/untracked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/untracked@2x.png -------------------------------------------------------------------------------- /themes/Monokai Pro/untracked@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jisaacks/GitGutter/2749dea35db751ea304314b2324e25874646be2a/themes/Monokai Pro/untracked@3x.png --------------------------------------------------------------------------------