/****************************************************************************
|
**
|
** Copyright (C) 2018 The Qt Company Ltd.
|
** Contact: https://www.qt.io/licensing/
|
**
|
** This file is part of the Qt VS Tools.
|
**
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
** Commercial License Usage
|
** Licensees holding valid commercial Qt licenses may use this file in
|
** accordance with the commercial license agreement provided with the
|
** Software or, alternatively, in accordance with the terms contained in
|
** a written agreement between you and The Qt Company. For licensing terms
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
** information use the contact form at https://www.qt.io/contact-us.
|
**
|
** GNU General Public License Usage
|
** Alternatively, this file may be used under the terms of the GNU
|
** General Public License version 3 as published by the Free Software
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
** included in the packaging of this file. Please review the following
|
** information to ensure the GNU General Public License requirements will
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
**
|
** $QT_END_LICENSE$
|
**
|
****************************************************************************/
|
|
using System;
|
using System.Collections.Generic;
|
using System.IO;
|
using System.Linq;
|
using System.Runtime.InteropServices;
|
using Microsoft.VisualStudio;
|
using Microsoft.VisualStudio.Debugger.Interop;
|
|
namespace QtVsTools.Qml.Debug.AD7
|
{
|
sealed partial class PendingBreakpoint : Disposable,
|
|
IDebugPendingBreakpoint2 // "This interface represents a breakpoint that is ready to bind
|
// to a code location."
|
{
|
static readonly string[] ValidExtensions = new string[] { ".qml", ".js" };
|
|
public QmlEngine Engine { get; private set; }
|
public IDebugBreakpointRequest2 Request { get; private set; }
|
public enum_BP_LOCATION_TYPE LocationType { get; private set; }
|
public BP_REQUEST_INFO RequestInfo { get; private set; }
|
public string FileName { get; private set; }
|
public TEXT_POSITION BeginPosition { get; private set; }
|
public TEXT_POSITION EndPosition { get; private set; }
|
public bool Enabled { get; private set; }
|
|
HashSet<Breakpoint> breakpoints;
|
|
public static PendingBreakpoint Create(QmlEngine engine, IDebugBreakpointRequest2 request)
|
{
|
var _this = new PendingBreakpoint();
|
return _this.Initialize(engine, request) ? _this : null;
|
}
|
|
private PendingBreakpoint()
|
{ }
|
|
private bool Initialize(QmlEngine engine, IDebugBreakpointRequest2 request)
|
{
|
var locationType = new enum_BP_LOCATION_TYPE[1];
|
if (request.GetLocationType(locationType) != VSConstants.S_OK)
|
return false;
|
|
var requestInfo = new BP_REQUEST_INFO[1];
|
if (request.GetRequestInfo(enum_BPREQI_FIELDS.BPREQI_ALLFIELDS, requestInfo)
|
!= VSConstants.S_OK) {
|
return false;
|
}
|
|
if (requestInfo[0].bpLocation.bpLocationType
|
!= (uint)enum_BP_LOCATION_TYPE.BPLT_CODE_FILE_LINE) {
|
return false;
|
}
|
|
var docPosition = Marshal.GetObjectForIUnknown(requestInfo[0].bpLocation.unionmember2)
|
as IDebugDocumentPosition2;
|
if (docPosition == null)
|
return false;
|
|
string fileName;
|
if (docPosition.GetFileName(out fileName) != VSConstants.S_OK)
|
return false;
|
|
if (!ValidExtensions.Where(x => string.Equals(x, Path.GetExtension(fileName))).Any())
|
return false;
|
|
TEXT_POSITION[] beginPosition = new TEXT_POSITION[1];
|
TEXT_POSITION[] endPosition = new TEXT_POSITION[1];
|
if (docPosition.GetRange(beginPosition, endPosition) != VSConstants.S_OK)
|
return false;
|
|
Engine = engine;
|
Request = request;
|
LocationType = locationType[0];
|
RequestInfo = requestInfo[0];
|
FileName = fileName;
|
BeginPosition = beginPosition[0];
|
EndPosition = endPosition[0];
|
|
breakpoints = new HashSet<Breakpoint>();
|
|
return true;
|
}
|
|
protected override void DisposeManaged()
|
{
|
foreach (var breakpoint in ThreadSafe(() => breakpoints.ToList()))
|
breakpoint.Dispose();
|
|
ThreadSafe(() => breakpoints.Clear());
|
}
|
|
public void DisposeBreakpoint(Breakpoint breakpoint)
|
{
|
ThreadSafe(() => breakpoints.Remove(breakpoint));
|
breakpoint.Dispose();
|
}
|
|
int IDebugPendingBreakpoint2.Bind()
|
{
|
foreach (var program in Engine.Programs) {
|
var breakpoint = Breakpoint.Create(this, program);
|
ThreadSafe(() => breakpoints.Add(breakpoint));
|
program.SetBreakpoint(breakpoint);
|
}
|
return VSConstants.S_OK;
|
}
|
|
int IDebugPendingBreakpoint2.Enable(int fEnable)
|
{
|
bool enable = (fEnable != 0);
|
if (Atomic(() => Enabled != enable, () => Enabled = enable)) {
|
foreach (var breakpoint in ThreadSafe(() => breakpoints.ToList()))
|
(breakpoint as IDebugBoundBreakpoint2).Enable(fEnable);
|
}
|
return VSConstants.S_OK;
|
}
|
|
int IDebugPendingBreakpoint2.Delete()
|
{
|
Engine.DisposePendingBreakpoint(this);
|
return VSConstants.S_OK;
|
}
|
|
int IDebugPendingBreakpoint2.EnumBoundBreakpoints(out IEnumDebugBoundBreakpoints2 ppEnum)
|
{
|
ppEnum = BoundBreakpointsEnum.Create(ThreadSafe(() => breakpoints.ToList()));
|
return VSConstants.S_OK;
|
}
|
|
int IDebugPendingBreakpoint2.GetState(PENDING_BP_STATE_INFO[] pState)
|
{
|
if (Disposed) {
|
pState[0].state = (enum_PENDING_BP_STATE)enum_BP_STATE.BPS_DELETED;
|
|
} else if (Enabled) {
|
pState[0].state = (enum_PENDING_BP_STATE)enum_BP_STATE.BPS_ENABLED;
|
|
} else {
|
pState[0].state = (enum_PENDING_BP_STATE)enum_BP_STATE.BPS_DISABLED;
|
}
|
|
return VSConstants.S_OK;
|
}
|
|
int IDebugPendingBreakpoint2.GetBreakpointRequest(out IDebugBreakpointRequest2 ppBPRequest)
|
{
|
ppBPRequest = Request;
|
return VSConstants.S_OK;
|
}
|
}
|
|
sealed partial class Breakpoint : Disposable, IBreakpoint,
|
|
IDebugBoundBreakpoint2, // "This interface represents a breakpoint that is bound to a
|
// code location."
|
|
IDebugBreakpointResolution2 // "This interface represents the information that describes a
|
// bound breakpoint."
|
{
|
public QmlDebugger Debugger { get; private set; }
|
public QmlEngine Engine { get; private set; }
|
public Program Program { get; private set; }
|
public PendingBreakpoint Parent { get; private set; }
|
public CodeContext CodeContext { get; private set; }
|
public bool Enabled { get; set; }
|
|
bool supressNotify;
|
|
string IBreakpoint.QrcPath
|
{
|
get
|
{
|
var qrcPath = Engine.FileSystem[Parent.FileName].QrcPath;
|
if (qrcPath == null)
|
return string.Empty;
|
if (qrcPath.StartsWith("qrc:///", StringComparison.InvariantCultureIgnoreCase))
|
qrcPath = qrcPath.Substring("qrc:///".Length);
|
return qrcPath;
|
}
|
}
|
|
uint IBreakpoint.Line
|
{
|
get { return Parent.BeginPosition.dwLine; }
|
}
|
|
public static Breakpoint Create(PendingBreakpoint parent, Program program)
|
{
|
return new Breakpoint
|
{
|
Engine = parent.Engine,
|
Parent = parent,
|
Program = program,
|
Debugger = program.Debugger,
|
Enabled = parent.Enabled,
|
CodeContext = CodeContext.Create(
|
parent.Engine,
|
program,
|
parent.FileName,
|
parent.BeginPosition.dwLine),
|
};
|
}
|
|
private Breakpoint()
|
{ }
|
|
protected override void DisposeManaged()
|
{
|
Program.ClearBreakpoint(this);
|
}
|
|
int IDebugBoundBreakpoint2.Enable(int fEnable)
|
{
|
bool enable = (fEnable != 0);
|
if (Atomic(() => Enabled != enable,
|
() => { Enabled = enable; supressNotify = true; })) {
|
|
if (enable)
|
Debugger.SetBreakpoint(this);
|
else
|
Debugger.ClearBreakpoint(this);
|
}
|
|
return VSConstants.S_OK;
|
}
|
|
int IDebugBoundBreakpoint2.Delete()
|
{
|
Parent.DisposeBreakpoint(this);
|
return VSConstants.S_OK;
|
}
|
|
void IBreakpoint.NotifySet()
|
{
|
if (!Atomic(() => supressNotify, () => supressNotify = false))
|
Program.NotifyBreakpointSet(this);
|
}
|
|
void IBreakpoint.NotifyClear()
|
{
|
if (!Atomic(() => supressNotify, () => supressNotify = false))
|
Program.NotifyBreakpointCleared(this);
|
}
|
|
void IBreakpoint.NotifyBreak()
|
{
|
Program.NotifyBreakpointHit(this);
|
}
|
|
void IBreakpoint.NotifyError(string errorMessage)
|
{
|
Program.OutputWriteLine(errorMessage);
|
}
|
|
int IDebugBoundBreakpoint2.GetPendingBreakpoint(
|
out IDebugPendingBreakpoint2 ppPendingBreakpoint)
|
{
|
ppPendingBreakpoint = Parent;
|
return VSConstants.S_OK;
|
}
|
|
int IDebugBoundBreakpoint2.GetState(enum_BP_STATE[] pState)
|
{
|
pState[0] = 0;
|
if (Disposed) {
|
pState[0] = enum_BP_STATE.BPS_DELETED;
|
|
} else if (Enabled) {
|
pState[0] = enum_BP_STATE.BPS_ENABLED;
|
|
} else {
|
pState[0] = enum_BP_STATE.BPS_DISABLED;
|
}
|
|
return VSConstants.S_OK;
|
}
|
|
|
#region //////////////////// IDebugBreakpointResolution2 //////////////////////////////////
|
|
int IDebugBoundBreakpoint2.GetBreakpointResolution(
|
out IDebugBreakpointResolution2 ppBPResolution)
|
{
|
ppBPResolution = this;
|
return VSConstants.S_OK;
|
}
|
|
int IDebugBreakpointResolution2.GetBreakpointType(enum_BP_TYPE[] pBPType)
|
{
|
pBPType[0] = enum_BP_TYPE.BPT_CODE;
|
return VSConstants.S_OK;
|
}
|
|
int IDebugBreakpointResolution2.GetResolutionInfo(
|
enum_BPRESI_FIELDS dwFields,
|
BP_RESOLUTION_INFO[] pBPResolutionInfo)
|
{
|
if ((dwFields & enum_BPRESI_FIELDS.BPRESI_BPRESLOCATION) != 0) {
|
BP_RESOLUTION_LOCATION location = new BP_RESOLUTION_LOCATION();
|
location.bpType = (uint)enum_BP_TYPE.BPT_CODE;
|
location.unionmember1
|
= Marshal.GetComInterfaceForObject(CodeContext, typeof(IDebugCodeContext2));
|
pBPResolutionInfo[0].bpResLocation = location;
|
pBPResolutionInfo[0].dwFields |= enum_BPRESI_FIELDS.BPRESI_BPRESLOCATION;
|
}
|
if ((dwFields & enum_BPRESI_FIELDS.BPRESI_PROGRAM) != 0) {
|
pBPResolutionInfo[0].pProgram = Program as IDebugProgram2;
|
pBPResolutionInfo[0].dwFields |= enum_BPRESI_FIELDS.BPRESI_PROGRAM;
|
}
|
|
return VSConstants.S_OK;
|
}
|
|
#endregion //////////////////// IDebugBreakpointResolution2 ///////////////////////////////
|
|
}
|
}
|