-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcommand.d
More file actions
270 lines (218 loc) · 7.61 KB
/
command.d
File metadata and controls
270 lines (218 loc) · 7.61 KB
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
module extensionapi.command;
import dccore.attr : hasAttribute, getAttributes, isType, isNotType;
import extensionapi.common : Application, BufferView, TextEditor, MenuItem, Shortcut, Log, CompletionEntry, CommandParameter, CommandCall, Hints, Fiber;
import dccore.command;
import std.meta : AliasSeq, anySatisfy, Filter, Replace, staticIndexOf, staticMap;
import std.traits : FieldNameTuple, isSomeFunction, Identity, ParameterIdentifierTuple, ParameterTypeTuple;
import poodinis : Autowire, DependencyContainer;
import poodinis.container : existingInstance;
private
{
static shared(DependencyContainer) g_CommandsContainer;
@property shared(DependencyContainer) commandsContainer()
{
if (g_CommandsContainer is null)
g_CommandsContainer = new DependencyContainer();
return g_CommandsContainer;
}
struct WrappedCommandInfo
{
MenuItem menuItem;
Shortcut[] shortcuts;
Hints hints;
}
static WrappedCommandInfo[TypeInfo] g_WrappedCommandInfo;
}
/** Attribute to specify a that a command should be run in a fiber
In a module that has a "mixin registerCommands":
A class derived from class Command or a public function use the @InFiber attribute to force
the command to be run in a fiber.
Another way to force running in a fiber is by setting one of the first parameters of the
command to be of type Fiber. This will run the command in a fiber at pass the fiber as
argument.
Example:
@InFiber
class SayHelloCommand : Command
{
this() { super(createParams("")); }
void run(Log log, string txt)
{
log.info(txt);
}
}
Example:
@InFiber
void textUppercase(Application app, string dummy)
{
app.currentBuffer.map!(std.uni.toUpper)(RegionQuery.selectionOrWord);
}
Example:
void textUppercase(Fiber fiber, Application app, string dummy)
{
// The fiber parameter is automatically provided to the function
// and the command is run in that fiber.
app.currentBuffer.map!(std.uni.toUpper)(RegionQuery.selectionOrWord);
}
*/
struct InFiber
{
}
/** Attribute to Register a free function as a Command
This will create a new FunctionCommand!Func that wraps the function. The Command.execute will
inspect the function parameter types and extract values of those types at runtime from the
Command.execute arguments. Then it will call the free function with the arguments.
In case the free function needs context information such as active BufferView instance or Application instance
it can get that by setting the first parameter to the type of context it needs. Supported contexts are:
* BufferView = the active buffer view currently having keyboard focus or null
* Application = the application instance
* Widget = the widget that currently has focus
* Context = A struct with all of the above.
*/
struct RegisterFunctionCommand(alias Func)
{
alias Function = Func;
alias FC = ExtensionCommandWrap!(Func, Command);
}
struct RegisterClassCommand(alias Cls)
{
alias FC = ExtensionCommandWrap!(Cls, Cls);
}
class Foo {}
Exception[] initCommands(InitCommandFunc)(Application app, InitCommandFunc f)
{
import std.range;
Exception[] exceptions;
commandsContainer.register!Application.existingInstance(app);
commandsContainer.register!Log.existingInstance(dccore.log.log);
Command[] commands = commandsContainer().resolveAll!Command;
foreach (c; commands)
{
try
{
TypeInfo ti = typeid(c);
WrappedCommandInfo* cmdInfo = ti in g_WrappedCommandInfo;
f(c, cmdInfo.menuItem, cmdInfo.shortcuts, cmdInfo.hints);
}
catch (Exception e)
exceptions ~= e;
}
return exceptions;
}
void finiCommands(Application app)
{
Command[] commands = app.commandManager.commands.values;
foreach (c; commands)
c.onUnloaded();
}
// Wrapper for a function or a class derived from Command where it will
// automatically inject needed values to either the function or a .run method on the class instance
// when executed through e.g. the command manager.
class ExtensionCommandWrap(alias AttributeHolder, Base) : Base
{
static if ( ! anySatisfy!(isType!Application, FieldNameTuple!Base) )
{
@Autowire
Application app;
}
alias InjectedTypes = AliasSeq!(Application, TextEditor, BufferView, Fiber, Log);
alias InjectedObjects = AliasSeq!(app, currentTextEditor, currentBuffer, Fiber.getThis, dccore.log.log);
static if ( isSomeFunction!AttributeHolder )
alias Func = AttributeHolder;
else
alias Func = run;
static if (hasAttribute!(AttributeHolder, InFiber) || anySatisfy!(isType!Fiber, ParameterTypeTuple!Func))
override bool mustRunInFiber() const pure nothrow @safe
{
return true;
}
static this()
{
alias WrappedType = ExtensionCommandWrap!(AttributeHolder, Base);
commandsContainer.register!(Command, WrappedType)();
WrappedCommandInfo info;
static if (hasAttribute!(AttributeHolder,MenuItem))
info.menuItem = getAttributes!(AttributeHolder, MenuItem)[0];
static if (hasAttribute!(AttributeHolder, Shortcut))
info.shortcuts = getAttributes!(AttributeHolder, Shortcut);
static if (hasAttribute!(AttributeHolder, Hints))
info.hints = getAttributes!(AttributeHolder, Hints)[0];
TypeInfo wrappedTypeInfo = typeid(WrappedType);
g_WrappedCommandInfo[wrappedTypeInfo] = info;
}
this()
{
enum getDefaultValue(U) = U.init;
alias p1 = Filter!(isNotType!InjectedTypes, ParameterTypeTuple!Func);
alias p2 = staticMap!(getDefaultValue, p1);
enum names = [ParameterIdentifierTuple!Func];
setCommandParameterDefinitions(createParams(names, p2));
}
private BufferView currentBuffer() { return app.getCurrentBuffer(); }
private TextEditor currentTextEditor() { return app.getCurrentTextEditor(); }
private auto call(alias F)(CommandParameter[] v)
{
enum count = Filter!(isType!InjectedTypes, ParameterTypeTuple!F).length;
enum nonInjectedArgsCount = ParameterTypeTuple!F.length - count;
template _replaceWithObject(T)
{
enum idx = staticIndexOf!(T, InjectedTypes);
static if (idx == -1)
{
alias _replaceWithObject = T; // Just put something there. It will not be used.
}
else
{
alias _replaceWithObject = InjectedObjects[idx];
}
}
alias t5 = staticMap!(_replaceWithObject, ParameterTypeTuple!F);
alias injectedArgs = t5[0..count];
// Save current active buffer since current buffer may be changed by the command
static if ( anySatisfy!(isType!(BufferView, TextEditor), ParameterTypeTuple!F) )
{
auto bv = app.getCurrentBuffer();
bv.beginUndoGroup();
scope (exit) bv.endUndoGroup();
}
alias parameterType(int idx) = ParameterTypeTuple!F[$-nonInjectedArgsCount+idx];
static string _setupArgs(int count)
{
import std.conv;
string res;
string delim = ",";
foreach (i; 0..count)
{
res ~= delim ~ "v[" ~ i.to!string ~ "].get!(parameterType!" ~ i.to!string ~ ")";
delim = ",";
}
return res;
}
assert(v.length >= nonInjectedArgsCount);
// Mixin magic to simply provide injected args and use v[0].get!parameterType!0 etc. for the rest or the args
mixin("return F(injectedArgs" ~ _setupArgs(nonInjectedArgsCount) ~ ");");
}
override void execute(CommandParameter[] v)
{
call!Func(v);
}
static if (__traits(hasMember, Base, "complete") && isSomeFunction!(Base.complete))
{
override CompletionEntry[] getCompletions(CommandParameter[] v)
{
return call!complete(v);
}
}
}
void registerCommandKeyBindings(Application app)
{
Command[] commands = app.commandManager.commands.values;
foreach (c; commands)
{
TypeInfo ti = typeid(c);
TypeInfo_Class tic = cast(TypeInfo_Class) ti;
string name = tic.name;
WrappedCommandInfo* cmdInfo = ti in g_WrappedCommandInfo;
if (cmdInfo !is null)
app.addCommandShortcuts(c.name, cmdInfo.shortcuts);
}
}