forked from debjan/scite-simple-version-control
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SciTE_GIT.lua
295 lines (261 loc) · 10.5 KB
/
SciTE_GIT.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
--[[
** Config Settings **
gitPath: Absolute path to 'git.exe' (can be left as 'git.exe' if Git has System %PATH% entry).
spawnerPath: Absolute path to 'spawner-ex.dll'.
tortoise: Option to run through Tortoise GUI instead of using console commands.
TortoiseGit: Absolute path to 'TortoiseGitProc.exe' (can be left as 'TortoiseGitProc.exe' if Tortoise Git has System %PATH% entry (it does by default)); required if Tortoise GUI option is enabled.
allowDestroy: Make destroy command available; as SciTE doesn't seem to allow confirmation dialogues it's recommended this is kept off.
commandNumber: Free SciTE command number slot.
--]]
git = {
-- Configuration data for use in the script.
config = {
gitPath = "git.exe",
spawnerPath = "/PATH-TO/spawner-ex.dll",
tortoise = false,
TortoiseGit = "TortoiseGitProc.exe",
allowDestroy = true,
commandNumber = 30,
},
-- Group of functions used by the init() method.
initFuncs = {
--Initialise the extension.
init = function(self)
self:setVariables()
if not self.getSpawner() then return end
ctrl.gitExists = ctrl.gitExists or self:checkGitExists(config['gitPath'])
if not ctrl.gitExists then return end
self.checkWinVer()
self.addToContext()
end,
-- Set variables to be used throughout the script.
setVariables = function(self)
scite_git = git.onContextSelect
config = git.config
session ={['control']={}, ['status']={}, ['stamp']={}}
ctrl = session.control
cmds = self.setCommands()
end,
-- Set up the strings used for Git commands.
setCommands = function()
if not config.tortoise then
cmds = {
Log = 'log --graph',
Diff = 'diff',
Add = 'add',
Commit = 'commit',
Revert = 'checkout',
Remove = 'rm --cached',
Status = 'status -u'
}
else cmds = {
Log = '/command:log /path:',
Diff = '/command:diff /path:',
Add = '/command:add /path:',
Commit = '/command:commit /path:',
Revert = '/command:revert /path:',
Remove ='/command:remove /path:',
Status = '/command:repostatus /path:'
}
end
cmds.Root = 'rev-parse --show-toplevel'
cmds.PorcStatus = 'status --porcelain -u'
return cmds
end,
--Load the spawner.
-- TODO: Possibly add user choice to continue using io.popen() if spawner not found.
getSpawner = function()
local fn, err
fn, err = package.loadlib(config.spawnerPath, 'luaopen_spawner')
if fn then fn() return true
else print('ERROR: spawner-ex.dll could not be found! Please check the path in the config section.') return false
end
end,
-- Check the Git executable exists.
checkGitExists = function(self, gitPath)
if self.checkDir(gitPath) then return true
elseif self.checkPath(gitPath) then return true
else print('ERROR: Git executable not found! Please check the path in the config section.') return false end
end,
-- Check for git.exe in the gitPath directory set in config.
checkDir = function(gitPath)
return spawner.popen(('if exist %q (echo true) else (echo false)'):format(gitPath)):read('*a'):gsub('\n', '') == 'true'
end,
-- Check for git.exe in Window's %PATH% variable.
checkPath = function(gitPath)
return spawner.popen('for %f in (' .. gitPath .. ') do @if "%~$PATH:f"=="" (echo false) else (echo true)'):read('*a'):gsub('\n', '')
end,
-- Checks Windows version.
checkWinVer = function()
if not session['ver'] and spawner then
local verString = spawner.popen('ver'):read('*a'):gsub('\n', '')
local verNum = (string.find(verString, 'Version'))+8
local verNumEnd = verNum + string.find(verString:sub(verNum), '%.')
session['ver'] = tonumber(verString:sub(verNum, verNumEnd))
end
end,
-- Add Git option to SciTE's right-click context menu.
addToContext = function()
cmdNum = config.commandNumber
context = props['user.context.menu']
gitContext = ('||%s|11%s|'):format('Git', cmdNum)
if not context:find(gitContext) then
props['user.context.menu'] = context..gitContext
props['command.' .. cmdNum .. '.*'] = 'scite_git'
props['command.mode.' .. cmdNum .. '.*'] = 'subsystem:lua'
end
end,
},
-- Group of functions used to create the User List context menu.
listFuncs = {
-- Executes when 'Git' option is selected on the context menu.
listSelect = function(self)
if ctrl[props['FilePath']] then
self:createList(ctrl[props['FilePath']])
elseif commandFuncs.gitStatus(true):sub(1,1) ~= 'f' then
ctrl[props['FilePath']] = "Git"; self:createList()
else
OnUserListSelection = function(num, cmd) return commandFuncs:initRepo(cmd) end
editor:UserListShow(3, "Init")
end
end,
-- Create the list
createList = function(self)
sessionFuncs:SetModifiedStamp()
sessionFuncs.setSessionStatus()
local allowedCmds = self.getAllowedCmds()
local cmdList = self.filterCommands(allowedCmds)
self.displayList(cmdList)
end,
-- Return allowed commands based on the results of the status query.
getAllowedCmds = function()
local cmds
local code = session.status[props['FilePath']]
if code == '?' then cmds = {1}; if config.allowDestroy then cmds[#cmds+1] = 8 end
elseif code == 'M' then cmds = {2,3,4,5,7}
elseif code == 'C' or code == " " then cmds = {4,6,7}; if config.allowDestroy then cmds[#cmds+1] = 8 end
elseif code == 'A' then cmds = {2,3,4,5,7}
elseif code == 'R' or code == 'D' then cmds = {2,4,5,7}
else cmds = {7}
end
return cmds
end,
-- Match the numbers in the allowed commands table with the commands to be listed.
filterCommands = function(allowedCmds)
local displayList = ''
local cmds = {'Add', 'Commit', 'Diff', 'Log', 'Revert', 'Remove', 'Status', 'Destroy'}
for i, v in ipairs(allowedCmds) do displayList = displayList .. ' ' .. cmds[v] end
return displayList:sub(2)
end,
-- Send the list actions to SciTe for display on the context menu.
displayList = function(cmdList)
-- TODO Check token function and what condition triggers it.
if scite_UserListShow then scite_UserListShow(self.token(cmdList), 1, commandFuncs.executeCmd)
else
OnUserListSelection = function(listNumber, selectedCmd)
return commandFuncs:executeCmd(selectedCmd) end
editor:UserListShow(6, cmdList)
end
end,
-- TODO: Not sure what function this serves yet...
token = function(cmdList)
print('TOKEN ACTIVATED')
local l = {}
for v in string.gmatch(cmdList, "%S+") do l[#l+1] = v end
return l
end,
},
-- Group of functions for Git commands.
commandFuncs = {
-- Check Git's status.
gitStatus = function(porcelain)
if porcelain == true then status = cmds['PorcStatus'] else status = cmds['Status'] end
-- TODO: Is that C at the end needed?
return spawner.popen(('cd /D %q & %q %s %q'):format(props['FileDir'], config.gitPath, status, props['FileNameExt'])):read('*a') .. 'C'
end,
-- Initialise a new repo.
initRepo = function(self, cmd)
ctrl[props['FilePath']] = cmd:gsub("Init", "")
print(spawner.popen(("cd /D %q & %q init && %q add %q && %q commit -m init"):format(props['FileDir'], config.gitPath, config.gitPath, props['FilePath'], config.gitPath)):read("*a"))
commandFuncs:executeCmd('Status')
sessionFuncs.setSessionStatus()
end,
-- Execute git command chosen from context menu.
executeCmd = function(self, cmd)
if cmd =="Destroy" then
-- TODO Maybe add a confirmation dialogue. Appears the only way to do this through the API is by having users type a confirmation string into the strip dialogue box.
self.destroy()
else
if config.tortoise then
self.tortoise(cmd)
else
scite.MenuCommand(IDM_CLEAROUTPUT)
if cmd == "Commit" then self:dialog(cmd)
else print(spawner.popen(("cd /D %q & %q %s %q"):format(props['FileDir'], config.gitPath, cmds[cmd], props['FileNameExt'])):read("*a"))
end
end
end
end,
-- Destroy a Git repo.
destroy = function()
local projectRoot = spawner.popen(('cd /D %q & %q %s'):format(props['FileDir'], config.gitPath, cmds['Root'])):read('*a'):gsub('\n', '') .. '/.' .. ctrl[props['FilePath']]:lower()
spawner.popen(('if exist %q rd /s /q %q'):format(projectRoot, projectRoot))
print('Repo destroyed')
end,
-- Take care of tortoise stuff
tortoise = function(cmd)
spawner.popen(("cd /D %q & %q %s%q"):format(props['FileDir'], config["TortoiseGit"], cmds[cmd], props['FileNameExt']))
end,
-- Enables the dialogue strip to input the commit message.
dialog = function(self, cmd)
scite.StripShow("!'" .. cmd .. "'[]((OK))(Cancel)")
function OnStrip(control, change)
if change == 1 and control == 2 then
local msg = scite.StripValue(1)
if msg:len() > 0 then
print(spawner.popen(("cd /D %q & %q %s %q -m %q && echo %s"):format(props['FileDir'], config.gitPath, cmds[cmd], props['FileNameExt'], msg, msg)):read("*a"))
sessionFuncs.setSessionStatus()
end
scite.StripShow("")
end
end
end
},
-- Group of session functions.
sessionFuncs = {
-- Set a timestamp of the files' modification dates and times into the session.
SetModifiedStamp = function(self)
if props['FileDir'] ~= '' then
if session['ver'] > 5 then
local dateTime = spawner.popen('forfiles /p "'..props['FileDir']..'" /m '..props['FileNameExt']..' /c "cmd /c echo @fdate @ftime"'):read('*a'):gsub('\n', '')
if session.stamp[props['FilePath']] ~= dateTime then
session.stamp[props['FilePath']] = dateTime
end
self.setSessionStatus()
else self.setSessionStatus() end
end
end,
-- Set the current Git status in the session.
setSessionStatus = function()
session.status[props['FilePath']] = (commandFuncs.gitStatus(true):gsub(' ','')):sub(1,1)
if session.status[props['FilePath']]:gsub("f", "a") == "a" then session.status[props['FilePath']], ctrl[props['FilePath']] = nil end
end
},
-- Set up function groups from each table of functions.
setFuncs = function()
initFuncs = setmetatable(git.initFuncs, git.initFuncs)
listFuncs = setmetatable(git.listFuncs, git.listFuncs)
sessionFuncs = setmetatable(git.sessionFuncs, git.sessionFuncs)
commandFuncs = setmetatable(git.commandFuncs, git.commandFuncs)
end,
-- Sequence the master functions.
sequence = function()
git.setFuncs()
initFuncs:init()
end,
-- Initialise the context menu list on selection.
onContextSelect = function()
listFuncs:listSelect()
end
}
git.sequence()