-
Notifications
You must be signed in to change notification settings - Fork 1
/
qpmenu.ahk
257 lines (209 loc) · 6.24 KB
/
qpmenu.ahk
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
; qpmenu - version 0.1.0
prepare:
; config
SetWorkingDir %A_ScriptDir%
CoordMode Menu, Screen
CoordMode Mouse, Screen
; consts
BASEDIR = %A_ScriptDir%
SP = \
EMBED_COUNTER_DELIM := " | "
EMPTY_STRING_DISPLAY := "<Empty>"
SYSCMD_PARENT_DIR := 1
; includes
#Include %A_ScriptDir%\libargument.ahk
#Include %A_ScriptDir%\config.ahk
; parsing_commandline_arguments
args := parse_arguments()
main:
list_fullpath := Object()
menuname_root := "menuname_root"
search_dir := determin_search_dir_root(args)
counter := new Counter()
;ahk は 1-origin なので +1
counter.plus()
try {
Menu, %menuname_root%, DeleteAll
} catch e {
; 初回時はメニューが存在せずしくじるので吸収する.
}
; prepend system command.
counter.plus()
list_fullpath.Push("DUMMY DATA")
Menu, %menuname_root%, Add, <<Parent Directory(&\)>> %EMBED_COUNTER_DELIM% 1, label_open
; prepare data for folder exclusion
exclude_folderlist := create_exclude_folderlist()
parse(menuname_root, list_fullpath, counter, search_dir)
posobj := determin_showpos(args)
showx := posobj.x
showy := posobj.y
Menu, %menuname_root%, Show, %showx%, %showy%
Return
label_open:
selected_idx := get_menuitem_index_from_menuname(A_ThisMenuItem, EMBED_COUNTER_DELIM)
; goto が関数内から使えないので, 汚いがここに書く...
if(selected_idx==SYSCMD_PARENT_DIR){
SplitPath, search_dir, , parent_of_search_dir
; 辿る先は determin_search_dir_root() で決めているので
; これに沿うようなやり方(argsを直接いじる)で更新する.
args.unclassified[1] := parent_of_search_dir
goto, main
}
selected_fullpath := list_fullpath[selected_idx]
open_method(selected_fullpath)
Return
funcs:
Return
open_method(fullpath){
s := GetKeyState("Shift")
a := GetKeyState("Alt")
c := GetKeyState("Control")
w := GetKeyState("LWin")
if(s && !a && !c && !w){
editor_path := CONFIG.TEXT_EDITOR_PATH
Run, "%editor_path%" "%fullpath%"
Return
}
if(!s && !a && c && !w){
SplitPath, fullpath, , dirname
Run, %dirname%
Return
}
; open normally.
Run, %fullpath%
}
; @param args A object parse_arguments() returns.
determin_showpos(args){
; デフォはマウス座標で, 他に指定があればそっちを使う感じに.
pos := get_mouse_pos()
if(is_not_empty_argument(args.x)){
pos.x := args.x
}
if(is_not_empty_argument(args.y)){
pos.y := args.y
}
Return pos
}
; @param args A object parse_arguments() returns.
determin_search_dir_root(args){
if(args.unclassified.Length() == 0){
Return %A_WorkingDir%
}
Return args.unclassified[1]
}
; Config の ".git;node_modules;..."
; ↓
; Array [".git", "node_modules", ...] ← これを作る.
;
; parse 関数内から使うなら Array しか手段が無い.
; Q1: parse 関数内で split するのは?
; → 処理時間かかるのでダメ(先に計算しておきたい)
; Q2: split 後の var1 var2 ... varN 形式を使うのは?
; → global var1, global var2, ... しか使えないのでダメ.
create_exclude_folderlist(){
config_exclude_foldername := CONFIG.EXCLUDE_FOLDERNAME
StringSplit, exclude_folders, config_exclude_foldername, ";"
exclude_folderlist := Object()
Loop, %exclude_folders0%
{
excluder_name = exclude_folders%A_Index%
exclude_folderlist.Insert(%excluder_name%)
}
Return exclude_folderlist
}
; @param menuname A string
; @param list_fullpath A object
; @param counter A Counter instance for each item id.
; @param search_dir A string absolute directory path
parse(menuname, list_fullpath, counter, search_dir){
; グローバルな定数にアクセスするのにいちいち global するのだるいな...
global EMBED_COUNTER_DELIM
global EMPTY_STRING_DISPLAY
; exclude_folderlist は中身が変化しないので global で先に計算したものを使うように.
global exclude_folderlist
; この search_dir が持つファイル(フォルダ含む)数.
; これが 0 の場合にそのままスルーするとサブメニュー結合時にこけちゃうので
; 対処している.
filecount := 0
Loop, Files, %search_dir%\*, F
{
fullpath := BASEDIR SP A_LoopFileFullPath
filename := A_LoopFileName
dirname := A_LoopFileDir
itemname := A_LoopFileName
curcount := counter.get()
list_fullpath.Push(fullpath)
; label_open 側で選択項目を一意に特定する術がないため,
; カウンタ情報を付けておく.
Menu, %menuname%, Add, %itemname%%EMBED_COUNTER_DELIM%%curcount%, label_open
counter.plus()
filecount := filecount + 1
}
Loop, Files, %search_dir%\*, D
{
fullpath := BASEDIR SP A_LoopFileFullPath
filename := A_LoopFileName
dirname := A_LoopFileDir
itemname := A_LoopFileName
; フォルダ除外
should_this_folder_skip := false
For index,exclude_foldername in exclude_folderlist
{
if(itemname == exclude_foldername){
should_this_folder_skip := true
Continue
}
}
if(should_this_folder_skip){
Continue
}
; メニュー名が重複するとサブメニューを一意に作れないので
; 重複しないであろうフルパスを使うことにする.
; 加えて, メニュー名に記号類は含められないので省く.
new_menuname := StrReplace(fullpath, "\")
new_menuname := StrReplace(new_menuname, ":")
new_menuname := StrReplace(new_menuname, ".")
new_menuname := StrReplace(new_menuname, " ")
new_search_dir := search_dir "\" itemname
parse(new_menuname, list_fullpath, counter, new_search_dir)
Menu, %menuname%, Add, %itemname%, :%new_menuname%
filecount := filecount + 1
}
if(filecount == 0){
Menu, %menuname%, Add, %EMPTY_STRING_DISPLAY%, label_open
Menu, %menuname%, Disable, %EMPTY_STRING_DISPLAY%
Return
}
}
get_mouse_pos(){
MouseGetPos, mousex, mousey
mousepos := {}
mousepos.x := mousex
mousepos.y := mousey
Return mousepos
}
get_menuitem_index_from_menuname(menuname, delim){
ls := StrSplit(menuname, delim)
Return ls[2]
}
is_empty_argument(arg){
Return arg==""
}
is_not_empty_argument(arg){
Return arg!=""
}
classes:
Return
class Counter {
__New(){
this._v := 0
}
plus(){
curv := this._v
newv := curv + 1
this._v := newv
}
get(){
Return this._v
}
}