More work on go-to-location support
This commit is contained in:
parent
ef85d2de5f
commit
bb3b33b471
336
autocomplete.d
336
autocomplete.d
|
@ -43,11 +43,193 @@ import stupidlog;
|
||||||
AutocompleteResponse findDeclaration(const AutocompleteRequest request)
|
AutocompleteResponse findDeclaration(const AutocompleteRequest request)
|
||||||
{
|
{
|
||||||
AutocompleteResponse response;
|
AutocompleteResponse response;
|
||||||
|
LexerConfig config;
|
||||||
|
config.fileName = "stdin";
|
||||||
|
auto tokens = byToken(cast(ubyte[]) request.sourceCode, config);
|
||||||
|
const(Token)[] tokenArray = void;
|
||||||
|
try {
|
||||||
|
tokenArray = tokens.array();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.error("Could not provide autocomplete due to lexing exception: ", e.msg);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
auto sortedTokens = assumeSorted(tokenArray);
|
||||||
|
string partial;
|
||||||
|
|
||||||
|
auto beforeTokens = sortedTokens.lowerBound(cast(size_t) request.cursorPosition);
|
||||||
|
|
||||||
|
Log.info("Token at cursor: ", beforeTokens[$ - 1]);
|
||||||
|
|
||||||
|
const(Scope)* completionScope = generateAutocompleteTrees(tokenArray, "stdin");
|
||||||
|
auto expression = getExpression(beforeTokens);
|
||||||
|
|
||||||
|
writeln(expression);
|
||||||
|
|
||||||
|
const(ACSymbol)*[] symbols = getSymbolsByTokenChain(completionScope, expression,
|
||||||
|
request.cursorPosition, CompletionType.identifiers);
|
||||||
|
|
||||||
|
if (symbols.length > 0)
|
||||||
|
{
|
||||||
|
response.symbolLocation = symbols[0].location;
|
||||||
|
response.symbolFilePath = symbols[0].symbolFile;
|
||||||
|
Log.info(beforeTokens[$ - 1].value, " declared in ",
|
||||||
|
response.symbolFilePath, " at ", response.symbolLocation);
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const(ACSymbol)*[] getSymbolsByTokenChain(T)(const(Scope)* completionScope,
|
||||||
|
T tokens, size_t cursorPosition, CompletionType completionType)
|
||||||
|
{
|
||||||
|
// Find the symbol corresponding to the beginning of the chain
|
||||||
|
const(ACSymbol)*[] symbols = completionScope.getSymbolsByNameAndCursor(
|
||||||
|
tokens[0].value, cursorPosition);
|
||||||
|
if (symbols.length == 0)
|
||||||
|
{
|
||||||
|
Log.trace("Could not find declaration of ", tokens[0].value);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completionType == CompletionType.identifiers
|
||||||
|
&& symbols[0].kind == CompletionKind.memberVariableName
|
||||||
|
|| symbols[0].kind == CompletionKind.variableName
|
||||||
|
|| symbols[0].kind == CompletionKind.aliasName
|
||||||
|
|| symbols[0].kind == CompletionKind.enumMember)
|
||||||
|
{
|
||||||
|
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
||||||
|
if (symbols.length == 0)
|
||||||
|
return symbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop: for (size_t i = 1; i < tokens.length; i++)
|
||||||
|
{
|
||||||
|
TokenType open;
|
||||||
|
TokenType close;
|
||||||
|
void skip()
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
for (int depth = 1; depth > 0 && i < tokens.length; i++)
|
||||||
|
{
|
||||||
|
if (tokens[i].type == open)
|
||||||
|
depth++;
|
||||||
|
else if (tokens[i].type == close)
|
||||||
|
{
|
||||||
|
depth--;
|
||||||
|
if (depth == 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with (TokenType) switch (tokens[i].type)
|
||||||
|
{
|
||||||
|
case int_:
|
||||||
|
case uint_:
|
||||||
|
case long_:
|
||||||
|
case ulong_:
|
||||||
|
case char_:
|
||||||
|
case wchar_:
|
||||||
|
case dchar_:
|
||||||
|
case bool_:
|
||||||
|
case byte_:
|
||||||
|
case ubyte_:
|
||||||
|
case short_:
|
||||||
|
case ushort_:
|
||||||
|
case cent_:
|
||||||
|
case ucent_:
|
||||||
|
case float_:
|
||||||
|
case ifloat_:
|
||||||
|
case cfloat_:
|
||||||
|
case idouble_:
|
||||||
|
case cdouble_:
|
||||||
|
case double_:
|
||||||
|
case real_:
|
||||||
|
case ireal_:
|
||||||
|
case creal_:
|
||||||
|
case this_:
|
||||||
|
symbols = symbols[0].getPartsByName(getTokenValue(tokens[i].type));
|
||||||
|
if (symbols.length == 0)
|
||||||
|
break loop;
|
||||||
|
break;
|
||||||
|
case identifier:
|
||||||
|
Log.trace("looking for ", tokens[i].value, " in ", symbols[0].name);
|
||||||
|
symbols = symbols[0].getPartsByName(tokens[i].value);
|
||||||
|
if (symbols.length == 0)
|
||||||
|
{
|
||||||
|
Log.trace("Couldn't find it.");
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
if (symbols[0].kind == CompletionKind.variableName
|
||||||
|
|| symbols[0].kind == CompletionKind.memberVariableName
|
||||||
|
|| symbols[0].kind == CompletionKind.enumMember
|
||||||
|
|| (symbols[0].kind == CompletionKind.functionName
|
||||||
|
&& (completionType == CompletionType.identifiers
|
||||||
|
|| i + 1 < tokens.length)))
|
||||||
|
{
|
||||||
|
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
||||||
|
}
|
||||||
|
if (symbols.length == 0)
|
||||||
|
break loop;
|
||||||
|
if (symbols[0].kind == CompletionKind.aliasName
|
||||||
|
&& (completionType == CompletionType.identifiers
|
||||||
|
|| i + 1 < tokens.length))
|
||||||
|
{
|
||||||
|
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
||||||
|
}
|
||||||
|
if (symbols.length == 0)
|
||||||
|
break loop;
|
||||||
|
break;
|
||||||
|
case lParen:
|
||||||
|
open = TokenType.lParen;
|
||||||
|
close = TokenType.rParen;
|
||||||
|
skip();
|
||||||
|
break;
|
||||||
|
case lBracket:
|
||||||
|
open = TokenType.lBracket;
|
||||||
|
close = TokenType.rBracket;
|
||||||
|
if (symbols[0].qualifier == SymbolQualifier.array)
|
||||||
|
{
|
||||||
|
auto h = i;
|
||||||
|
skip();
|
||||||
|
Parser p;
|
||||||
|
p.setTokens(tokens[h .. i].array());
|
||||||
|
if (!p.isSliceExpression())
|
||||||
|
{
|
||||||
|
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (symbols[0].qualifier == SymbolQualifier.assocArray)
|
||||||
|
{
|
||||||
|
symbols = symbols[0].type is null ? [] :[symbols[0].type];
|
||||||
|
skip();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto h = i;
|
||||||
|
skip();
|
||||||
|
Parser p;
|
||||||
|
p.setTokens(tokens[h .. i].array());
|
||||||
|
const(ACSymbol)*[] overloads;
|
||||||
|
if (p.isSliceExpression())
|
||||||
|
overloads = symbols[0].getPartsByName("opSlice");
|
||||||
|
else
|
||||||
|
overloads = symbols[0].getPartsByName("opIndex");
|
||||||
|
if (overloads.length > 0)
|
||||||
|
{
|
||||||
|
symbols = overloads[0].type is null ? [] : [overloads[0].type];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case dot:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return symbols;
|
||||||
|
}
|
||||||
|
|
||||||
AutocompleteResponse complete(const AutocompleteRequest request)
|
AutocompleteResponse complete(const AutocompleteRequest request)
|
||||||
{
|
{
|
||||||
Log.info("Got a completion request");
|
Log.info("Got a completion request");
|
||||||
|
@ -210,157 +392,12 @@ void setCompletions(T)(ref AutocompleteResponse response,
|
||||||
if (tokens.length == 0)
|
if (tokens.length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Find the symbol corresponding to the beginning of the chain
|
const(ACSymbol)*[] symbols = getSymbolsByTokenChain(completionScope, tokens,
|
||||||
const(ACSymbol)*[] symbols = completionScope.getSymbolsByNameAndCursor(
|
cursorPosition, completionType);
|
||||||
tokens[0].value, cursorPosition);
|
|
||||||
if (symbols.length == 0)
|
|
||||||
{
|
|
||||||
Log.trace("Could not find declaration of ", tokens[0].value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (completionType == CompletionType.identifiers
|
if (symbols.length == 0)
|
||||||
&& symbols[0].kind == CompletionKind.memberVariableName
|
return;
|
||||||
|| symbols[0].kind == CompletionKind.variableName
|
|
||||||
|| symbols[0].kind == CompletionKind.aliasName
|
|
||||||
|| symbols[0].kind == CompletionKind.enumMember)
|
|
||||||
{
|
|
||||||
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
|
||||||
if (symbols.length == 0)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loop: for (size_t i = 1; i < tokens.length; i++)
|
|
||||||
{
|
|
||||||
TokenType open;
|
|
||||||
TokenType close;
|
|
||||||
void skip()
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
for (int depth = 1; depth > 0 && i < tokens.length; i++)
|
|
||||||
{
|
|
||||||
if (tokens[i].type == open)
|
|
||||||
depth++;
|
|
||||||
else if (tokens[i].type == close)
|
|
||||||
{
|
|
||||||
depth--;
|
|
||||||
if (depth == 0) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
with (TokenType) switch (tokens[i].type)
|
|
||||||
{
|
|
||||||
case int_:
|
|
||||||
case uint_:
|
|
||||||
case long_:
|
|
||||||
case ulong_:
|
|
||||||
case char_:
|
|
||||||
case wchar_:
|
|
||||||
case dchar_:
|
|
||||||
case bool_:
|
|
||||||
case byte_:
|
|
||||||
case ubyte_:
|
|
||||||
case short_:
|
|
||||||
case ushort_:
|
|
||||||
case cent_:
|
|
||||||
case ucent_:
|
|
||||||
case float_:
|
|
||||||
case ifloat_:
|
|
||||||
case cfloat_:
|
|
||||||
case idouble_:
|
|
||||||
case cdouble_:
|
|
||||||
case double_:
|
|
||||||
case real_:
|
|
||||||
case ireal_:
|
|
||||||
case creal_:
|
|
||||||
case this_:
|
|
||||||
symbols = symbols[0].getPartsByName(getTokenValue(tokens[i].type));
|
|
||||||
if (symbols.length == 0)
|
|
||||||
break loop;
|
|
||||||
break;
|
|
||||||
case identifier:
|
|
||||||
Log.trace("looking for ", tokens[i].value, " in ", symbols[0].name);
|
|
||||||
symbols = symbols[0].getPartsByName(tokens[i].value);
|
|
||||||
if (symbols.length == 0)
|
|
||||||
{
|
|
||||||
Log.trace("Couldn't find it.");
|
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
if (symbols[0].kind == CompletionKind.variableName
|
|
||||||
|| symbols[0].kind == CompletionKind.memberVariableName
|
|
||||||
|| symbols[0].kind == CompletionKind.enumMember
|
|
||||||
|| (symbols[0].kind == CompletionKind.functionName
|
|
||||||
&& (completionType == CompletionType.identifiers
|
|
||||||
|| i + 1 < tokens.length)))
|
|
||||||
{
|
|
||||||
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
|
||||||
}
|
|
||||||
if (symbols.length == 0)
|
|
||||||
break loop;
|
|
||||||
if (symbols[0].kind == CompletionKind.aliasName
|
|
||||||
&& (completionType == CompletionType.identifiers
|
|
||||||
|| i + 1 < tokens.length))
|
|
||||||
{
|
|
||||||
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
|
||||||
}
|
|
||||||
if (symbols.length == 0)
|
|
||||||
break loop;
|
|
||||||
break;
|
|
||||||
case lParen:
|
|
||||||
open = TokenType.lParen;
|
|
||||||
close = TokenType.rParen;
|
|
||||||
skip();
|
|
||||||
break;
|
|
||||||
case lBracket:
|
|
||||||
open = TokenType.lBracket;
|
|
||||||
close = TokenType.rBracket;
|
|
||||||
if (symbols[0].qualifier == SymbolQualifier.array)
|
|
||||||
{
|
|
||||||
auto h = i;
|
|
||||||
skip();
|
|
||||||
Parser p;
|
|
||||||
p.setTokens(tokens[h .. i].array());
|
|
||||||
if (!p.isSliceExpression())
|
|
||||||
{
|
|
||||||
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (symbols[0].qualifier == SymbolQualifier.assocArray)
|
|
||||||
{
|
|
||||||
symbols = symbols[0].type is null ? [] :[symbols[0].type];
|
|
||||||
skip();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto h = i;
|
|
||||||
skip();
|
|
||||||
Parser p;
|
|
||||||
p.setTokens(tokens[h .. i].array());
|
|
||||||
const(ACSymbol)*[] overloads;
|
|
||||||
if (p.isSliceExpression())
|
|
||||||
overloads = symbols[0].getPartsByName("opSlice");
|
|
||||||
else
|
|
||||||
overloads = symbols[0].getPartsByName("opIndex");
|
|
||||||
if (overloads.length > 0)
|
|
||||||
{
|
|
||||||
symbols = overloads[0].type is null ? [] : [overloads[0].type];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case dot:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (symbols.length == 0)
|
|
||||||
{
|
|
||||||
Log.error("Could not get completions");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (completionType == CompletionType.identifiers)
|
if (completionType == CompletionType.identifiers)
|
||||||
{
|
{
|
||||||
foreach (s; symbols[0].parts.filter!(a => a.name !is null
|
foreach (s; symbols[0].parts.filter!(a => a.name !is null
|
||||||
|
@ -403,7 +440,6 @@ void setCompletions(T)(ref AutocompleteResponse response,
|
||||||
response.completions ~= symbol.callTip;
|
response.completions ~= symbol.callTip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
T getExpression(T)(T beforeTokens)
|
T getExpression(T)(T beforeTokens)
|
||||||
|
|
59
client.d
59
client.d
|
@ -46,7 +46,7 @@ int main(string[] args)
|
||||||
{
|
{
|
||||||
getopt(args, "cursorPos|c", &cursorPos, "I", &importPaths,
|
getopt(args, "cursorPos|c", &cursorPos, "I", &importPaths,
|
||||||
"port|p", &port, "help|h", &help, "shutdown", &shutdown,
|
"port|p", &port, "help|h", &help, "shutdown", &shutdown,
|
||||||
"clearCache", &clearCache, "symbolLocation", &symbolLocation);
|
"clearCache", &clearCache, "symbolLocation|l", &symbolLocation);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -119,6 +119,7 @@ int main(string[] args)
|
||||||
request.importPaths = importPaths;
|
request.importPaths = importPaths;
|
||||||
request.sourceCode = sourceCode;
|
request.sourceCode = sourceCode;
|
||||||
request.cursorPosition = cursorPos;
|
request.cursorPosition = cursorPos;
|
||||||
|
request.kind = symbolLocation ? RequestKind.symbolLocation : RequestKind.autocomplete;
|
||||||
|
|
||||||
// Send message to server
|
// Send message to server
|
||||||
TcpSocket socket = createSocket(port);
|
TcpSocket socket = createSocket(port);
|
||||||
|
@ -128,26 +129,11 @@ int main(string[] args)
|
||||||
|
|
||||||
AutocompleteResponse response = getResponse(socket);
|
AutocompleteResponse response = getResponse(socket);
|
||||||
|
|
||||||
if (response.completions.length > 0)
|
if (symbolLocation)
|
||||||
{
|
printLocationResponse(response);
|
||||||
writeln(response.completionType);
|
else
|
||||||
auto app = appender!(string[])();
|
printCompletionResponse(response);
|
||||||
if (response.completionType == CompletionType.identifiers)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < response.completions.length; i++)
|
|
||||||
app.put(format("%s\t%s", response.completions[i], response.completionKinds[i]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (completion; response.completions)
|
|
||||||
{
|
|
||||||
app.put(completion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Deduplicate overloaded methods
|
|
||||||
foreach (line; app.data.sort.uniq)
|
|
||||||
writeln(line);
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +163,7 @@ Options:
|
||||||
--shutdown
|
--shutdown
|
||||||
Instructs the server to shut down.
|
Instructs the server to shut down.
|
||||||
|
|
||||||
--symbolLocation
|
--symbolLocation | -l
|
||||||
Get the file name and position that the symbol at the cursor location
|
Get the file name and position that the symbol at the cursor location
|
||||||
was defined.
|
was defined.
|
||||||
|
|
||||||
|
@ -225,3 +211,32 @@ AutocompleteResponse getResponse(TcpSocket socket)
|
||||||
msgpack.unpack(buffer[0..bytesReceived], response);
|
msgpack.unpack(buffer[0..bytesReceived], response);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printLocationResponse(AutocompleteResponse response)
|
||||||
|
{
|
||||||
|
writefln("%s\t%d", response.symbolFilePath, response.symbolLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printCompletionResponse(AutocompleteResponse response)
|
||||||
|
{
|
||||||
|
if (response.completions.length > 0)
|
||||||
|
{
|
||||||
|
writeln(response.completionType);
|
||||||
|
auto app = appender!(string[])();
|
||||||
|
if (response.completionType == CompletionType.identifiers)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < response.completions.length; i++)
|
||||||
|
app.put(format("%s\t%s", response.completions[i], response.completionKinds[i]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (completion; response.completions)
|
||||||
|
{
|
||||||
|
app.put(completion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Deduplicate overloaded methods
|
||||||
|
foreach (line; app.data.sort.uniq)
|
||||||
|
writeln(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -100,6 +100,25 @@ function M.cycleCalltips(delta)
|
||||||
showCurrentCallTip()
|
showCurrentCallTip()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.gotoDeclaration()
|
||||||
|
local fileName = os.tmpname()
|
||||||
|
local command = M.PATH_TO_DCD_CLIENT .. " -l -c" .. buffer.current_pos .. " > " .. fileName
|
||||||
|
local mode = "w"
|
||||||
|
if _G.WIN32 then
|
||||||
|
mode = "wb"
|
||||||
|
end
|
||||||
|
local p = io.popen(command, mode)
|
||||||
|
p:write(buffer:get_text())
|
||||||
|
p:flush()
|
||||||
|
p:close()
|
||||||
|
local tmpFile = io.open(fileName, "r")
|
||||||
|
local r = tmpFile:read("*a")
|
||||||
|
if r ~= "\n" then
|
||||||
|
-- TODO: Go to declaration
|
||||||
|
end
|
||||||
|
os.remove(fileName)
|
||||||
|
end
|
||||||
|
|
||||||
events.connect(events.CALL_TIP_CLICK, function(arrow)
|
events.connect(events.CALL_TIP_CLICK, function(arrow)
|
||||||
if buffer:get_lexer() ~= "dmd" then return end
|
if buffer:get_lexer() ~= "dmd" then return end
|
||||||
if arrow == 1 then
|
if arrow == 1 then
|
||||||
|
|
17
messages.d
17
messages.d
|
@ -93,7 +93,12 @@ enum CompletionType : string
|
||||||
* The auto-completion list consists of a listing of functions and their
|
* The auto-completion list consists of a listing of functions and their
|
||||||
* parameters.
|
* parameters.
|
||||||
*/
|
*/
|
||||||
calltips = "calltips"
|
calltips = "calltips",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response contains the location of a symbol declaration.
|
||||||
|
*/
|
||||||
|
location = "location"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,6 +159,16 @@ struct AutocompleteResponse
|
||||||
*/
|
*/
|
||||||
string completionType;
|
string completionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the file that contains the symbol.
|
||||||
|
*/
|
||||||
|
string symbolFilePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The byte offset at which the symbol is located.
|
||||||
|
*/
|
||||||
|
size_t symbolLocation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The completions
|
* The completions
|
||||||
*/
|
*/
|
||||||
|
|
7
server.d
7
server.d
|
@ -154,9 +154,12 @@ int main(string[] args)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AutocompleteResponse response = complete(request);
|
AutocompleteResponse response =
|
||||||
|
request.kind == RequestKind.autocomplete
|
||||||
|
? complete(request)
|
||||||
|
: findDeclaration(request);
|
||||||
ubyte[] responseBytes = msgpack.pack(response);
|
ubyte[] responseBytes = msgpack.pack(response);
|
||||||
assert(s.send(responseBytes) == responseBytes.length);
|
s.send(responseBytes);
|
||||||
}
|
}
|
||||||
Log.info("Request processed in ", requestWatch.peek().to!("msecs", float), " milliseconds");
|
Log.info("Request processed in ", requestWatch.peek().to!("msecs", float), " milliseconds");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue