├── .gitignore
├── LICENSE
├── Main.sublime-menu
├── README.md
├── easy_diff.py
├── easy_diff.sublime-settings
├── easy_diff_basic.py
├── easy_diff_dynamic_menu.py
├── easy_diff_global.py
├── easy_diff_version_control.py
└── lib
├── __init__.py
├── git.py
├── hg.py
├── multiconf.py
└── svn.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 |
3 | # C extensions
4 | *.so
5 |
6 | # Packages
7 | *.egg
8 | *.egg-info
9 | dist
10 | build
11 | eggs
12 | parts
13 | bin
14 | var
15 | sdist
16 | develop-eggs
17 | .installed.cfg
18 | __pycache__
19 |
20 | # Installer logs
21 | pip-log.txt
22 |
23 | # Unit test / coverage reports
24 | .coverage
25 | .tox
26 | nosetests.xml
27 |
28 | # Translations
29 | *.mo
30 |
31 | # Mr Developer
32 | .mr.developer.cfg
33 | .project
34 | .pydevproject
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Isaac Muse
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Main.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "Preferences",
4 | "mnemonic": "n",
5 | "id": "preferences",
6 | "children":
7 | [
8 | {
9 | "caption": "Package Settings",
10 | "mnemonic": "P",
11 | "id": "package-settings",
12 | "children":
13 | [
14 | {
15 | "caption": "EasyDiff",
16 | "children":
17 | [
18 | {
19 | "command": "open_file", "args":
20 | {
21 | "file": "${packages}/EasyDiff/easy_diff.sublime-settings"
22 | },
23 | "caption": "Settings – Default"
24 | },
25 | {
26 | "command": "open_file", "args":
27 | {
28 | "file": "${packages}/User/easy_diff.sublime-settings"
29 | },
30 | "caption": "Settings – User"
31 | },
32 | { "caption": "-" }
33 | ]
34 | }
35 | ]
36 | }
37 | ]
38 | }
39 | ]
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EasyDiff
2 |
3 | Diff plugin for Sublime Text
4 |
5 |
6 |
7 | - Allows comparing views, selections, multi-selections, and clipboard combinations.
8 | - Can compare working copy against the base or previous revsion of a file in SVN, Git, and Mercurial (requires some setup and configuration).
9 | - Dynamic context menus for selecting left side and right side compare. Dynamic menus show what file is on *left* side (think Beyond Compare context menus on windows).
10 | - View diffs in a view buffer or output panel
11 | - You can selectively hide version control menus or disable the command completely via the settings file.
12 | - Can open diffs in external diff programs if desired (requires some setup and configuration)
13 | - Show only interanl diff options, only external options, or show both depending on your needs and preferences.
14 |
15 | # Usage
16 | Easy diff is easy to use. Simply select the `Set Left Side` option in the context menus to set what is to be compared on the left side when in a view. Then select the what to compare to via `Compare with` menu option (select the right side view first if comparing to a view).
17 |
18 | For version control, just select the applicable option when in a view that is versioned controlled.
19 |
20 | # General Settings
21 | Easy diff, by default, shows diffs in a spearate view, you can display the diff in an output panel if desired using the following setting:
22 |
23 | ```javascript
24 | // If enabled, this allows for multiple selections.
25 | // Each selection is separated with a new line.
26 | "multi_select": false
27 | ```
28 |
29 | Easy allows for diffing with the clipboard selections, and multi-selections. These all can be turned off or on as desired:
30 |
31 | ```javascript
32 | // Use a buffer instead of the output panel
33 | "use_buffer": true,
34 |
35 | // Enable clipboard commands
36 | "use_clipboard": true,
37 |
38 | // Enable selection commands
39 | "use_selections": false,
40 | ```
41 |
42 | # Dynamic Menu
43 | Easy diff creates a dynamic menu in `User/EasyDiff/Context.sublime-menu`, `User/EasyDiff/Tab Context.sublime-menu`, and `User/EasyDiff/Side Bar.sublime-menu`. The content of this context menu changes depending on what is enabled or disabled, hidden or shown, and depending on whether a view, selection, or clipboard has been selected for left side compare. If a view that was previously set has been closed, that view will no longer be reported in the context menu. You can look here to see how the commands are constructed if you would like to bind the options to shortcuts or to the command palette.
44 |
45 | ## Excluding Dynamic Menu from Certain UI Elements
46 | Easy diff shows access commands in the view, tab, and sidebar context menu. If it is desired to exclude access in one of these UI elements, you can remove the element from the following setting:
47 |
48 | ```javascript
49 | // Menus to show (view|tab|sidebar)
50 | "menu_types": ["view", "tab", "sidebar"],
51 | ```
52 |
53 | # Version Control Setup
54 | EasyDiff currently supports SVN, Git, and Mercurial. These options should only appear in the context menus if EasyDiff can find the binaries `svn`, `git`, and `hg` respectively.
55 |
56 | If one of these options shows up, and you wish to disable them, you can go to your settings file and disable them completely with the following settings:
57 |
58 | ```javascript
59 | // Turn off svn completely
60 | "svn_disabled": false,
61 |
62 | // Turn off git completely
63 | "git_disabled": false,
64 |
65 | // Turn off (Mercurial) hg completely
66 | "hg_disabled": false,
67 | ```
68 |
69 | If you would simply like to hide the options (because you have bound their operations to a shortcut or to the command palette), you can hide the options without disabling them:
70 |
71 | ```javascript
72 | // Turn off svn menu access
73 | "svn_hide_menu": false,
74 |
75 | // Turn off git menu access
76 | "git_hide_menu": false,
77 |
78 | // Turn off (Mercurial) hg menu access
79 | "hg_hide_menu": false,
80 | ```
81 |
82 | If your binaries are not in your system's path, you will need to configure the following settings with the path to your binaries:
83 |
84 | ```javascript
85 | // SVN path
86 | "svn": "",
87 |
88 | // Git Path
89 | "git": "",
90 |
91 | // (Mercurial) Hg path
92 | "hg": "",
93 | ```
94 |
95 | Currently, by default, EasyDiff will check if the current view is versioned controlled by one of your enabled version control options when displaying the context menu. This allows the for non-pertinent options to be grayed out. With some version control systems, this can occasionally cause a lag when displaying those options. You can turn off this functionality if it becomes a problem with the following settings:
96 |
97 | ```javascript
98 | // Do not perform a version check on files
99 | // when evaluating whether version control
100 | // commands are enabled. May avoid slowing
101 | // down context menu draw in large version
102 | // controlled projects.
103 | "skip_version_check_on_is_enabled": false,
104 | ```
105 |
106 | # Diffing with External Diff Tools
107 | Easy diff, by default, has options to diff everything internally in a single view. But, it can be configured to diff in external tools instead.
108 |
109 | Configure the external binary setting to point to diff tool binary, and then enable external diff options:
110 |
111 | ```javascript
112 | // Show external options (options to send files to external diff tool)
113 | "show_external": false,
114 |
115 | // External diff tool path (absolute)
116 | "external_diff": "",
117 | ```
118 |
119 | The external option assumes the diff the tool takes arguments as such: `tool file1 file2`. If this is not the case, you will probably have to wrap the command in a shell script that takes the options as described, and call it directly instead. For instance, using DeltaWalker on Mac, I copied their provided workflow configuration to a shell script, and call it directly:
120 |
121 | ```bash
122 | #!/bin/sh
123 | DW_PATH=/Applications/DeltaWalker.app/Contents/MacOS
124 |
125 | java -Ddw.path=$DW_PATH -jar $DW_PATH/dw.jar "$1" "$2" "$3" "$4" "$5" "$6"
126 |
127 | ```
128 |
129 | # Hiding External or Internal Diffing from Context Menu
130 | EasyDiff allows you to hide either internal diffing options, external diffing options, or both. The later options is useful if you do not use the context menu, but have bound the commands to keyboard shortcuts or to the command palette.
131 |
132 | The settings are:
133 | ```javascript
134 | // Show internal diff options (Easy Diff in a view or buffer)
135 | "show_internal": true,
136 |
137 | // Show external options (options to send files to external diff tool)
138 | "show_external": false,
139 | ```
140 |
141 | # License
142 |
143 | EasyDiff is released under the MIT license.
144 |
145 | Copyright (c) 2013 Isaac Muse
146 |
147 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
148 |
149 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
150 |
151 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
152 |
--------------------------------------------------------------------------------
/easy_diff.py:
--------------------------------------------------------------------------------
1 | """
2 | Easy Diff
3 |
4 | Copyright (c) 2013 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | import time
9 | import difflib
10 | from os.path import basename, join, splitext
11 | from os import stat as osstat
12 | import tempfile
13 | from EasyDiff.easy_diff_global import load_settings, get_encoding, notify
14 | import subprocess
15 |
16 | LEFT = 1
17 | RIGHT = 2
18 |
19 |
20 | class EasyDiffView(object):
21 | def __init__(self, name, content, encoding):
22 | self.filename = name
23 | self.content = content
24 | self.time = time.ctime()
25 | self.encode = encoding
26 |
27 | def encoding(self):
28 | return self.encode
29 |
30 | def get_time(self):
31 | return self.time
32 |
33 | def file_name(self):
34 | return self.filename
35 |
36 | def substr(self, region):
37 | return self.content[region.begin():region.end() + 1]
38 |
39 | def size(self):
40 | return len(self.content)
41 |
42 |
43 | class EasyDiffInput(object):
44 | def __init__(self, v1, v2, external=False):
45 | self.untitled = False
46 | self.temp_folder = None
47 | self.process_view(v1, LEFT, external)
48 | self.process_view(v2, RIGHT, external)
49 |
50 | def process_view(self, view, side, external):
51 | self.side = side
52 | name = view.file_name()
53 | if name is None:
54 | self.set_view_buffer(view, self.untitled, external)
55 | elif isinstance(view, EasyDiffView):
56 | self.set_special(view, external)
57 | else:
58 | self.set_view(view)
59 |
60 | self.set_buffer(view, external)
61 |
62 | def set_buffer(self, view, external):
63 | setattr(
64 | self,
65 | "b%d" % self.side,
66 | view.substr(sublime.Region(0, view.size())).splitlines() if not external else []
67 | )
68 |
69 | def set_view(self, view):
70 | setattr(self, "f%d" % self.side, view.file_name())
71 | setattr(self, "t%d" % self.side, time.ctime(osstat(view.file_name()).st_mtime))
72 |
73 | def set_special(self, view, external):
74 | setattr(self, "f%d" % self.side, view.file_name())
75 | if external:
76 | setattr(self, "f%d" % self.side, self.create_temp(view, view.file_name().replace("*", "")))
77 | setattr(self, "t%d" % self.side, view.get_time())
78 |
79 | def set_view_buffer(self, view, untitled, external):
80 | setattr(
81 | self,
82 | "f%d" % self.side,
83 | self.create_temp(view, "Untitled2" if self.untitled else "Untitled") if external else "Untitled2" if self.untitled else "Untitled"
84 | )
85 | setattr(self, "t%d" % self.side, time.ctime())
86 | self.untitled = True
87 |
88 | def create_temp(self, v, name):
89 | file_name = None
90 | if self.temp_folder is None:
91 | self.temp_folder = tempfile.mkdtemp(prefix="easydiff")
92 | file_name = self.create_file(v, name)
93 | else:
94 | file_name = self.create_file(v, name)
95 | return file_name
96 |
97 | def create_file(self, v, name):
98 | root, ext = splitext(name)
99 | with open(join(self.temp_folder, "%s-%s%s" % (root, "LEFT" if self.side == LEFT else "RIGHT", ext)), "wb") as f:
100 | encoding = get_encoding(v)
101 | try:
102 | bfr = v.substr(sublime.Region(0, v.size())).encode(encoding)
103 | except:
104 | bfr = v.substr(sublime.Region(0, v.size())).encode("utf-8")
105 | f.write(bfr)
106 | return f.name
107 |
108 |
109 | class EasyDiff(object):
110 | @classmethod
111 | def extcompare(cls, inputs, ext_diff):
112 | subprocess.Popen(
113 | [
114 | ext_diff,
115 | inputs.f1,
116 | inputs.f2
117 | ]
118 | )
119 |
120 | @classmethod
121 | def compare(cls, inputs):
122 | diff = difflib.unified_diff(
123 | inputs.b1, inputs.b2,
124 | inputs.f1, inputs.f2,
125 | inputs.t1, inputs.t2,
126 | lineterm=''
127 | )
128 | result = u"\n".join(line for line in diff)
129 |
130 | if result == "":
131 | notify("No Difference")
132 | return
133 |
134 | use_buffer = bool(load_settings().get("use_buffer", False))
135 |
136 | win = sublime.active_window()
137 | if use_buffer:
138 | v = win.new_file()
139 | v.set_name("EasyDiff: %s -> %s (%s)" % (basename(inputs.f1), basename(inputs.f2), time.ctime()))
140 | v.set_scratch(True)
141 | v.assign_syntax('Packages/Diff/Diff.tmLanguage')
142 | v.run_command('append', {'characters': result})
143 | else:
144 | v = win.create_output_panel('easy_diff')
145 | v.assign_syntax('Packages/Diff/Diff.tmLanguage')
146 | v.run_command('append', {'characters': result})
147 | win.run_command("show_panel", {"panel": "output.easy_diff"})
148 |
--------------------------------------------------------------------------------
/easy_diff.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | // Show internal diff options (Easy Diff in a view or buffer)
3 | "show_internal": true,
4 |
5 | // Show external options (options to send files to external diff tool)
6 | "show_external": false,
7 |
8 | // External diff tool path (absolute)
9 | "external_diff": "",
10 |
11 | // SVN path
12 | "svn": "",
13 |
14 | // Git Path
15 | "git": "",
16 |
17 | // (Mercurial) Hg path
18 | "hg": "",
19 |
20 | // Turn off svn completely
21 | "svn_disabled": false,
22 |
23 | // Turn off git completely
24 | "git_disabled": false,
25 |
26 | // Turn off (Mercurial) hg completely
27 | "hg_disabled": false,
28 |
29 | // Turn off svn menu access
30 | "svn_hide_menu": false,
31 |
32 | // Turn off git menu access
33 | "git_hide_menu": false,
34 |
35 | // Turn off (Mercurial) hg menu access
36 | "hg_hide_menu": false,
37 |
38 | // Menus to show (view|tab|sidebar)
39 | "menu_types": ["view", "tab", "sidebar"],
40 |
41 | // Do not perform a version check on files
42 | // when evaluating whether version control
43 | // commands are enabled. May avoid slowing
44 | // down context menu draw in large version
45 | // controlled projects.
46 | "skip_version_check_on_is_enabled": false,
47 |
48 | // Use a buffer instead of the output panel
49 | "use_buffer": true,
50 |
51 | // Enable clipboard commands
52 | "use_clipboard": true,
53 |
54 | // Enable selection commands
55 | "use_selections": true,
56 |
57 | // If enabled, this allows for multiple selections.
58 | // Each selection is separated with a new line.
59 | "multi_select": false,
60 |
61 | "use_sub_notify": true
62 | }
63 |
--------------------------------------------------------------------------------
/easy_diff_basic.py:
--------------------------------------------------------------------------------
1 | """
2 | Easy Diff Basic Commands
3 |
4 | Copyright (c) 2013 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | import sublime_plugin
9 | from os.path import basename
10 | from EasyDiff.easy_diff_global import load_settings, log, get_external_diff, get_target
11 | from EasyDiff.easy_diff_dynamic_menu import update_menu
12 | from EasyDiff.easy_diff import EasyDiffView, EasyDiffInput, EasyDiff
13 |
14 | LEFT = None
15 |
16 |
17 | ###############################
18 | # Helper Functions
19 | ###############################
20 | def diff(right, external=False):
21 | lw = None
22 | rw = None
23 | lv = None
24 | rv = None
25 |
26 | for w in sublime.windows():
27 | if w.id() == LEFT["win_id"]:
28 | lw = w
29 | if w.id() == right["win_id"]:
30 | rw = w
31 | if lw is not None and rw is not None:
32 | break
33 |
34 | if lw is not None:
35 | for v in lw.views():
36 | if v.id() == LEFT["view_id"]:
37 | lv = v
38 | break
39 | else:
40 | if LEFT["clip"]:
41 | lv = LEFT["clip"]
42 |
43 | if rw is not None:
44 | for v in rw.views():
45 | if v.id() == right["view_id"]:
46 | rv = v
47 | break
48 | else:
49 | if right["clip"]:
50 | rv = right["clip"]
51 |
52 | if lv is not None and rv is not None:
53 | ext_diff = get_external_diff()
54 | if external:
55 | EasyDiff.extcompare(EasyDiffInput(lv, rv, external=True), ext_diff)
56 | else:
57 | EasyDiff.compare(EasyDiffInput(lv, rv))
58 | else:
59 | log("Can't compare")
60 |
61 |
62 | ###############################
63 | # Helper Classes
64 | ###############################
65 | class _EasyDiffSelection(object):
66 | def get_selections(self):
67 | bfr = ""
68 | length = len(self.view.sel())
69 | for s in self.view.sel():
70 | if s.size() == 0:
71 | continue
72 | bfr += self.view.substr(s)
73 | if length > 1:
74 | bfr += "\n"
75 | length -= 1
76 | return bfr
77 |
78 | def get_encoding(self):
79 | return self.view.encoding()
80 |
81 | def has_selections(self):
82 | selections = False
83 | if bool(load_settings().get("multi_select", False)):
84 | for s in self.view.sel():
85 | if s.size() > 0:
86 | selections = True
87 | break
88 | else:
89 | selections = len(self.view.sel()) == 1 and self.view.sel()[0].size() > 0
90 | return selections
91 |
92 |
93 | class _EasyDiffCompareBothTextCommand(sublime_plugin.TextCommand):
94 | def run(self, edit, external=False, group=-1, index=-1):
95 | if index != -1:
96 | # Ensure we have the correct view
97 | self.view = sublime.active_window().views_in_group(group)[index]
98 | diff(self.get_right(), external=external)
99 |
100 | def view_has_selections(self, group=-1, index=-1):
101 | has_selections = False
102 | if index != -1:
103 | view = sublime.active_window().views_in_group(group)[index]
104 | if bool(load_settings().get("multi_select", False)):
105 | for sel in view.sel():
106 | if sel.size() > 0:
107 | has_selections = True
108 | break
109 | else:
110 | has_selections = len(view.sel()) == 1 and view.sel()[0].size() > 0
111 | else:
112 | has_selections = self.has_selections()
113 | return has_selections
114 |
115 | def get_right(self):
116 | return None
117 |
118 | def check_enabled(self, group=-1, index=-1):
119 | return True
120 |
121 | def is_enabled(self, external=False, group=-1, index=-1):
122 | return LEFT is not None and self.check_enabled(group, index)
123 |
124 |
125 | class _EasyDiffCompareBothWindowCommand(sublime_plugin.WindowCommand):
126 | no_view = False
127 |
128 | def run(self, external=False, paths=[], group=-1, index=-1):
129 | self.external = external
130 | self.set_view(paths, group, index)
131 | if not self.no_view and self.view is None:
132 | return
133 | if not self.no_view:
134 | sublime.set_timeout(self.is_loaded, 100)
135 | else:
136 | self.diff()
137 |
138 | def is_loaded(self):
139 | if self.view.is_loading():
140 | sublime.set_timeout(self.is_loaded, 100)
141 | else:
142 | self.diff()
143 |
144 | def diff(self):
145 | diff(self.get_right(), external=self.external)
146 |
147 | def set_view(self, paths, group=-1, index=-1, open_file=True):
148 | if len(paths):
149 | file_path = get_target(paths)
150 | if file_path is None:
151 | return
152 | if open_file:
153 | self.view = self.window.open_file(file_path)
154 | elif index != -1:
155 | self.view = self.window.views_in_group(group)[index]
156 | else:
157 | self.view = self.window.active_view()
158 |
159 | def get_right(self):
160 | return None
161 |
162 | def check_enabled(self, paths=[], group=-1, index=-1):
163 | return True
164 |
165 | def is_enabled(self, external=False, paths=[], group=-1, index=-1):
166 | return LEFT is not None and self.check_enabled(paths)
167 |
168 |
169 | ###############################
170 | # Set View
171 | ###############################
172 | class EasyDiffSetLeftCommand(sublime_plugin.WindowCommand):
173 | def run(self, paths=[], group=-1, index=-1):
174 | global LEFT
175 | self.set_view(paths, group, index)
176 | if self.view is None:
177 | return
178 | LEFT = {"win_id": self.view.window().id(), "view_id": self.view.id(), "clip": None}
179 | name = self.view.file_name()
180 | if name is None:
181 | name = "Untitled"
182 | update_menu(basename(name))
183 |
184 | def set_view(self, paths, group=-1, index=-1, open_file=True):
185 | if len(paths):
186 | file_path = get_target(paths)
187 | if file_path is None:
188 | return
189 | if open_file:
190 | self.view = self.window.open_file(file_path)
191 | elif index != -1:
192 | self.view = self.window.views_in_group(group)[index]
193 | else:
194 | self.view = self.window.active_view()
195 |
196 | def is_enabled(self, paths=[], group=-1, index=-1):
197 | return get_target(paths, group, index) is not None if len(paths) or index != -1 else True
198 |
199 |
200 | class EasyDiffCompareBothViewCommand(_EasyDiffCompareBothWindowCommand):
201 | def get_right(self):
202 | return {"win_id": self.view.window().id(), "view_id": self.view.id(), "clip": None}
203 |
204 | def check_enabled(self, paths=[], group=-1, index=-1):
205 | return True
206 |
207 | def is_enabled(self, external=False, paths=[], group=-1, index=-1):
208 | return LEFT is not None and (get_target(paths, group, index) is not None if len(paths) or index != -1 else True) and self.check_enabled()
209 |
210 |
211 | ###############################
212 | # Set Clipboard
213 | ###############################
214 | class EasyDiffSetLeftClipboardCommand(sublime_plugin.WindowCommand):
215 | def run(self, paths=[], group=-1, index=-1):
216 | global LEFT
217 | LEFT = {"win_id": None, "view_id": None, "clip": EasyDiffView("**clipboard**", sublime.get_clipboard(), "UTF-8")}
218 | update_menu("**clipboard**")
219 |
220 | def is_enabled(self, paths=[], group=-1, index=-1):
221 | valid_path = get_target(paths, group, index) is not None if len(paths) or index != -1 else True
222 | return bool(load_settings().get("use_clipboard", True)) and valid_path
223 |
224 | def is_visible(self, paths=[], group=-1, index=-1):
225 | return bool(load_settings().get("use_clipboard", True))
226 |
227 |
228 | class EasyDiffCompareBothClipboardCommand(_EasyDiffCompareBothWindowCommand):
229 | no_view = True
230 |
231 | def get_right(self):
232 | return {"win_id": None, "view_id": None, "clip": EasyDiffView("**clipboard**", sublime.get_clipboard(), "UTF-8")}
233 |
234 | def check_enabled(self, paths=[], group=-1, index=-1):
235 | valid_path = get_target(paths, group, index) is not None if len(paths) or index != -1 else True
236 | return bool(load_settings().get("use_clipboard", True)) and valid_path
237 |
238 | def is_visible(self, external=False, paths=[], group=-1, index=-1):
239 | return bool(load_settings().get("use_clipboard", True))
240 |
241 |
242 | ###############################
243 | # Set Selection
244 | ###############################
245 | class EasyDiffSetLeftSelectionCommand(sublime_plugin.TextCommand, _EasyDiffSelection):
246 | def run(self, edit, group=-1, index=-1):
247 | global LEFT
248 | if index != -1:
249 | # Ensure we have the correct view
250 | self.view = sublime.active_window().views_in_group(group)[index]
251 | LEFT = {"win_id": None, "view_id": None, "clip": EasyDiffView("**selection**", self.get_selections(), self.get_encoding())}
252 | update_menu("**selection**")
253 |
254 | def view_has_selections(self, group=-1, index=-1):
255 | has_selections = False
256 | if index != -1:
257 | view = sublime.active_window().views_in_group(group)[index]
258 | if bool(load_settings().get("multi_select", False)):
259 | for sel in view.sel():
260 | if sel.size() > 0:
261 | has_selections = True
262 | break
263 | else:
264 | has_selections = len(view.sel()) == 1 and view.sel()[0].size() > 0
265 | else:
266 | has_selections = self.has_selections()
267 | return has_selections
268 |
269 | def is_enabled(self, group=-1, index=-1):
270 | return bool(load_settings().get("use_selections", True)) and self.view_has_selections(group, index)
271 |
272 | def is_visible(self, group=-1, index=-1):
273 | return bool(load_settings().get("use_selections", True))
274 |
275 |
276 | class EasyDiffCompareBothSelectionCommand(_EasyDiffCompareBothTextCommand, _EasyDiffSelection):
277 | def get_right(self):
278 | return {"win_id": None, "view_id": None, "clip": EasyDiffView("**selection**", self.get_selections(), self.get_encoding())}
279 |
280 | def check_enabled(self, group=-1, index=-1):
281 | return bool(load_settings().get("use_selections", True)) and self.view_has_selections(group, index)
282 |
283 | def is_visible(self, external=False, group=-1, index=-1):
284 | return bool(load_settings().get("use_selections", True))
285 |
286 |
287 | ###############################
288 | # View Close Listener
289 | ###############################
290 | class EasyDiffListener(sublime_plugin.EventListener):
291 | def on_close(self, view):
292 | global LEFT
293 | vid = view.id()
294 | if LEFT is not None and vid == LEFT["view_id"]:
295 | LEFT = None
296 | update_menu()
297 |
298 |
299 | ###############################
300 | # Loaders
301 | ###############################
302 | def basic_reload():
303 | global LEFT
304 | LEFT = None
305 | update_menu()
306 | settings = load_settings()
307 | settings.clear_on_change('reload_basic')
308 | settings.add_on_change('reload_basic', basic_reload)
309 |
310 |
311 | def plugin_loaded():
312 | basic_reload()
313 |
--------------------------------------------------------------------------------
/easy_diff_dynamic_menu.py:
--------------------------------------------------------------------------------
1 | """
2 | Easy Diff Dynamic Menu
3 |
4 | Copyright (c) 2013 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | from os.path import join, exists
9 | from os import makedirs, remove
10 | from EasyDiff.easy_diff_global import load_settings, debug, get_external_diff
11 | from EasyDiff.lib.multiconf import get as multiget
12 |
13 | MENU_FOLDER = "EasyDiff"
14 | CONTEXT_MENU = "Context.sublime-menu"
15 | SIDEBAR_MENU = "Side Bar.sublime-menu"
16 | TAB_MENU = "Tab Context.sublime-menu"
17 |
18 |
19 | ###############################
20 | # General Menus
21 | ###############################
22 | DIFF_MENU = '''[
23 | %(internal)s
24 | %(vc_internal)s
25 | %(external)s
26 | %(vc_external)s
27 | { "caption": "-"}
28 | ]
29 | '''
30 |
31 | VC_INTERNAL_MENU = '''{
32 | "caption": "EasyDiff Version Control",
33 | "children":
34 | [
35 | %(vc)s
36 | ]
37 | },
38 | '''
39 |
40 | VC_EXTERNAL_MENU = '''{
41 | "caption": "Diff Version Control",
42 | "children":
43 | [
44 | %(vc)s
45 | ]
46 | },
47 | '''
48 |
49 | ###############################
50 | # View Menus
51 | ###############################
52 | INTERNAL_MENU = '''{ "caption": "-" },
53 | {
54 | "caption": "EasyDiff Set Left Side",
55 | "children":
56 | [
57 | {
58 | "caption": "View",
59 | "command": "easy_diff_set_left"
60 | },
61 | {
62 | "caption": "Clipboard",
63 | "command": "easy_diff_set_left_clipboard"
64 | },
65 | {
66 | "caption": "Selection",
67 | "command": "easy_diff_set_left_selection"
68 | }
69 | ]
70 | },
71 | {
72 | "caption": "EasyDiff Compare with \\"%(file_name)s\\"",
73 | "children":
74 | [
75 | {
76 | "caption": "View",
77 | "command": "easy_diff_compare_both_view"
78 | },
79 | {
80 | "caption": "Clipboard",
81 | "command": "easy_diff_compare_both_clipboard"
82 | },
83 | {
84 | "caption": "Selection",
85 | "command": "easy_diff_compare_both_selection"
86 | }
87 | ]
88 | },
89 | '''
90 |
91 | EXTERNAL_MENU = '''{ "caption": "-" },
92 | {
93 | "caption": "Diff Set Left Side",
94 | "children":
95 | [
96 | {
97 | "caption": "View",
98 | "command": "easy_diff_set_left"
99 | },
100 | {
101 | "caption": "Clipboard",
102 | "command": "easy_diff_set_left_clipboard"
103 | },
104 | {
105 | "caption": "Selection",
106 | "command": "easy_diff_set_left_selection"
107 | }
108 | ]
109 | },
110 | {
111 | "caption": "Diff Compare with \\"%(file_name)s\\"",
112 | "children":
113 | [
114 | {
115 | "caption": "View",
116 | "command": "easy_diff_compare_both_view",
117 | "args": {"external": true}
118 | },
119 | {
120 | "caption": "Clipboard",
121 | "command": "easy_diff_compare_both_clipboard",
122 | "args": {"external": true}
123 | },
124 | {
125 | "caption": "Selection",
126 | "command": "easy_diff_compare_both_selection",
127 | "args": {"external": true}
128 | }
129 | ]
130 | },
131 | '''
132 |
133 | SVN_INTERNAL_MENU = '''
134 | {
135 | "caption": "SVN Diff",
136 | "command": "easy_diff_svn"
137 | },
138 | {
139 | "caption": "SVN Diff with Previous Revision",
140 | "command": "easy_diff_svn",
141 | "args": {"last": true}
142 | },
143 | {
144 | "caption": "SVN Revert",
145 | "command": "easy_diff_svn",
146 | "args": {"revert": true}
147 | },
148 | { "caption": "-"}'''
149 |
150 | GIT_INTERNAL_MENU = '''
151 | {
152 | "caption": "Git Diff",
153 | "command": "easy_diff_git"
154 | },
155 | {
156 | "caption": "Git Diff with Previous Revision",
157 | "command": "easy_diff_git",
158 | "args": {"last": true}
159 | },
160 | {
161 | "caption": "Git Revert",
162 | "command": "easy_diff_git",
163 | "args": {"revert": true}
164 | },
165 | { "caption": "-"}'''
166 |
167 | HG_INTERNAL_MENU = '''
168 | {
169 | "caption": "Mercurial Diff",
170 | "command": "easy_diff_hg"
171 | },
172 | {
173 | "caption": "Mercurial Diff with Previous Revision",
174 | "command": "easy_diff_hg",
175 | "args": {"last": true}
176 | },
177 | {
178 | "caption": "Mercurial Revert",
179 | "command": "easy_diff_hg",
180 | "args": {"revert": true}
181 | },
182 | { "caption": "-"}'''
183 |
184 | SVN_EXTERNAL_MENU = '''
185 | {
186 | "caption": "SVN Diff",
187 | "command": "easy_diff_svn",
188 | "args": {"external": true}
189 | },
190 | {
191 | "caption": "SVN Diff with Previous Revision",
192 | "command": "easy_diff_svn",
193 | "args": {"external": true, "last": true}
194 | },
195 | {
196 | "caption": "SVN Revert",
197 | "command": "easy_diff_svn",
198 | "args": {"revert": true}
199 | },
200 | { "caption": "-"}'''
201 |
202 | GIT_EXTERNAL_MENU = '''
203 | {
204 | "caption": "Git Diff",
205 | "command": "easy_diff_git",
206 | "args": {"external": true}
207 | },
208 | {
209 | "caption": "Git Diff with Previous Revision",
210 | "command": "easy_diff_git",
211 | "args": {"external": true, "last": true}
212 | },
213 | {
214 | "caption": "Git Revert",
215 | "command": "easy_diff_git",
216 | "args": {"revert": true}
217 | },
218 | { "caption": "-"}'''
219 |
220 | HG_EXTERNAL_MENU = '''
221 | {
222 | "caption": "Mercurial Diff",
223 | "command": "easy_diff_hg",
224 | "args": {"external": true}
225 | },
226 | {
227 | "caption": "Mercurial Diff with Previous Revision",
228 | "command": "easy_diff_hg",
229 | "args": {"external": true, "last": true}
230 | },
231 | {
232 | "caption": "Mercurial Revert",
233 | "command": "easy_diff_hg",
234 | "args": {"revert": true}
235 | },
236 | { "caption": "-"}'''
237 |
238 |
239 | ###############################
240 | # Sidebar Menus
241 | ###############################
242 | INTERNAL_SIDEBAR_MENU = '''{ "caption": "-" },
243 | {
244 | "caption": "EasyDiff Set Left Side",
245 | "children":
246 | [
247 | {
248 | "caption": "View",
249 | "command": "easy_diff_set_left",
250 | "args": {"paths": []}
251 | },
252 | {
253 | "caption": "Clipboard",
254 | "command": "easy_diff_set_left_clipboard",
255 | "args": {"paths": []}
256 | }
257 | ]
258 | },
259 | {
260 | "caption": "EasyDiff Compare with \\"%(file_name)s\\"",
261 | "children":
262 | [
263 | {
264 | "caption": "View",
265 | "command": "easy_diff_compare_both_view",
266 | "args": {"paths": []}
267 | },
268 | {
269 | "caption": "Clipboard",
270 | "command": "easy_diff_compare_both_clipboard",
271 | "args": {"paths": []}
272 | }
273 | ]
274 | },
275 | '''
276 |
277 | EXTERNAL_SIDEBAR_MENU = '''{ "caption": "-" },
278 | {
279 | "caption": "Diff Set Left Side",
280 | "children":
281 | [
282 | {
283 | "caption": "View",
284 | "command": "easy_diff_set_left",
285 | "args": {"paths": []}
286 | },
287 | {
288 | "caption": "Clipboard",
289 | "command": "easy_diff_set_left_clipboard",
290 | "args": {"paths": []}
291 | }
292 | ]
293 | },
294 | {
295 | "caption": "Diff Compare with \\"%(file_name)s\\"",
296 | "children":
297 | [
298 | {
299 | "caption": "View",
300 | "command": "easy_diff_compare_both_view",
301 | "args": {"external": true, "paths": []}
302 | },
303 | {
304 | "caption": "Clipboard",
305 | "command": "easy_diff_compare_both_clipboard",
306 | "args": {"external": true, "paths": []}
307 | }
308 | ]
309 | },
310 | '''
311 |
312 | SVN_SIDEBAR_INTERNAL_MENU = '''
313 | {
314 | "caption": "SVN Diff",
315 | "command": "easy_diff_svn",
316 | "args": {"paths": []}
317 | },
318 | {
319 | "caption": "SVN Diff with Previous Revision",
320 | "command": "easy_diff_svn",
321 | "args": {"last": true, "paths": []}
322 | },
323 | {
324 | "caption": "SVN Revert",
325 | "command": "easy_diff_svn",
326 | "args": {"revert": true, "paths": []}
327 | },
328 | { "caption": "-"}'''
329 |
330 | GIT_SIDEBAR_INTERNAL_MENU = '''
331 | {
332 | "caption": "Git Diff",
333 | "command": "easy_diff_git",
334 | "args": {"paths": []}
335 | },
336 | {
337 | "caption": "Git Diff with Previous Revision",
338 | "command": "easy_diff_git",
339 | "args": {"last": true, "paths": []}
340 | },
341 | {
342 | "caption": "Git Revert",
343 | "command": "easy_diff_git",
344 | "args": {"revert": true, "paths": []}
345 | },
346 | { "caption": "-"}'''
347 |
348 | HG_SIDEBAR_INTERNAL_MENU = '''
349 | {
350 | "caption": "Mercurial Diff",
351 | "command": "easy_diff_hg",
352 | "args": {"paths": []}
353 | },
354 | {
355 | "caption": "Mercurial Diff with Previous Revision",
356 | "command": "easy_diff_hg",
357 | "args": {"last": true, "paths": []}
358 | },
359 | {
360 | "caption": "Mercurial Revert",
361 | "command": "easy_diff_hg",
362 | "args": {"revert": true, "paths": []}
363 | },
364 | { "caption": "-"}'''
365 |
366 | SVN_SIDEBAR_EXTERNAL_MENU = '''
367 | {
368 | "caption": "SVN Diff",
369 | "command": "easy_diff_svn",
370 | "args": {"external": true, "paths": []}
371 | },
372 | {
373 | "caption": "SVN Diff with Previous Revision",
374 | "command": "easy_diff_svn",
375 | "args": {"external": true, "last": true, "paths": []}
376 | },
377 | {
378 | "caption": "SVN Revert",
379 | "command": "easy_diff_svn",
380 | "args": {"revert": true, "paths": []}
381 | },
382 | { "caption": "-"}'''
383 |
384 | GIT_SIDEBAR_EXTERNAL_MENU = '''
385 | {
386 | "caption": "Git Diff",
387 | "command": "easy_diff_git",
388 | "args": {"external": true, "paths": []}
389 | },
390 | {
391 | "caption": "Git Diff with Previous Revision",
392 | "command": "easy_diff_git",
393 | "args": {"external": true, "last": true, "paths": []}
394 | },
395 | {
396 | "caption": "Git Revert",
397 | "command": "easy_diff_git",
398 | "args": {"revert": true, "paths": []}
399 | },
400 | { "caption": "-"}'''
401 |
402 | HG_SIDEBAR_EXTERNAL_MENU = '''
403 | {
404 | "caption": "Mercurial Diff",
405 | "command": "easy_diff_hg",
406 | "args": {"external": true, "paths": []}
407 | },
408 | {
409 | "caption": "Mercurial Diff with Previous Revision",
410 | "command": "easy_diff_hg",
411 | "args": {"external": true, "last": true, "paths": []}
412 | },
413 | {
414 | "caption": "Mercurial Revert",
415 | "command": "easy_diff_hg",
416 | "args": {"revert": true, "paths": []}
417 | },
418 | { "caption": "-"}'''
419 |
420 |
421 | ###############################
422 | # Tab Menus
423 | ###############################
424 | INTERNAL_TAB_MENU = '''{ "caption": "-" },
425 | {
426 | "caption": "EasyDiff Set Left Side",
427 | "children":
428 | [
429 | {
430 | "caption": "View",
431 | "command": "easy_diff_set_left",
432 | "args": {"group": -1, "index": -1}
433 | },
434 | {
435 | "caption": "Clipboard",
436 | "command": "easy_diff_set_left_clipboard",
437 | "args": {"group": -1, "index": -1}
438 | },
439 | {
440 | "caption": "Selection",
441 | "command": "easy_diff_set_left_selection",
442 | "args": {"group": -1, "index": -1}
443 | }
444 | ]
445 | },
446 | {
447 | "caption": "EasyDiff Compare with \\"%(file_name)s\\"",
448 | "children":
449 | [
450 | {
451 | "caption": "View",
452 | "command": "easy_diff_compare_both_view",
453 | "args": {"group": -1, "index": -1}
454 | },
455 | {
456 | "caption": "Clipboard",
457 | "command": "easy_diff_compare_both_clipboard",
458 | "args": {"group": -1, "index": -1}
459 | },
460 | {
461 | "caption": "Selection",
462 | "command": "easy_diff_compare_both_selection",
463 | "args": {"group": -1, "index": -1}
464 | }
465 | ]
466 | },
467 | '''
468 |
469 | EXTERNAL_TAB_MENU = '''{ "caption": "-" },
470 | {
471 | "caption": "Diff Set Left Side",
472 | "children":
473 | [
474 | {
475 | "caption": "View",
476 | "command": "easy_diff_set_left",
477 | "args": {"group": -1, "index": -1}
478 | },
479 | {
480 | "caption": "Clipboard",
481 | "command": "easy_diff_set_left_clipboard",
482 | "args": {"group": -1, "index": -1}
483 | },
484 | {
485 | "caption": "Selection",
486 | "command": "easy_diff_set_left_selection",
487 | "args": {"group": -1, "index": -1}
488 | }
489 | ]
490 | },
491 | {
492 | "caption": "Diff Compare with \\"%(file_name)s\\"",
493 | "children":
494 | [
495 | {
496 | "caption": "View",
497 | "command": "easy_diff_compare_both_view",
498 | "args": {"external": true, "group": -1, "index": -1}
499 | },
500 | {
501 | "caption": "Clipboard",
502 | "command": "easy_diff_compare_both_clipboard",
503 | "args": {"external": true, "group": -1, "index": -1}
504 | },
505 | {
506 | "caption": "Selection",
507 | "command": "easy_diff_compare_both_selection",
508 | "args": {"external": true, "group": -1, "index": -1}
509 | }
510 | ]
511 | },
512 | '''
513 |
514 | SVN_TAB_INTERNAL_MENU = '''
515 | {
516 | "caption": "SVN Diff",
517 | "command": "easy_diff_svn",
518 | "args": {"group": -1, "index": -1}
519 | },
520 | {
521 | "caption": "SVN Diff with Previous Revision",
522 | "command": "easy_diff_svn",
523 | "args": {"last": true, "group": -1, "index": -1}
524 | },
525 | {
526 | "caption": "SVN Revert",
527 | "command": "easy_diff_svn",
528 | "args": {"revert": true, "group": -1, "index": -1}
529 | },
530 | { "caption": "-"}'''
531 |
532 | GIT_TAB_INTERNAL_MENU = '''
533 | {
534 | "caption": "Git Diff",
535 | "command": "easy_diff_git",
536 | "args": {"group": -1, "index": -1}
537 | },
538 | {
539 | "caption": "Git Diff with Previous Revision",
540 | "command": "easy_diff_git",
541 | "args": {"last": true, "group": -1, "index": -1}
542 | },
543 | {
544 | "caption": "Git Revert",
545 | "command": "easy_diff_git",
546 | "args": {"revert": true, "group": -1, "index": -1}
547 | },
548 | { "caption": "-"}'''
549 |
550 | HG_TAB_INTERNAL_MENU = '''
551 | {
552 | "caption": "Mercurial Diff",
553 | "command": "easy_diff_hg",
554 | "args": {"group": -1, "index": -1}
555 | },
556 | {
557 | "caption": "Mercurial Diff with Previous Revision",
558 | "command": "easy_diff_hg",
559 | "args": {"last": true, "group": -1, "index": -1}
560 | },
561 | {
562 | "caption": "Mercurial Revert",
563 | "command": "easy_diff_hg",
564 | "args": {"revert": true, "group": -1, "index": -1}
565 | },
566 | { "caption": "-"}'''
567 |
568 | SVN_TAB_EXTERNAL_MENU = '''
569 | {
570 | "caption": "SVN Diff",
571 | "command": "easy_diff_svn",
572 | "args": {"external": true, "group": -1, "index": -1}
573 | },
574 | {
575 | "caption": "SVN Diff with Previous Revision",
576 | "command": "easy_diff_svn",
577 | "args": {"external": true, "last": true, "group": -1, "index": -1}
578 | },
579 | {
580 | "caption": "SVN Revert",
581 | "command": "easy_diff_svn",
582 | "args": {"revert": true, "group": -1, "index": -1}
583 | },
584 | { "caption": "-"}'''
585 |
586 | GIT_TAB_EXTERNAL_MENU = '''
587 | {
588 | "caption": "Git Diff",
589 | "command": "easy_diff_git",
590 | "args": {"external": true, "group": -1, "index": -1}
591 | },
592 | {
593 | "caption": "Git Diff with Previous Revision",
594 | "command": "easy_diff_git",
595 | "args": {"external": true, "last": true, "group": -1, "index": -1}
596 | },
597 | {
598 | "caption": "Git Revert",
599 | "command": "easy_diff_git",
600 | "args": {"revert": true, "group": -1, "index": -1}
601 | },
602 | { "caption": "-"}'''
603 |
604 | HG_TAB_EXTERNAL_MENU = '''
605 | {
606 | "caption": "Mercurial Diff",
607 | "command": "easy_diff_hg",
608 | "args": {"external": true, "group": -1, "index": -1}
609 | },
610 | {
611 | "caption": "Mercurial Diff with Previous Revision",
612 | "command": "easy_diff_hg",
613 | "args": {"external": true, "last": true, "group": -1, "index": -1}
614 | },
615 | {
616 | "caption": "Mercurial Revert",
617 | "command": "easy_diff_hg",
618 | "args": {"revert": true, "group": -1, "index": -1}
619 | },
620 | { "caption": "-"}'''
621 |
622 |
623 | ###############################
624 | # Menu Updater
625 | ###############################
626 | class MenuUpdater(object):
627 | def __init__(self, name):
628 | self.name = name
629 | self.menu_path = join(sublime.packages_path(), "User", MENU_FOLDER)
630 | if not exists(self.menu_path):
631 | makedirs(self.menu_path)
632 | settings = load_settings()
633 | self.menu_types = multiget(settings, "menu_types", [])
634 | self.svn_disabled = multiget(settings, "svn_disabled", False) or multiget(settings, "svn_hide_menu", False)
635 | self.git_disabled = multiget(settings, "git_disabled", False) or multiget(settings, "git_hide_menu", False)
636 | self.hg_disabled = multiget(settings, "hg_disabled", False) or multiget(settings, "hg_hide_menu", False)
637 | self.show_ext = multiget(settings, "show_external", False) and get_external_diff() is not None
638 | self.show_int = multiget(settings, "show_internal", True)
639 |
640 | def update_menu(self, menu_name, menus):
641 | if exists(self.menu_path):
642 | menu = join(self.menu_path, menu_name)
643 | vc_internal = []
644 | vc_internal_menu = None
645 | if self.show_int:
646 | if not self.svn_disabled:
647 | vc_internal.append(menus["svn"]["internal"])
648 | if not self.git_disabled:
649 | vc_internal.append(menus["git"]["internal"])
650 | if not self.hg_disabled:
651 | vc_internal.append(menus["hg"]["internal"])
652 | if len(vc_internal):
653 | vc_internal_menu = ",\n".join(vc_internal)
654 |
655 | vc_external = []
656 | vc_external_menu = None
657 | if self.show_ext:
658 | if not self.svn_disabled:
659 | vc_external.append(menus["svn"]["external"])
660 | if not self.git_disabled:
661 | vc_external.append(menus["git"]["external"])
662 | if not self.hg_disabled:
663 | vc_external.append(menus["hg"]["external"])
664 | if len(vc_external):
665 | vc_external_menu = ",\n".join(vc_external)
666 | with open(menu, "w") as f:
667 | f.write(
668 | DIFF_MENU % {
669 | "internal": ("" if not self.show_int else menus["internal"] % {"file_name": self.name}),
670 | "external": ("" if not self.show_ext else menus["external"] % {"file_name": self.name}),
671 | "vc_internal": ("" if vc_internal_menu is None or not self.show_int else VC_INTERNAL_MENU % {"vc": vc_internal_menu}),
672 | "vc_external": ("" if vc_external_menu is None or not self.show_ext else VC_EXTERNAL_MENU % {"vc": vc_external_menu})
673 | }
674 | )
675 |
676 | def remove_menu(self, menu_name):
677 | if exists(self.menu_path):
678 | menu = join(self.menu_path, menu_name)
679 | if exists(menu):
680 | remove(menu)
681 |
682 | def update_context_menu(self):
683 | menus = {
684 | "internal": INTERNAL_MENU,
685 | "external": EXTERNAL_MENU,
686 | "svn": {
687 | "internal": SVN_INTERNAL_MENU,
688 | "external": SVN_EXTERNAL_MENU
689 | },
690 | "git": {
691 | "internal": GIT_INTERNAL_MENU,
692 | "external": GIT_EXTERNAL_MENU
693 | },
694 | "hg": {
695 | "internal": HG_INTERNAL_MENU,
696 | "external": HG_EXTERNAL_MENU
697 | }
698 | }
699 | if "view" in self.menu_types:
700 | self.update_menu(CONTEXT_MENU, menus)
701 | else:
702 | self.remove_menu(CONTEXT_MENU)
703 |
704 | def update_sidebar_menu(self):
705 | menus = {
706 | "internal": INTERNAL_SIDEBAR_MENU,
707 | "external": EXTERNAL_SIDEBAR_MENU,
708 | "svn": {
709 | "internal": SVN_SIDEBAR_INTERNAL_MENU,
710 | "external": SVN_SIDEBAR_EXTERNAL_MENU
711 | },
712 | "git": {
713 | "internal": GIT_SIDEBAR_INTERNAL_MENU,
714 | "external": GIT_SIDEBAR_EXTERNAL_MENU
715 | },
716 | "hg": {
717 | "internal": HG_SIDEBAR_INTERNAL_MENU,
718 | "external": HG_SIDEBAR_EXTERNAL_MENU
719 | }
720 | }
721 | if "sidebar" in self.menu_types:
722 | self.update_menu(SIDEBAR_MENU, menus)
723 | else:
724 | self.remove_menu(SIDEBAR_MENU)
725 |
726 | def update_tab_menu(self):
727 | menus = {
728 | "internal": INTERNAL_TAB_MENU,
729 | "external": EXTERNAL_TAB_MENU,
730 | "svn": {
731 | "internal": SVN_TAB_INTERNAL_MENU,
732 | "external": SVN_TAB_EXTERNAL_MENU
733 | },
734 | "git": {
735 | "internal": GIT_TAB_INTERNAL_MENU,
736 | "external": GIT_TAB_EXTERNAL_MENU
737 | },
738 | "hg": {
739 | "internal": HG_TAB_INTERNAL_MENU,
740 | "external": HG_TAB_EXTERNAL_MENU
741 | }
742 | }
743 | if "tab" in self.menu_types:
744 | self.update_menu(TAB_MENU, menus)
745 | else:
746 | self.remove_menu(TAB_MENU)
747 |
748 |
749 | def update_menu(name="..."):
750 | menu_updater = MenuUpdater(name)
751 | menu_updater.update_context_menu()
752 | menu_updater.update_sidebar_menu()
753 | menu_updater.update_tab_menu()
754 |
755 |
756 | ###############################
757 | # Loaders
758 | ###############################
759 | def refresh_menu():
760 | update_menu()
761 | debug("refresh menu")
762 | settings = load_settings()
763 | settings.clear_on_change('reload_menu')
764 | settings.add_on_change('reload_menu', refresh_menu)
765 |
766 |
767 | def plugin_loaded():
768 | refresh_menu()
769 |
--------------------------------------------------------------------------------
/easy_diff_global.py:
--------------------------------------------------------------------------------
1 | """
2 | Easy Diff Global
3 |
4 | Copyright (c) 2013 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | from os.path import exists, normpath, abspath, isdir
9 | from EasyDiff.lib.multiconf import get as multiget
10 | import re
11 | try:
12 | from SubNotify.sub_notify import SubNotifyIsReadyCommand as Notify
13 | except:
14 | class Notify:
15 | @classmethod
16 | def is_ready(cls):
17 | return False
18 |
19 | DEBUG = False
20 | SETTINGS = "easy_diff.sublime-settings"
21 |
22 |
23 | def log(msg, status=False):
24 | string = str(msg)
25 | print("EasyDiff: %s" % string)
26 | if status:
27 | notify(string)
28 |
29 |
30 | def debug(msg, status=False):
31 | if DEBUG:
32 | log(msg, status)
33 |
34 |
35 | def load_settings():
36 | return sublime.load_settings(SETTINGS)
37 |
38 |
39 | def global_reload():
40 | set_debug_flag()
41 | settings = load_settings()
42 | settings.clear_on_change('reload_global')
43 | settings.add_on_change('reload_global', global_reload)
44 |
45 |
46 | def set_debug_flag():
47 | global DEBUG
48 | settings = load_settings()
49 | DEBUG = settings.get("debug", False)
50 | debug("debug logging enabled")
51 |
52 |
53 | def get_encoding(view):
54 | encoding = view.encoding()
55 | mapping = [
56 | ("with BOM", ""),
57 | ("Windows", "cp"),
58 | ("-", "_"),
59 | (" ", "")
60 | ]
61 | encoding = view.encoding()
62 | m = re.match(r'.+\((.*)\)', encoding)
63 | if m is not None:
64 | encoding = m.group(1)
65 |
66 | for item in mapping:
67 | encoding = encoding.replace(item[0], item[1])
68 |
69 | return "utf_8" if encoding in ["Undefined", "Hexidecimal"] else encoding
70 |
71 |
72 | def get_external_diff():
73 | settings = load_settings()
74 | ext_diff = multiget(settings, "external_diff", None)
75 | diff_path = None if ext_diff is None or ext_diff == "" or not exists(abspath(normpath(ext_diff))) else abspath(normpath(ext_diff))
76 | debug("External diff was not found!" if diff_path is None else "External diff \"%s\" found." % diff_path)
77 | return diff_path
78 |
79 |
80 | def get_target(paths=[], group=-1, index=-1):
81 | target = None
82 | if index != -1:
83 | view = sublime.active_window().views_in_group(group)[index]
84 | target = view.file_name()
85 | if target is None:
86 | target = ""
87 | elif len(paths) and exists(paths[0]) and not isdir(paths[0]):
88 | target = paths[0]
89 | return target
90 |
91 |
92 | def notify(msg):
93 | if load_settings().get("use_sub_notify", False) and Notify.is_ready():
94 | sublime.run_command("sub_notify", {"title": "EasyDiff", "msg": msg})
95 | else:
96 | sublime.status_message(msg)
97 |
98 |
99 | def plugin_loaded():
100 | global_reload()
101 |
--------------------------------------------------------------------------------
/easy_diff_version_control.py:
--------------------------------------------------------------------------------
1 | """
2 | Easy Diff Version Control
3 |
4 | Copyright (c) 2013 Isaac Muse
5 | License: MIT
6 | """
7 | import sublime
8 | import sublime_plugin
9 | from os.path import basename, splitext, join
10 | import EasyDiff.lib.svn as svn
11 | import EasyDiff.lib.git as git
12 | import EasyDiff.lib.hg as hg
13 | from EasyDiff.lib.multiconf import get as multiget
14 | from EasyDiff.easy_diff_global import load_settings, log, debug, get_encoding, get_external_diff, get_target, notify
15 | import subprocess
16 | import tempfile
17 |
18 | SVN_ENABLED = False
19 | GIT_ENABLED = False
20 | HG_ENABLED = False
21 |
22 |
23 | ###############################
24 | # Version Control Base
25 | ###############################
26 | class _VersionControlDiff(object):
27 | control_type = ""
28 | control_enabled = False
29 | temp_folder = None
30 |
31 | def get_diff(self, name, **kwargs):
32 | return None
33 |
34 | def is_versioned(self, name):
35 | return False
36 |
37 | def vc_is_enabled(self, name):
38 | enabled = False
39 | if name is not None:
40 | try:
41 | enabled = (
42 | self.control_enabled and
43 | (
44 | multiget(load_settings(), "skip_version_check_on_is_enabled", False) or
45 | self.is_versioned(name)
46 | )
47 | )
48 | except:
49 | pass
50 | return enabled
51 |
52 | def decode(self, result):
53 | try:
54 | debug("decoding with %s" % self.encoding)
55 | return result.decode(self.encoding)
56 | except:
57 | debug("fallback to utf-8 decode")
58 | return result.decode('utf-8')
59 |
60 | def get_encoding(self):
61 | return get_encoding(self.view)
62 |
63 | def create_temp(self):
64 | if self.temp_folder is None:
65 | self.temp_folder = tempfile.mkdtemp(prefix="easydiff")
66 |
67 | def get_files(name, **kwargs):
68 | return None, None
69 |
70 | def revert_file(self, name):
71 | pass
72 |
73 | def revert(self, name):
74 | result = self.get_diff(name)
75 |
76 | if result == "":
77 | notify("Nothing to Revert")
78 | result = None
79 |
80 | if result is not None and sublime.ok_cancel_dialog("Are you sure you want to revert \"%s\"?" % basename(name)):
81 | try:
82 | self.revert_file(name)
83 | except Exception as e:
84 | debug(e)
85 | sublime.error_message("Could not revert \"%s\"!" % basename(name))
86 |
87 | def internal_diff(self, name, **kwargs):
88 | result = self.get_diff(name, **kwargs)
89 |
90 | if result == "":
91 | notify("No Difference")
92 | result = None
93 |
94 | if result is not None:
95 | use_buffer = bool(load_settings().get("use_buffer", False))
96 |
97 | win = sublime.active_window()
98 | if use_buffer:
99 | v = win.new_file()
100 | v.set_name("EasyDiff: %s (%s)" % (self.control_type, basename(name)))
101 | v.set_scratch(True)
102 | v.assign_syntax('Packages/Diff/Diff.tmLanguage')
103 | v.run_command('append', {'characters': result})
104 | else:
105 | v = win.create_output_panel('easy_diff')
106 | v.assign_syntax('Packages/Diff/Diff.tmLanguage')
107 | v.run_command('append', {'characters': result})
108 | win.run_command("show_panel", {"panel": "output.easy_diff"})
109 |
110 | def external_diff(self, name, **kwargs):
111 | self.create_temp()
112 | f1, f2 = self.get_files(name, **kwargs)
113 | ext_diff = get_external_diff()
114 | if f1 is not None and f2 is not None:
115 | subprocess.Popen(
116 | [
117 | ext_diff,
118 | f1,
119 | f2
120 | ]
121 | )
122 |
123 | def is_loaded(self):
124 | if self.view.is_loading():
125 | sublime.set_timeout(self.is_loaded, 100)
126 | else:
127 | self.diff()
128 |
129 | def vc_run(self, **kwargs):
130 | self.kwargs = kwargs
131 | sublime.set_timeout(self.is_loaded, 100)
132 |
133 | def diff(self):
134 | name = self.view.file_name() if self.view is not None else None
135 | self.encoding = self.get_encoding()
136 | if name is not None:
137 | if self.kwargs.get("revert"):
138 | self.revert(name)
139 | else:
140 | external = self.kwargs.get("external", False)
141 | if not external:
142 | self.internal_diff(name, **self.kwargs)
143 | else:
144 | self.external_diff(name, **self.kwargs)
145 |
146 |
147 | class _VersionControlCommand(sublime_plugin.WindowCommand):
148 | def run(self, paths=[], group=-1, index=-1, **kwargs):
149 | if len(paths):
150 | name = get_target(paths)
151 | elif index != -1:
152 | self.view = sublime.active_window().views_in_group(group)[index]
153 | name = self.view.file_name()
154 | else:
155 | self.view = self.window.active_view()
156 | if self.view is None:
157 | return False
158 | name = self.view.file_name() if self.view is not None else None
159 |
160 | if name is None:
161 | return False
162 |
163 | if len(paths):
164 | self.view = self.window.open_file(name)
165 |
166 | self.vc_run(**kwargs)
167 |
168 | def is_enabled(self, paths=[], group=-1, index=-1, **kwargs):
169 | if len(paths) or index != -1:
170 | name = get_target(paths, group, index)
171 | else:
172 | self.view = self.window.active_view()
173 | if self.view is None:
174 | return False
175 | name = self.view.file_name() if self.view is not None else None
176 |
177 | if name is None:
178 | return False
179 |
180 | return self.vc_is_enabled(name)
181 |
182 |
183 | ###############################
184 | # Version Control Specific Classes
185 | ###############################
186 | class _EasyDiffSvn(_VersionControlDiff):
187 | def setup(self):
188 | self.control_type = "SVN"
189 | self.control_enabled = SVN_ENABLED
190 |
191 | def revert_file(self, name):
192 | svn.revert(name)
193 |
194 | def get_files(self, name, **kwargs):
195 | f1 = None
196 | f2 = None
197 | if self.is_versioned(name):
198 | f2 = name
199 | root, ext = splitext(basename(name))
200 | rev = None
201 | if kwargs.get("last", False):
202 | rev = "PREV"
203 | f1 = join(self.temp_folder, "%s-r%s-LEFT%s" % (root, rev, ext))
204 | else:
205 | rev = "BASE"
206 | f1 = join(self.temp_folder, "%s-r%s-LEFT%s" % (root, rev, ext))
207 | svn.export(f2, f1, rev=rev)
208 | else:
209 | log("View not versioned under SVN!", status=True)
210 | return f1, f2
211 |
212 | def is_versioned(self, name):
213 | disabled = multiget(load_settings(), "svn_disabled", False)
214 | return not disabled and svn.is_versioned(name)
215 |
216 | def get_diff(self, name, **kwargs):
217 | result = None
218 | if self.is_versioned(name):
219 | result = self.decode(svn.diff(name, last=kwargs.get("last", False))).replace('\r', '')
220 | else:
221 | log("View not versioned under SVN!", status=True)
222 | return result
223 |
224 |
225 | class _EasyDiffGit(_VersionControlDiff):
226 | def setup(self):
227 | self.control_type = "GIT"
228 | self.control_enabled = GIT_ENABLED
229 |
230 | def revert_file(self, name):
231 | git.checkout(name)
232 |
233 | def get_files(self, name, **kwargs):
234 | f1 = None
235 | f2 = None
236 | if self.is_versioned(name):
237 | f2 = name
238 | root, ext = splitext(basename(name))
239 | rev = None
240 | if kwargs.get("last", False):
241 | revs = git.getrevision(f2, 2)
242 | if revs is not None and len(revs) == 2:
243 | rev = revs[1]
244 | if rev is not None:
245 | f1 = join(self.temp_folder, "%s-r%s-LEFT%s" % (root, rev, ext))
246 | else:
247 | rev = "HEAD"
248 | f1 = join(self.temp_folder, "%s-r%s-LEFT%s" % (root, rev, ext))
249 | if f1 is not None:
250 | with open(f1, "wb") as f:
251 | bfr = git.show(f2, rev)
252 | if bfr is not None:
253 | f.write(bfr)
254 | else:
255 | f1 = None
256 | else:
257 | log("View not versioned under Git!", status=True)
258 | return f1, f2
259 |
260 | def is_versioned(self, name):
261 | disabled = multiget(load_settings(), "git_disabled", False)
262 | return not disabled and git.is_versioned(name)
263 |
264 | def get_diff(self, name, **kwargs):
265 | result = None
266 | if git.is_versioned(name):
267 | result = self.decode(
268 | git.diff(
269 | name,
270 | last=kwargs.get("last", False)
271 | )
272 | ).replace('\r', '')
273 | else:
274 | log("View not versioned under Git!", status=True)
275 | return result
276 |
277 |
278 | class _EasyDiffHg(_VersionControlDiff):
279 | def setup(self):
280 | self.control_type = "HG"
281 | self.control_enabled = HG_ENABLED
282 |
283 | def revert_file(self, name):
284 | hg.revert(name)
285 |
286 | def get_files(self, name, **kwargs):
287 | f1 = None
288 | f2 = None
289 | if self.is_versioned(name):
290 | f2 = name
291 | root, ext = splitext(basename(name))
292 | rev = None
293 | if kwargs.get("last", False):
294 | revs = hg.getrevision(f2, 2)
295 | if revs is not None and len(revs) == 2:
296 | rev = revs[1]
297 | if rev is not None:
298 | f1 = join(self.temp_folder, "%s-r%s-LEFT%s" % (root, rev, ext))
299 | else:
300 | # Leave rev as None
301 | f1 = join(self.temp_folder, "%s-r%s-LEFT%s" % (root, "BASE", ext))
302 | if f1 is not None:
303 | with open(f1, "wb") as f:
304 | bfr = hg.cat(f2, rev)
305 | if bfr is not None:
306 | f.write(bfr)
307 | else:
308 | f1 = None
309 | else:
310 | log("View not versioned under Mercurial!", status=True)
311 | return f1, f2
312 |
313 | def is_versioned(self, name):
314 | disabled = multiget(load_settings(), "hg_disabled", False)
315 | return not disabled and hg.is_versioned(name)
316 |
317 | def get_diff(self, name, **kwargs):
318 | result = None
319 | if self.is_versioned(name):
320 | result = self.decode(hg.diff(name, last=kwargs.get("last", False))).replace('\r', '')
321 | else:
322 | log("View not versioned under Mercurial!", status=True)
323 | return result
324 |
325 | class _EasyDiffAuto(_VersionControlDiff):
326 | def setup(self):
327 | self.control_type = "Auto"
328 | self.control_enabled = True
329 | self.vcs = None
330 |
331 | def revert_file(self, name):
332 | return self.vcs.revert(name) if self.vcs != None else None
333 |
334 | def get_files(self, name, **kwargs):
335 | return self.vcs.get_files(name, **kwargs) if self.vcs != None else None
336 |
337 | def what_vcs(self, name):
338 | if (not multiget(load_settings(), "svn_disabled", False) and _EasyDiffSvn().is_versioned(name)):
339 | return _EasyDiffSvn()
340 | elif (not multiget(load_settings(), "git_disabled", False) and _EasyDiffGit().is_versioned(name)):
341 | return _EasyDiffGit()
342 | elif (not multiget(load_settings(), "hg_disabled", False) and _EasyDiffHg().is_versioned(name)):
343 | return _EasyDiffHg()
344 |
345 | return None
346 |
347 | def is_versioned(self, name):
348 | #if self.vcs == None:
349 | self.vcs = self.what_vcs(name)
350 |
351 | return self.vcs.is_versioned(name) if self.vcs != None else False
352 |
353 | def get_diff(self, name, **kwargs):
354 | #if self.vcs == None:
355 | self.vcs = self.what_vcs(name)
356 |
357 | return self.vcs.get_diff(name, **kwargs) if self.vcs != None else None
358 |
359 | ###############################
360 | # Version Control Commands
361 | ###############################
362 | class EasyDiffAutoCommand(_VersionControlCommand, _EasyDiffAuto):
363 | def __init__(self, window):
364 | super().__init__(window)
365 | self.setup()
366 |
367 | class EasyDiffSvnCommand(_VersionControlCommand, _EasyDiffSvn):
368 | def __init__(self, window):
369 | super().__init__(window)
370 | self.setup()
371 |
372 |
373 | class EasyDiffGitCommand(_VersionControlCommand, _EasyDiffGit):
374 | def __init__(self, window):
375 | super().__init__(window)
376 | self.setup()
377 |
378 |
379 | class EasyDiffHgCommand(_VersionControlCommand, _EasyDiffHg):
380 | def __init__(self, window):
381 | super().__init__(window)
382 | self.setup()
383 |
384 |
385 | ###############################
386 | # Loaders
387 | ###############################
388 | def setup_vc_binaries():
389 | global SVN_ENABLED
390 | global GIT_ENABLED
391 | global HG_ENABLED
392 |
393 | settings = load_settings()
394 | svn_path = multiget(settings, "svn", None)
395 | git_path = multiget(settings, "git", None)
396 | hg_path = multiget(settings, "hg", None)
397 | if svn_path is not None and svn_path != "":
398 | svn.set_svn_path(svn_path)
399 | if git_path is not None and git_path != "":
400 | git.set_git_path(git_path)
401 | if hg_path is not None and hg_path != "":
402 | hg.set_hg_path(hg_path)
403 |
404 | try:
405 | log("svn %s" % svn.version())
406 | SVN_ENABLED = True
407 | except:
408 | log("svn not found or is not working!")
409 | pass
410 | try:
411 | log("git %s" % git.version())
412 | GIT_ENABLED = True
413 | except:
414 | log("git not found or is not working!")
415 | pass
416 | try:
417 | log("hg %s" % hg.version())
418 | HG_ENABLED = True
419 | except:
420 | log("hg not found or is not working!")
421 | pass
422 |
423 | settings.clear_on_change('reload_vc')
424 | settings.add_on_change('reload_vc', setup_vc_binaries)
425 |
426 |
427 | def plugin_loaded():
428 | setup_vc_binaries()
429 |
--------------------------------------------------------------------------------
/lib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/derjanb/EasyDiff/2933ed6c75a0691bed9d2bb297a1ba80d81df810/lib/__init__.py
--------------------------------------------------------------------------------
/lib/git.py:
--------------------------------------------------------------------------------
1 | """
2 | git
3 |
4 | Copyright (c) 2013 Isaac Muse
5 | License: MIT
6 | """
7 | # import xml.etree.ElementTree as ET
8 | from os import environ
9 | import re
10 | import subprocess
11 | import sys
12 | from os.path import exists, isfile, dirname, join
13 |
14 | if sys.platform.startswith('win'):
15 | _PLATFORM = "windows"
16 | elif sys.platform == "darwin":
17 | _PLATFORM = "osx"
18 | else:
19 | _PLATFORM = "linux"
20 |
21 | _git_path = "git.exe" if _PLATFORM == "windows" else "git"
22 |
23 | # UNSTAGED_DIFF = 0
24 | # STAGED_DIFF = 1
25 | # ALL_DIFF = 2
26 |
27 |
28 | def is_system_root(target):
29 | """
30 | Check if target is the root folder
31 | """
32 |
33 | root = False
34 | windows = _PLATFORM == "windows"
35 | if windows and re.match(r"^[A-Za-z]{1}:\\$", target) is not None:
36 | root = True
37 | elif not windows and target == '/':
38 | root = True
39 |
40 | return root
41 |
42 |
43 | def get_git_tree(target):
44 | """
45 | Recursively get Git tree
46 | """
47 |
48 | root = is_system_root(target)
49 | is_file = isfile(target)
50 | folder = dirname(target) if is_file else target
51 | if exists(join(folder, ".git")):
52 | return folder
53 | else:
54 | if root:
55 | return None
56 | else:
57 | return get_git_tree(dirname(folder))
58 |
59 |
60 | def get_git_dir(tree):
61 | """
62 | Get Git directory from tree
63 | """
64 |
65 | return join(tree, ".git")
66 |
67 |
68 | def gitopen(args, git_tree=None):
69 | """
70 | Call Git with arguments
71 | """
72 |
73 | returncode = None
74 | output = None
75 |
76 | if git_tree is not None:
77 | cmd = [_git_path, "--work-tree=%s" % git_tree, "--git-dir=%s" % get_git_dir(git_tree)] + args
78 | else:
79 | cmd = [_git_path] + args
80 |
81 | env = environ.copy()
82 |
83 | if _PLATFORM == "windows":
84 | env['LC_ALL'] = 'en_US'
85 | startupinfo = subprocess.STARTUPINFO()
86 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
87 | process = subprocess.Popen(
88 | cmd,
89 | startupinfo=startupinfo,
90 | stdout=subprocess.PIPE,
91 | stderr=subprocess.STDOUT,
92 | stdin=subprocess.PIPE,
93 | shell=False,
94 | env=env
95 | )
96 | else:
97 | env['LC_ALL'] = 'en_US.UTF-8'
98 | process = subprocess.Popen(
99 | cmd,
100 | stdout=subprocess.PIPE,
101 | stderr=subprocess.STDOUT,
102 | stdin=subprocess.PIPE,
103 | shell=False,
104 | env=env
105 | )
106 | output = process.communicate()
107 | returncode = process.returncode
108 |
109 | assert returncode == 0, "Runtime Error: %s" % output[0].rstrip()
110 |
111 | return output[0]
112 |
113 |
114 | def show(target, rev):
115 | """
116 | Show file at revision
117 | """
118 |
119 | assert exists(target), "%s does not exist!" % target
120 | git_tree = get_git_tree(target)
121 | bfr = None
122 | target = target.replace(git_tree, "", 1).lstrip("\\" if _PLATFORM == "windows" else "/")
123 |
124 | if _PLATFORM == "windows":
125 | target = target.replace("\\", "/")
126 | if git_tree is not None:
127 | bfr = gitopen(["show", "%s:%s" % (rev, target)], git_tree)
128 | return bfr
129 |
130 |
131 | def getrevision(target, count=1):
132 | """
133 | Get revision(s)
134 | """
135 |
136 | assert exists(target), "%s does not exist!" % target
137 | git_tree = get_git_tree(target)
138 | revs = None
139 |
140 | if git_tree is not None:
141 | revs = []
142 | lg = gitopen(["log", "--no-color", "--pretty=oneline", "-n", str(count), target], git_tree)
143 | for m in re.finditer(br"([a-f\d]{40}) .*\r?\n", lg):
144 | revs.append(m.group(1).decode("utf-8"))
145 | return revs
146 |
147 |
148 | def checkout(target, rev=None):
149 | """
150 | Checkout file
151 | """
152 |
153 | assert exists(target), "%s does not exist!" % target
154 | git_tree = get_git_tree(target)
155 |
156 | if git_tree is not None:
157 | args = ["checkout"]
158 | if rev is not None:
159 | args.append(rev)
160 | args.append(target)
161 |
162 | gitopen(args, git_tree)
163 |
164 |
165 | def diff(target, last=False):
166 | """
167 | Diff current file against last revision
168 | """
169 |
170 | assert exists(target), "%s does not exist!" % target
171 | # assert diff_type in [ALL_DIFF, STAGED_DIFF, UNSTAGED_DIFF], "diff_type is bad!"
172 | git_tree = get_git_tree(target)
173 | results = b""
174 |
175 | if git_tree is not None:
176 | args = ["diff", "--no-color", "--src-prefix=%s/" % git_tree, "--dst-prefix=%s/" % git_tree]
177 |
178 | if last:
179 | revs = getrevision(target, 2)
180 |
181 | if len(revs) == 2:
182 | args += [revs[1], "--"]
183 | else:
184 | args = None
185 | else:
186 | args += ["HEAD", "--"]
187 |
188 | # Staged only
189 | # elif diff_type == STAGED_DIFF:
190 | # args.append("--cached")
191 |
192 | if args:
193 | results = gitopen(args + [target], git_tree)
194 | return results
195 |
196 |
197 | def is_versioned(target):
198 | """
199 | Check if file/folder is versioned
200 | """
201 |
202 | assert exists(target), "%s does not exist!" % target
203 | git_tree = get_git_tree(target)
204 |
205 | versioned = False
206 | if git_tree is not None:
207 | output = gitopen(["status", "--ignored", "--porcelain", target], git_tree)
208 | if not (output.startswith(b"!!") or output.startswith(b"??")):
209 | versioned = True
210 |
211 | return versioned
212 |
213 |
214 | def version():
215 | """
216 | Get Git app version
217 | """
218 |
219 | version = None
220 | output = gitopen(['--version'])
221 | m = re.search(br" version ([\d\.A-Za-z]+)", output)
222 | if m is not None:
223 | version = m.group(1).decode('utf-8')
224 | return version
225 |
226 |
227 | def set_git_path(pth):
228 | """
229 | Set Git path
230 | """
231 |
232 | global _git_path
233 | _git_path = pth
234 |
--------------------------------------------------------------------------------
/lib/hg.py:
--------------------------------------------------------------------------------
1 | """
2 | hg
3 |
4 | Copyright (c) 2013 Isaac Muse
5 | License: MIT
6 | """
7 | import xml.etree.ElementTree as ET
8 | from os import environ
9 | import re
10 | import subprocess
11 | import sys
12 | from os.path import exists, dirname
13 |
14 | if sys.platform.startswith('win'):
15 | _PLATFORM = "windows"
16 | elif sys.platform == "darwin":
17 | _PLATFORM = "osx"
18 | else:
19 | _PLATFORM = "linux"
20 |
21 | _hg_path = "hg.exe" if _PLATFORM == "windows" else "hg"
22 |
23 |
24 | def hgopen(args, cwd=None):
25 | """
26 | Call Git with arguments
27 | """
28 |
29 | returncode = None
30 | output = None
31 |
32 | cmd = [_hg_path] + args
33 |
34 | env = environ.copy()
35 |
36 | if _PLATFORM == "windows":
37 | env['LC_ALL'] = 'en_US'
38 | startupinfo = subprocess.STARTUPINFO()
39 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
40 | process = subprocess.Popen(
41 | cmd,
42 | startupinfo=startupinfo,
43 | stdout=subprocess.PIPE,
44 | stderr=subprocess.STDOUT,
45 | stdin=subprocess.PIPE,
46 | cwd=cwd,
47 | shell=False,
48 | env=env
49 | )
50 | else:
51 | env['LC_ALL'] = 'en_US.UTF-8'
52 | process = subprocess.Popen(
53 | cmd,
54 | stdout=subprocess.PIPE,
55 | stderr=subprocess.STDOUT,
56 | stdin=subprocess.PIPE,
57 | cwd=cwd,
58 | shell=False,
59 | env=env
60 | )
61 | output = process.communicate()
62 | returncode = process.returncode
63 |
64 | assert returncode == 0, "Runtime Error: %s\n%s" % (output[0].rstrip(), str(cmd))
65 |
66 | return output[0]
67 |
68 |
69 | def cat(target, rev=None):
70 | """
71 | Show file at revision
72 | """
73 |
74 | assert exists(target), "%s does not exist!" % target
75 | args = ["cat", target]
76 | if rev is not None:
77 | args += ["-r", str(rev)]
78 | return hgopen(args)
79 |
80 |
81 | def revert(target):
82 | """
83 | Revert file
84 | """
85 |
86 | assert exists(target), "%s does not exist!" % target
87 | hgopen(["revert", "--no-backup", target], dirname(target))
88 |
89 |
90 | def getrevision(target, count=1):
91 | """
92 | Get revision(s)
93 | """
94 |
95 | assert exists(target), "%s does not exist!" % target
96 | results = log(target, count)
97 | assert results is not None, "Failed to acquire log info!"
98 | revs = []
99 | for entry in results.findall('logentry'):
100 | revs.append(entry.attrib["node"])
101 | return revs
102 |
103 |
104 | def diff(target, last=False):
105 | """
106 | Diff current file against last revision
107 | """
108 |
109 | args = None
110 |
111 | assert exists(target), "%s does not exist!" % target
112 | if last:
113 | revs = getrevision(target, 2)
114 | if len(revs) == 2:
115 | args = ["diff", "-p", "-r", revs[1]]
116 | else:
117 | args = ["diff", "-p"]
118 |
119 | return hgopen(args + [target]) if args is not None else b""
120 |
121 |
122 | def log(target=None, limit=0):
123 | """
124 | Get hg log(s)
125 | """
126 |
127 | assert exists(target), "%s does not exist!" % target
128 |
129 | args = ["log", "--style=xml"]
130 | if limit != 0:
131 | args.append("-l")
132 | args.append(str(limit))
133 | if target is not None:
134 | args.append(target)
135 | output = hgopen(args)
136 |
137 | if output != "":
138 | results = ET.fromstring(output)
139 | else:
140 | results = None
141 |
142 | return results
143 |
144 |
145 | def is_versioned(target):
146 | """
147 | Check if file/folder is versioned
148 | """
149 |
150 | assert exists(target), "%s does not exist!" % target
151 | versioned = False
152 | try:
153 | results = log(target, 1)
154 | if results is not None:
155 | if results.find("logentry") is not None:
156 | versioned = True
157 | except:
158 | pass
159 |
160 | return versioned
161 |
162 |
163 | def version():
164 | """
165 | Get hg app version
166 | """
167 |
168 | version = None
169 | output = hgopen(['--version'])
170 | m = re.search(br"\bversion ([\d\.A-Za-z]+)", output)
171 | if m is not None:
172 | version = m.group(1).decode('utf-8')
173 | return version
174 |
175 |
176 | def set_hg_path(pth):
177 | """
178 | Set hg path
179 | """
180 |
181 | global _hg_path
182 | _hg_path = pth
183 |
--------------------------------------------------------------------------------
/lib/multiconf.py:
--------------------------------------------------------------------------------
1 | import socket
2 | import sublime
3 | import re
4 |
5 | """
6 | Copyright (C) 2012 Isaac Muse
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11 |
12 | Licensed under MIT.
13 |
14 | Thanks to: biermeester and matthjes for their ideas and contributions
15 |
16 | Multiconf is a module that allows you to read platforma and/or host
17 | specific configuration values to be used by Sublime Text 2 plugins.
18 |
19 | Using this module's `get` function, allows the user to replace any settings
20 | value in a '.settings' file with a dictionary containing multiple values.
21 |
22 | Mulitconf does this by using a dictionary with a special identifier
23 | "#multiconf#" and a list of dictionaries identified by a qualifier of the form
24 |
25 | ":[;:]..."
26 |
27 | For example, the following setting
28 |
29 | "user_home": "/home"
30 |
31 | would result in `get("user_home")` returning the value "/home" but it could also
32 | be replaced with
33 |
34 | "user_home": {
35 | "#multiconf#": [
36 | {"os:windows": "C:\\Users"},
37 | {"os:linux;host:his_pc": "/home"},
38 | {"os:linux;host:her_pc": "/home/her/special"}
39 | ]
40 | }
41 |
42 | Now the same configuration file will provide different values depending on the
43 | machine it's on. On an MS Windows machine the value returned by `get` will be
44 | "C:\\Users", and on a Linux machine with the host name 'his_pc' the value will be
45 | "/home".
46 | """
47 |
48 | __version__ = "1.0"
49 |
50 | __CURRENT_HOSTNAME = socket.gethostname().lower()
51 |
52 | QUALIFIERS = r"""([A-Za-z\d_]*):([^;]*)(?:;|$)"""
53 |
54 |
55 | def get(settings_obj, key, default=None, callback=None):
56 | """
57 | Return a Sublime Text plugin setting value
58 |
59 | Parameters:
60 | settings_obj - a sublime.Settings object or a dictionary containing
61 | settings
62 | key - the name of the setting
63 | default - the default value to return if the key value is not found.
64 | callback - a callback function that, if provided, will be called with
65 | the found and default values as parameters.
66 |
67 | """
68 | # Parameter validation
69 | if not isinstance(settings_obj, (dict, sublime.Settings)):
70 | raise AttributeError("Invalid settings object")
71 | if not isinstance(key, str):
72 | raise AttributeError("Invalid callback function")
73 | if callback is not None and not hasattr(callback, '__call__'):
74 | raise AttributeError("Invalid callback function")
75 |
76 | setting = settings_obj.get(key, default)
77 | final_val = None
78 |
79 | if isinstance(setting, dict) and "#multiconf#" in setting:
80 | reject_item = False
81 | for entry in setting["#multiconf#"]:
82 | reject_item = False if isinstance(entry, dict) and len(entry) else True
83 |
84 | k, v = entry.popitem()
85 |
86 | if reject_item:
87 | continue
88 |
89 | for qual in re.compile(QUALIFIERS).finditer(k):
90 | if Qualifications.exists(qual.group(1)):
91 | reject_item = not Qualifications.eval_qual(qual.group(1), qual.group(2))
92 | else:
93 | reject_item = True
94 | if reject_item:
95 | break
96 |
97 | if not reject_item:
98 | final_val = v
99 | break
100 |
101 | if reject_item:
102 | final_val = default
103 | else:
104 | final_val = setting
105 |
106 | return callback(final_val, default) if callback else final_val
107 |
108 |
109 | class QualException(Exception):
110 | pass
111 |
112 |
113 | class Qualifications(object):
114 | __qualifiers = {}
115 |
116 | @classmethod
117 | def add_qual(cls, key, callback):
118 | if isinstance(key, str) and re.match(r"^[a-zA-Z][a-zA-Z\d_]*$", key) is None:
119 | raise QualException("'%s' is not a valid function name." % key)
120 | if not hasattr(callback, '__call__'):
121 | raise QualException("Bad function callback.")
122 | if key in cls.__qualifiers:
123 | raise QualException("'%s' qualifier already exists." % key)
124 |
125 | cls.__qualifiers[key] = callback
126 |
127 | @classmethod
128 | def exists(cls, key):
129 | return (key in cls.__qualifiers)
130 |
131 | @classmethod
132 | def eval_qual(cls, key, value):
133 | try:
134 | return cls.__qualifiers[key](value)
135 | except:
136 | raise QualException("Failed to execute %s qualifier" % key)
137 |
138 |
139 | def __host_match(h):
140 | return (h.lower() == __CURRENT_HOSTNAME)
141 |
142 |
143 | def __os_match(os):
144 | return (os == sublime.platform())
145 |
146 |
147 | Qualifications.add_qual("host", __host_match)
148 | Qualifications.add_qual("os", __os_match)
149 |
--------------------------------------------------------------------------------
/lib/svn.py:
--------------------------------------------------------------------------------
1 | """
2 | svn
3 |
4 | Copyright (c) 2013 Isaac Muse
5 | License: MIT
6 | """
7 | import xml.etree.ElementTree as ET
8 | from os import environ
9 | import re
10 | import subprocess
11 | import sys
12 | from os.path import exists, isfile
13 |
14 | NO_LOCK = 0
15 | LOCAL_LOCK = 1
16 | REMOTE_LOCK = 2
17 | ORPHAN_LOCK = 3
18 |
19 | if sys.platform.startswith('win'):
20 | _PLATFORM = "windows"
21 | elif sys.platform == "darwin":
22 | _PLATFORM = "osx"
23 | else:
24 | _PLATFORM = "linux"
25 |
26 | _svn_path = "svn.exe" if _PLATFORM == "windows" else "svn"
27 |
28 |
29 | def svnopen(args):
30 | """
31 | Call SVN with arguments
32 | """
33 |
34 | returncode = None
35 | output = None
36 | cmd = [_svn_path, "--non-interactive"] + args
37 |
38 | env = environ.copy()
39 |
40 | if _PLATFORM == "windows":
41 | env['LC_ALL'] = 'en_US'
42 | startupinfo = subprocess.STARTUPINFO()
43 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
44 | process = subprocess.Popen(
45 | cmd,
46 | startupinfo=startupinfo,
47 | stdout=subprocess.PIPE,
48 | stderr=subprocess.STDOUT,
49 | stdin=subprocess.PIPE,
50 | shell=False,
51 | env=env
52 | )
53 | else:
54 | env['LC_ALL'] = 'en_US.UTF-8'
55 | process = subprocess.Popen(
56 | cmd,
57 | stdout=subprocess.PIPE,
58 | stderr=subprocess.STDOUT,
59 | stdin=subprocess.PIPE,
60 | shell=False,
61 | env=env
62 | )
63 | output = process.communicate()
64 | returncode = process.returncode
65 |
66 | assert returncode == 0, "Runtime Error: %s" % output[0].rstrip()
67 |
68 | return output[0]
69 |
70 |
71 | def revert(target):
72 | """
73 | Revert file
74 | """
75 |
76 | assert exists(target), "%s does not exist!" % target
77 | svnopen(["revert", target])
78 |
79 |
80 | def info(target):
81 | """
82 | Get general SVN info
83 | """
84 |
85 | assert (target.startswith("http://") or target.startswith("https://")) or exists(target), "%s does not exist!" % target
86 | output = svnopen(['info', "--xml", target])
87 | return ET.fromstring(output)
88 |
89 |
90 | def searchinfo(xml, *args):
91 | """
92 | Search the info (acquired via the info method) for the keys given.
93 | Return the related info for each key in a dictionary.
94 | """
95 |
96 | entry = xml.find("entry")
97 |
98 | if len(args) == 0:
99 | return {}
100 |
101 | keys = {}
102 | for a in args:
103 | try:
104 | if a == "url":
105 | keys[a] = entry.find(a).text
106 | elif a in ["root", "uuid"]:
107 | repo = entry.find("repository")
108 | keys[a] = repo.find(a).text
109 | elif a in ["revision", "author", "date"]:
110 | com = entry.find("commit")
111 | if a == "revision":
112 | keys[a] = com.attrib["revision"]
113 | else:
114 | keys[a] = com.find(a).text
115 | elif a in ["token", "owner", "created", "expires"]:
116 | lk = entry.find("lock")
117 | keys[a] = lk.find(a).text
118 | except:
119 | keys[a] = None
120 | return keys
121 |
122 |
123 | def geturl(pth):
124 | """
125 | Get SVN url from file
126 | """
127 |
128 | output = info(pth)
129 | search_targets = ["url"]
130 | keys = searchinfo(output, *search_targets)
131 | return keys.get(search_targets[0])
132 |
133 |
134 | def getrevision(pth):
135 | """
136 | Get SVN revision
137 | """
138 |
139 | output = info(pth)
140 | search_targets = ["revision"]
141 | keys = searchinfo(output, *search_targets)
142 | return keys.get(search_targets[0])
143 |
144 |
145 | def diff(target, last=False):
146 | """
147 | Get SVN diff of last version
148 | """
149 |
150 | assert exists(target), "%s does not exist!" % target
151 | assert isfile(target), "%s is not a file!" % target
152 | return svnopen(['diff', '-rPREV', target]) if last else svnopen(['diff', target])
153 |
154 |
155 | def commit(pth, msg=""):
156 | """
157 | Commit changes
158 | """
159 |
160 | assert exists(pth), "%s does not exist!" % pth
161 | svnopen(["commit", pth, "-m", msg])
162 |
163 |
164 | def checklock(pth):
165 | """
166 | Check if file is locked
167 | """
168 |
169 | lock_msg = ""
170 | lock_type = NO_LOCK
171 | lock_token = None
172 | last_token = None
173 |
174 | url = geturl(pth)
175 |
176 | for obj in [pth, url]:
177 | output = info(pth)
178 | search_targets = ["owner", "created", "token"]
179 | keys = searchinfo(output, *search_targets)
180 | owner = keys.get(search_targets[0])
181 | when = keys.get(search_targets[1])
182 | lock_token = keys.get(search_targets[2])
183 |
184 | if owner is not None:
185 | msg = "SVN (checklock): %s was locked by '%s' at %s" % (pth, owner, when)
186 | if obj == pth:
187 | lock_type = ORPHAN_LOCK
188 | last_token = lock_token
189 | lock_token = None
190 | lock_msg = msg + " : BAD LOCK"
191 | elif obj == url and last_token is not None and last_token == lock_token:
192 | lock_type = LOCAL_LOCK
193 | lock_msg = msg + " : LOCAL LOCK"
194 | elif obj == url and last_token is None:
195 | lock_type = REMOTE_LOCK
196 | lock_msg = msg + " : REMOTE LOCK"
197 |
198 | return lock_msg, lock_type
199 |
200 |
201 | def lock(pth):
202 | """
203 | Lock file
204 | """
205 |
206 | assert exists(pth), "%s does not exist!" % pth
207 | svnopen(['lock', pth])
208 |
209 |
210 | def breaklock(pth, force=False):
211 | """
212 | Breack file lock
213 | """
214 |
215 | assert exists(pth), "%s does not exist!" % pth
216 |
217 | args = ['unlock']
218 | if force:
219 | args += ["--force", pth]
220 | else:
221 | args.append(pth)
222 |
223 | svnopen(args)
224 |
225 |
226 | def checkout(url, pth):
227 | """
228 | Checkout SVN url
229 | """
230 |
231 | svnopen(['checkout', url, pth])
232 | assert exists(pth)
233 |
234 |
235 | def update(pth):
236 | """
237 | Update SVN directory
238 | """
239 |
240 | assert exists(pth), "%s does not exist!" % pth
241 | svnopen(['update', pth])
242 |
243 |
244 | def export(url, name, rev=None):
245 | """
246 | Export file
247 | """
248 |
249 | args = ["export"]
250 | if rev is not None:
251 | args.append("-r%s" % str(rev))
252 | args += [url, name]
253 |
254 | svnopen(args)
255 | assert exists(name), "%s appears to not have been exported!" % name
256 |
257 |
258 | def add(pth):
259 | """
260 | Add a file
261 | """
262 |
263 | assert exists(pth), "%s does not exist!" % pth
264 | svnopen(['add', pth])
265 |
266 |
267 | def cleanup(pth):
268 | """
269 | Clean up a folder
270 | """
271 |
272 | assert exists(pth), "%s does not exist!" % pth
273 | svnopen(['cleanup', pth])
274 |
275 |
276 | def status(pth, ignore_externals=False, ignore_unversioned=False, depth="infinity"):
277 | """
278 | Get the SVN status for the folder
279 | """
280 |
281 | assert exists(pth), "%s does not exist!" % pth
282 |
283 | attributes = {
284 | "added": [],
285 | "conflicted": [],
286 | "deleted": [],
287 | "external": [],
288 | "ignored": [],
289 | "incomplete": [],
290 | "merged": [],
291 | "missing": [],
292 | "modified": [],
293 | "none": [],
294 | "normal": [],
295 | "obstructed": [],
296 | "replaced": [],
297 | "unversioned": []
298 | }
299 |
300 | args = ['status', '--xml', '--depth', depth]
301 | if ignore_externals:
302 | args.append('--ignore-externals')
303 |
304 | args.append(pth)
305 |
306 | output = svnopen(args)
307 | root = ET.fromstring(output)
308 |
309 | target = root.find("target")
310 | entries = target.findall("entry")
311 | if len(entries):
312 | for entry in entries:
313 | s = entry.find("wc-status")
314 | item = s.attrib["item"]
315 | if ignore_unversioned and item == "unversioned":
316 | continue
317 | if ignore_externals and item == "external":
318 | continue
319 | if item in attributes:
320 | attributes[item].append(entry.attrib["path"])
321 | elif not ignore_unversioned and re.search(r"svn: warning: .* is not a working copy", target.text.lstrip()) is not None:
322 | attributes["unversioned"].append(target.attrib["path"])
323 |
324 | return attributes
325 |
326 |
327 | def is_versioned(target):
328 | """
329 | Check if file/folder is versioned
330 | """
331 |
332 | assert exists(target), "%s does not exist!" % target
333 |
334 | versioned = False
335 | try:
336 | entries = status(target, depth="empty")
337 | if len(entries["unversioned"]) == 0:
338 | versioned = True
339 | except:
340 | pass
341 |
342 | return versioned
343 |
344 |
345 | def version():
346 | """
347 | Get SVN app version
348 | """
349 |
350 | version = None
351 | output = svnopen(['--version'])
352 | m = re.search(br" version (\d+\.\d+\.\d+) ", output)
353 | if m is not None:
354 | version = m.group(1).decode('utf-8')
355 | return version
356 |
357 |
358 | def set_svn_path(pth):
359 | """
360 | Set SVN path
361 | """
362 |
363 | global _svn_path
364 | _svn_path = pth
365 |
--------------------------------------------------------------------------------