implement function breakpoints

This commit is contained in:
Will Portnoy 2019-04-26 11:44:26 -07:00
Родитель d6e8b1c2fe
Коммит 6f5bd46f8a
5 изменённых файлов: 186 добавлений и 50 удалений

Просмотреть файл

@ -308,6 +308,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
supportsConfigurationDoneRequest = true,
supportsSetVariable = true,
supportsEvaluateForHovers = true,
supportsFunctionBreakpoints = true,
supportTerminateDebuggee = this.terminate != null,
supportsTerminateRequest = this.terminate != null,
};
@ -341,6 +342,22 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
return Protocol.Response.From(NextSeq, setBreakpoints, new { breakpoints });
}
else if (message is Protocol.Request<Protocol.SetFunctionBreakpoints> setFunctionBreakpoints)
{
var arguments = setFunctionBreakpoints.arguments;
await OutputAsync($"Set function breakpoints.", null, cancellationToken).ConfigureAwait(false);
var breakpoints = this.breakpoints.SetBreakpoints(arguments.breakpoints);
foreach (var breakpoint in breakpoints)
{
if (breakpoint.verified)
{
var item = this.breakpoints.ItemFor(breakpoint);
await OutputAsync($"Set breakpoint at {codeModel.NameFor(item)}", item, cancellationToken).ConfigureAwait(false);
}
}
return Protocol.Response.From(NextSeq, setFunctionBreakpoints, new { breakpoints });
}
else if (message is Protocol.Request<Protocol.Threads> threads)
{
var body = new

Просмотреть файл

@ -22,7 +22,8 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
/// <returns></returns>
public static BotAdapter UseDebugger(this BotAdapter botAdapter, int port, Source.IRegistry registry = null, IBreakpoints breakpoints = null, Action terminate = null, ICodeModel codeModel = null, IDataModel dataModel = null, ILogger logger = null, ICoercion coercion = null)
{
var sourceMap = new SourceMap();
codeModel = codeModel ?? new CodeModel();
var sourceMap = new SourceMap(codeModel);
DebugSupport.SourceRegistry = registry ?? sourceMap;
return botAdapter.Use(new DebugAdapter(port: port,
registry: registry ?? sourceMap,

Просмотреть файл

@ -122,6 +122,17 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<KeyValuePair<ulong, T>>)this).GetEnumerator();
public IEnumerable<T> Items
{
get
{
lock (gate)
{
return this.itemByCode.Values.ToArray();
}
}
}
public T this[ulong code]
{
get

Просмотреть файл

@ -241,6 +241,8 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
case "attach": return token.ToObject<Request<Attach>>();
case "initialize": return token.ToObject<Request<Initialize>>();
case "setBreakpoints": return token.ToObject<Request<SetBreakpoints>>();
case "setFunctionBreakpoints": return token.ToObject<Request<SetFunctionBreakpoints>>();
case "configurationDone": return token.ToObject<Request<ConfigurationDone>>();
case "threads": return token.ToObject<Request<Threads>>();
case "stackTrace": return token.ToObject<Request<StackTrace>>();
case "scopes": return token.ToObject<Request<Scopes>>();
@ -252,10 +254,8 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
case "next": return token.ToObject<Request<Next>>();
case "stepIn": return token.ToObject<Request<Next>>();
case "stepOut": return token.ToObject<Request<Next>>();
case "configurationDone": return token.ToObject<Request<ConfigurationDone>>();
case "terminate": return token.ToObject<Request<Terminate>>();
case "disconnect": return token.ToObject<Request<Disconnect>>();
case "setFunctionBreakpoints":
case "setExceptionBreakpoints":
default: return token.ToObject<Request>();
}

Просмотреть файл

@ -11,15 +11,22 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
bool IsBreakPoint(object item);
object ItemFor(Protocol.Breakpoint breakpoint);
IReadOnlyList<Protocol.Breakpoint> SetBreakpoints(Protocol.Source source, IReadOnlyList<Protocol.SourceBreakpoint> sourceBreakpoints);
IReadOnlyList<Protocol.Breakpoint> SetBreakpoints(IReadOnlyList<Protocol.FunctionBreakpoint> functionBreakpoints);
IReadOnlyList<Protocol.Breakpoint> ApplyUpdates();
}
public sealed class SourceMap : Source.IRegistry, IBreakpoints
{
private readonly ICodeModel codeModel;
private readonly object gate = new object();
private readonly Dictionary<object, Source.Range> sourceByItem = new Dictionary<object, Source.Range>(ReferenceEquality<object>.Instance);
private bool dirty = true;
public SourceMap(ICodeModel codeModel)
{
this.codeModel = codeModel ?? throw new ArgumentNullException(nameof(codeModel));
}
void Source.IRegistry.Add(object item, Source.Range range)
{
if (!Path.IsPathRooted(range.Path))
@ -52,15 +59,19 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
public sealed class Row
{
public Row(Protocol.Source source, Protocol.SourceBreakpoint sourceBreakpoint, Protocol.Breakpoint breakpoint)
public Row(Protocol.Source source, Protocol.SourceBreakpoint sourceBreakpoint)
{
Source = source;
SourceBreakpoint = sourceBreakpoint;
Breakpoint = breakpoint;
}
public Row(Protocol.FunctionBreakpoint functionBreakpoint)
{
FunctionBreakpoint = functionBreakpoint;
}
public Protocol.Source Source { get; }
public Protocol.SourceBreakpoint SourceBreakpoint { get; }
public Protocol.Breakpoint Breakpoint { get; }
public Protocol.FunctionBreakpoint FunctionBreakpoint { get; }
public Protocol.Breakpoint Breakpoint { get; } = new Protocol.Breakpoint();
public object item { get; set; }
}
@ -72,54 +83,120 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
private static bool PathEquals(string one, string two) =>
string.Equals(one, two, StringComparison.CurrentCultureIgnoreCase);
private IReadOnlyList<Protocol.Breakpoint> Update()
public static bool Equals(Protocol.Breakpoint breakpoint, Source.Range range) =>
(breakpoint.source == null && range == null)
|| (breakpoint.source.path == range.Path
&& breakpoint.line == range.Start.LineIndex
&& breakpoint.endLine == range.After.LineIndex
&& breakpoint.column == range.Start.CharIndex
&& breakpoint.endColumn == range.After.CharIndex);
public static void Assign(Protocol.Breakpoint breakpoint, Source.Range range)
{
if (range != null)
{
breakpoint.verified = true;
breakpoint.source = new Protocol.Source(range.Path);
breakpoint.line = range.Start.LineIndex;
breakpoint.endLine = range.After.LineIndex;
breakpoint.column = range.Start.CharIndex;
breakpoint.endColumn = range.After.CharIndex;
}
else
{
breakpoint.verified = false;
breakpoint.source = null;
breakpoint.line = null;
breakpoint.endLine = null;
breakpoint.column = null;
breakpoint.endColumn = null;
}
}
private bool TryUpdate(Row row, KeyValuePair<object, Source.Range> sourceItem)
{
var item = sourceItem.Key;
var source = sourceItem.Value;
if (object.Equals(row.item, item) && Equals(row.Breakpoint, source))
{
return false;
}
row.item = item;
Assign(row.Breakpoint, source);
return true;
}
private bool TryUpdate(Row row)
{
var breakpoint = row.Breakpoint;
if (breakpoint.id == 0)
{
breakpoint.id = this.rows.Add(row);
}
IEnumerable<KeyValuePair<object, Source.Range>> options;
var functionBreakpoint = row.FunctionBreakpoint;
if (functionBreakpoint != null)
{
options = from sourceItem in sourceByItem
let item = sourceItem.Key
let name = codeModel.NameFor(item)
where name.IndexOf(functionBreakpoint.name, StringComparison.CurrentCultureIgnoreCase) >= 0
orderby name.Length
select sourceItem;
}
else
{
options = from sourceItem in sourceByItem
let source = sourceItem.Value
where PathEquals(source.Path, row.Source.path)
where source.Start.LineIndex >= row.SourceBreakpoint.line
let distance = Math.Abs(source.Start.LineIndex - row.SourceBreakpoint.line)
orderby distance
select sourceItem;
}
options = options.ToArray();
var best = options.FirstOrDefault();
return TryUpdate(row, best);
}
private void RebuildItems()
{
lock (gate)
{
items.Clear();
var changes = new List<Protocol.Breakpoint>();
foreach (var kv in rows)
foreach (var row in rows.Items)
{
var row = kv.Value;
var item = row.item;
if (item != null)
{
items.Add(item);
}
}
}
}
var options = from sourceItem in sourceByItem
let source = sourceItem.Value
where PathEquals(source.Path, row.Source.path)
where source.Start.LineIndex >= row.SourceBreakpoint.line
let distance = Math.Abs(source.Start.LineIndex - row.SourceBreakpoint.line)
orderby distance
select sourceItem;
private IReadOnlyList<Protocol.Breakpoint> Update()
{
lock (gate)
{
var changes = new List<Protocol.Breakpoint>();
options = options.ToArray();
var best = options.FirstOrDefault();
var itemNew = best.Key;
var verifiedNew = itemNew != null;
var lineNew = verifiedNew
? best.Value.Start.LineIndex
: row.SourceBreakpoint.line;
var itemOld = row.item;
var verifiedOld = row.Breakpoint.verified;
var lineOld = row.Breakpoint.line;
var changed = itemNew != itemOld
|| verifiedNew != verifiedOld
|| lineNew != lineOld;
if (changed)
foreach (var row in rows.Items)
{
if (TryUpdate(row))
{
changes.Add(row.Breakpoint);
row.item = itemNew;
row.Breakpoint.verified = verifiedNew;
row.Breakpoint.line = lineNew;
}
}
if (itemNew != null)
{
items.Add(itemNew);
}
if (changes.Count > 0)
{
RebuildItems();
}
return changes;
@ -131,23 +208,53 @@ namespace Microsoft.Bot.Builder.Dialogs.Debugging
lock (gate)
{
var path = source.path;
foreach (var kv in rows)
foreach (var row in rows.Items)
{
var row = kv.Value;
if (PathEquals(row.Source.path, path))
if (row.FunctionBreakpoint == null && PathEquals(row.Source.path, path))
{
rows.Remove(row);
}
}
var breakpoints = new List<Protocol.Breakpoint>(sourceBreakpoints.Count);
foreach (var sourceBreakpoint in sourceBreakpoints)
{
var breakpoint = new Protocol.Breakpoint() { source = source };
var row = new Row(source, sourceBreakpoint, breakpoint);
breakpoint.id = this.rows.Add(row);
var row = new Row(source, sourceBreakpoint);
TryUpdate(row);
breakpoints.Add(row.Breakpoint);
}
return Update();
RebuildItems();
return breakpoints;
}
}
IReadOnlyList<Protocol.Breakpoint> IBreakpoints.SetBreakpoints(IReadOnlyList<Protocol.FunctionBreakpoint> functionBreakpoints)
{
lock (gate)
{
foreach (var row in rows.Items)
{
if (row.FunctionBreakpoint != null)
{
rows.Remove(row);
}
}
var breakpoints = new List<Protocol.Breakpoint>(functionBreakpoints.Count);
foreach (var functionBreakpoint in functionBreakpoints)
{
var row = new Row(functionBreakpoint);
TryUpdate(row);
breakpoints.Add(row.Breakpoint);
}
RebuildItems();
return breakpoints;
}
}