You're breathtaking!
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SystemDefaultCodeEditor.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
|
||||
CodeEditorTypes SystemDefaultCodeEditor::GetType() const
|
||||
{
|
||||
return CodeEditorTypes::SystemDefault;
|
||||
}
|
||||
|
||||
String SystemDefaultCodeEditor::GetName() const
|
||||
{
|
||||
return TEXT("System Default");
|
||||
}
|
||||
|
||||
void SystemDefaultCodeEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
Platform::StartProcess(path, StringView::Empty, StringView::Empty);
|
||||
}
|
||||
|
||||
void SystemDefaultCodeEditor::OpenSolution()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Editor/Scripting/CodeEditor.h"
|
||||
|
||||
/// <summary>
|
||||
/// Basic implementation for editing source code on a target platform.
|
||||
/// </summary>
|
||||
class SystemDefaultCodeEditor : public CodeEditor
|
||||
{
|
||||
public:
|
||||
|
||||
// [CodeEditor]
|
||||
CodeEditorTypes GetType() const override;
|
||||
String GetName() const override;
|
||||
void OpenFile(const String& path, int32 line) override;
|
||||
void OpenSolution() override;
|
||||
};
|
||||
@@ -0,0 +1,796 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
|
||||
// Import EnvDTE
|
||||
#pragma warning(disable : 4278)
|
||||
#pragma warning(disable : 4146)
|
||||
#import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("8.0") lcid("0") raw_interfaces_only named_guids
|
||||
#pragma warning(default : 4146)
|
||||
#pragma warning(default : 4278)
|
||||
|
||||
// Import Microsoft.VisualStudio.Setup.Configuration.Native
|
||||
#include <Microsoft.VisualStudio.Setup.Configuration.Native/Setup.Configuration.h>
|
||||
#pragma comment(lib, "Microsoft.VisualStudio.Setup.Configuration.Native.lib")
|
||||
|
||||
#include "VisualStudioConnection.h"
|
||||
#include "Engine/Platform/Windows/ComPtr.h"
|
||||
|
||||
/// <summary>
|
||||
/// Handles retrying of calls that fail to access Visual Studio.
|
||||
/// This is due to the weird nature of VS when calling its methods from external code.
|
||||
/// If this message filter isn't registered some calls will just fail silently.
|
||||
/// </summary>
|
||||
class VSMessageFilter : public IMessageFilter
|
||||
{
|
||||
private:
|
||||
|
||||
LONG _refCount;
|
||||
|
||||
public:
|
||||
|
||||
VSMessageFilter()
|
||||
{
|
||||
_refCount = 0;
|
||||
}
|
||||
|
||||
virtual ~VSMessageFilter()
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
DWORD __stdcall HandleInComingCall(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo) override
|
||||
{
|
||||
return SERVERCALL_ISHANDLED;
|
||||
}
|
||||
|
||||
DWORD __stdcall RetryRejectedCall(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType) override
|
||||
{
|
||||
if (dwRejectType == SERVERCALL_RETRYLATER)
|
||||
{
|
||||
// Retry immediatey
|
||||
return 99;
|
||||
}
|
||||
|
||||
// Cancel the call
|
||||
return -1;
|
||||
}
|
||||
|
||||
DWORD __stdcall MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType) override
|
||||
{
|
||||
return PENDINGMSG_WAITDEFPROCESS;
|
||||
}
|
||||
|
||||
// COM requirement. Returns instance of an interface of provided type.
|
||||
HRESULT __stdcall QueryInterface(REFIID iid, void** ppvObject) override
|
||||
{
|
||||
if (iid == IID_IDropTarget || iid == IID_IUnknown)
|
||||
{
|
||||
AddRef();
|
||||
*ppvObject = this;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
*ppvObject = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
// COM requirement. Increments objects reference count.
|
||||
ULONG __stdcall AddRef() override
|
||||
{
|
||||
return _InterlockedIncrement(&_refCount);
|
||||
}
|
||||
|
||||
// COM requirement. Decreases the objects reference count and deletes the object if its zero.
|
||||
ULONG __stdcall Release() override
|
||||
{
|
||||
LONG count = _InterlockedDecrement(&_refCount);
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper macro
|
||||
#define CHECK_VS_RESULT(target) if (FAILED(result)) return #target " failed with result: " + std::to_string((int)result);
|
||||
|
||||
#define USE_ITEM_OPERATIONS_OPEN 0
|
||||
#define USE_PROJECT_ITEM_OPEN 1
|
||||
#define USE_DOCUMENT_OPEN 0
|
||||
|
||||
class LocalBSTR
|
||||
{
|
||||
public:
|
||||
|
||||
BSTR Str;
|
||||
|
||||
public:
|
||||
|
||||
LocalBSTR()
|
||||
{
|
||||
Str = nullptr;
|
||||
}
|
||||
|
||||
LocalBSTR(const LPSTR str)
|
||||
{
|
||||
const auto wslen = MultiByteToWideChar(CP_ACP, 0, str, (int)strlen(str), nullptr, 0);
|
||||
Str = SysAllocStringLen(0, wslen);
|
||||
MultiByteToWideChar(CP_ACP, 0, str, (int)strlen(str), Str, wslen);
|
||||
}
|
||||
|
||||
LocalBSTR(const wchar_t* str)
|
||||
{
|
||||
Str = ::SysAllocString(str);
|
||||
}
|
||||
|
||||
~LocalBSTR()
|
||||
{
|
||||
if (Str)
|
||||
{
|
||||
::SysFreeString(Str);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
operator const BSTR() const
|
||||
{
|
||||
return Str;
|
||||
}
|
||||
|
||||
operator const wchar_t*() const
|
||||
{
|
||||
return Str;
|
||||
}
|
||||
};
|
||||
|
||||
namespace VisualStudio
|
||||
{
|
||||
bool SameFile(HANDLE h1, HANDLE h2)
|
||||
{
|
||||
BY_HANDLE_FILE_INFORMATION bhfi1 = { 0 };
|
||||
BY_HANDLE_FILE_INFORMATION bhfi2 = { 0 };
|
||||
|
||||
if (::GetFileInformationByHandle(h1, &bhfi1) && ::GetFileInformationByHandle(h2, &bhfi2))
|
||||
{
|
||||
return ((bhfi1.nFileIndexHigh == bhfi2.nFileIndexHigh) && (bhfi1.nFileIndexLow == bhfi2.nFileIndexLow) && (bhfi1.dwVolumeSerialNumber == bhfi2.dwVolumeSerialNumber));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AreFilePathsEqual(const wchar_t* path1, const wchar_t* path2)
|
||||
{
|
||||
if (wcscmp(path1, path2) == 0)
|
||||
return true;
|
||||
|
||||
HANDLE file1 = CreateFileW(path1, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
HANDLE file2 = CreateFileW(path2, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
|
||||
bool result = SameFile(file1, file2);
|
||||
|
||||
CloseHandle(file1);
|
||||
CloseHandle(file2);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class ConnectionInternal
|
||||
{
|
||||
public:
|
||||
|
||||
const wchar_t* ClsID;
|
||||
LocalBSTR SolutionPath;
|
||||
|
||||
CLSID CLSID;
|
||||
ComPtr<EnvDTE::_DTE> DTE;
|
||||
|
||||
public:
|
||||
|
||||
ConnectionInternal(const wchar_t* clsID, const wchar_t* solutionPath)
|
||||
: ClsID(clsID)
|
||||
, SolutionPath(solutionPath)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return DTE != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Scans the running processes to find a running Visual Studio instance with the specified version and open solution.
|
||||
//
|
||||
// @param[in] clsID Class ID of the specific Visual Studio version we are looking for.
|
||||
// @param[in] solutionPath Path to the solution the instance needs to have open.
|
||||
// @return DTE object that may be used to interact with the Visual Studio instance, or null if
|
||||
// not found.
|
||||
static Result FindRunningInstance(ConnectionHandle connection)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
ComPtr<IRunningObjectTable> runningObjectTable;
|
||||
result = GetRunningObjectTable(0, &runningObjectTable);
|
||||
CHECK_VS_RESULT("VisualStudio::FindRunningInstance - GetRunningObjectTable");
|
||||
|
||||
ComPtr<IEnumMoniker> enumMoniker;
|
||||
result = runningObjectTable->EnumRunning(&enumMoniker);
|
||||
CHECK_VS_RESULT("VisualStudio::FindRunningInstance - EnumRunning");
|
||||
|
||||
ComPtr<IMoniker> dteMoniker;
|
||||
result = CreateClassMoniker(connection->CLSID, &dteMoniker);
|
||||
CHECK_VS_RESULT("VisualStudio::FindRunningInstance - CreateClassMoniker");
|
||||
|
||||
ComPtr<IMoniker> moniker;
|
||||
ULONG count = 0;
|
||||
while (enumMoniker->Next(1, &moniker, &count) == S_OK)
|
||||
{
|
||||
if (moniker->IsEqual(dteMoniker))
|
||||
{
|
||||
ComPtr<IUnknown> curObject;
|
||||
result = runningObjectTable->GetObjectW(moniker, &curObject);
|
||||
moniker.Detach();
|
||||
|
||||
if (result != S_OK)
|
||||
continue;
|
||||
|
||||
ComPtr<EnvDTE::_DTE> dte;
|
||||
curObject->QueryInterface(__uuidof(EnvDTE::_DTE), (void**)&dte);
|
||||
|
||||
if (dte == nullptr)
|
||||
continue;
|
||||
|
||||
ComPtr<EnvDTE::_Solution> solution;
|
||||
if (FAILED(dte->get_Solution(&solution)))
|
||||
continue;
|
||||
|
||||
LocalBSTR fullName;
|
||||
if (FAILED(solution->get_FullName(&fullName.Str)))
|
||||
continue;
|
||||
|
||||
if (AreFilePathsEqual(connection->SolutionPath, fullName))
|
||||
{
|
||||
// Found
|
||||
connection->DTE = dte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
// Opens a new Visual Studio instance of the specified version with the provided solution.
|
||||
//
|
||||
// @param[in] clsID Class ID of the specific Visual Studio version to start.
|
||||
// @param[in] solutionPath Path to the solution the instance needs to open.
|
||||
static Result OpenInstance(ConnectionHandle connection)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
ComPtr<IUnknown> newInstance = nullptr;
|
||||
result = CoCreateInstance(connection->CLSID, nullptr, CLSCTX_LOCAL_SERVER, EnvDTE::IID__DTE, (LPVOID*)&newInstance);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - CoCreateInstance");
|
||||
|
||||
newInstance->QueryInterface(&connection->DTE);
|
||||
if (connection->DTE == nullptr)
|
||||
return "Invalid DTE handle";
|
||||
|
||||
connection->DTE->put_UserControl(TRUE);
|
||||
|
||||
ComPtr<EnvDTE::_Solution> solution;
|
||||
result = connection->DTE->get_Solution(&solution);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - dte->get_Solution");
|
||||
|
||||
result = solution->Open(connection->SolutionPath);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - solution->Open");
|
||||
|
||||
// Wait until VS opens
|
||||
int maxWaitMs = 10 * 1000;
|
||||
int elapsedMs = 0;
|
||||
int stepTimeMs = 100;
|
||||
while (elapsedMs < maxWaitMs)
|
||||
{
|
||||
ComPtr<EnvDTE::Window> window = nullptr;
|
||||
if (SUCCEEDED(connection->DTE->get_MainWindow(&window)))
|
||||
return Result::Ok;
|
||||
|
||||
Sleep(stepTimeMs);
|
||||
elapsedMs += stepTimeMs;
|
||||
}
|
||||
|
||||
return "Visual Studio open timout";
|
||||
}
|
||||
|
||||
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::ProjectItems>& projectItems, BSTR filePath)
|
||||
{
|
||||
long count;
|
||||
projectItems->get_Count(&count);
|
||||
if (count == 0)
|
||||
return nullptr;
|
||||
|
||||
for (long i = 1; i <= count; i++) // They are counting from [1..Count]
|
||||
{
|
||||
ComPtr<EnvDTE::ProjectItem> projectItem;
|
||||
projectItems->Item(_variant_t(i), &projectItem);
|
||||
if (!projectItem)
|
||||
continue;
|
||||
|
||||
short fileCount = 0;
|
||||
projectItem->get_FileCount(&fileCount);
|
||||
for (short fileIndex = 1; fileIndex <= fileCount; fileIndex++)
|
||||
{
|
||||
_bstr_t filename;
|
||||
projectItem->get_FileNames(fileIndex, filename.GetAddress());
|
||||
|
||||
if (filename.GetBSTR() != nullptr && AreFilePathsEqual(filePath, filename))
|
||||
{
|
||||
return projectItem;
|
||||
}
|
||||
}
|
||||
|
||||
ComPtr<EnvDTE::ProjectItems> childProjectItems;
|
||||
projectItem->get_ProjectItems(&childProjectItems);
|
||||
if (childProjectItems)
|
||||
{
|
||||
ComPtr<EnvDTE::ProjectItem> result = FindItem(childProjectItems, filePath);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ComPtr<EnvDTE::ProjectItem> FindItem(const ComPtr<EnvDTE::_Solution>& solution, BSTR filePath)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
ComPtr<EnvDTE::Projects> projects;
|
||||
result = solution->get_Projects(&projects);
|
||||
if (FAILED(result))
|
||||
return nullptr;
|
||||
|
||||
long projectsCount = 0;
|
||||
result = projects->get_Count(&projectsCount);
|
||||
if (FAILED(result))
|
||||
return nullptr;
|
||||
|
||||
for (long projectIndex = 1; projectIndex <= projectsCount; projectIndex++) // They are counting from [1..Count]
|
||||
{
|
||||
ComPtr<EnvDTE::Project> project;
|
||||
result = projects->Item(_variant_t(projectIndex), &project);
|
||||
if (FAILED(result) || !project)
|
||||
continue;
|
||||
|
||||
ComPtr<EnvDTE::ProjectItems> projectItems;
|
||||
result = project->get_ProjectItems(&projectItems);
|
||||
if (FAILED(result) || !projectItems)
|
||||
continue;
|
||||
|
||||
auto projectItem = FindItem(projectItems, filePath);
|
||||
if (projectItem)
|
||||
{
|
||||
return projectItem;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Opens a file on a specific line in a running Visual Studio instance.
|
||||
//
|
||||
// @param[in] dte DTE object retrieved from FindRunningInstance() or OpenInstance().
|
||||
// @param[in] filePath Path of the file to open. File should be a part of the VS solution.
|
||||
// @param[in] line Line on which to focus Visual Studio after the file is open.
|
||||
static Result OpenFile(ConnectionHandle handle, BSTR filePath, unsigned int line)
|
||||
{
|
||||
HRESULT result;
|
||||
LocalBSTR viewKind(EnvDTE::vsViewKindPrimary);
|
||||
|
||||
#if USE_ITEM_OPERATIONS_OPEN
|
||||
ComPtr<EnvDTE::ItemOperations> itemOperations;
|
||||
result = handle->DTE->get_ItemOperations(&itemOperations);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - DTE->get_ItemOperations");
|
||||
#endif
|
||||
|
||||
// Check if that file is opened
|
||||
VARIANT_BOOL isOpen = 0;
|
||||
#if USE_ITEM_OPERATIONS_OPEN
|
||||
result = itemOperations->IsFileOpen(filePath, viewKind.Str, &isOpen);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - itemOperations->IsFileOpen");
|
||||
#else
|
||||
result = handle->DTE->get_IsOpenFile(viewKind.Str, filePath, &isOpen);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - DTE->get_IsOpenFile");
|
||||
#endif
|
||||
|
||||
// Open or navigate to a window with a file
|
||||
ComPtr<EnvDTE::Window> window;
|
||||
ComPtr<EnvDTE::Document> document;
|
||||
if (isOpen == 0)
|
||||
{
|
||||
// Open file
|
||||
#if USE_DOCUMENT_OPEN
|
||||
ComPtr<EnvDTE::Documents> documents;
|
||||
result = handle->DTE->get_Documents(&documents);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - DTE->get_Documents");
|
||||
|
||||
ComPtr<EnvDTE::Document> tmp;
|
||||
LocalBSTR kind(_T("Auto"));
|
||||
result = documents->Open(filePath, kind.Str, VARIANT_FALSE, &tmp);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - documents->Open");
|
||||
|
||||
result = tmp->get_ActiveWindow(&window);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - tmp->get_ActiveWindow");
|
||||
#elif USE_PROJECT_ITEM_OPEN
|
||||
ComPtr<EnvDTE::_Solution> solution;
|
||||
result = handle->DTE->get_Solution(&solution);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - DTE->get_Solution");
|
||||
|
||||
ComPtr<EnvDTE::ProjectItem> projectItem = FindItem(solution, filePath);
|
||||
if (projectItem)
|
||||
{
|
||||
result = projectItem->Open(viewKind, &window);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - projectItem->Open");
|
||||
}
|
||||
#elif USE_ITEM_OPERATIONS_OPEN
|
||||
result = itemOperations->OpenFile(filePath, viewKind, &window);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - itemOperations->OpenFile");
|
||||
#else
|
||||
result = handle->DTE->OpenFile(viewKind, filePath, &window);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - DTE->OpenFile");
|
||||
#endif
|
||||
if (window == nullptr)
|
||||
return Result::Ok;
|
||||
|
||||
// Activate window and get document handle
|
||||
result = window->Activate();
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - window->Activate");
|
||||
result = handle->DTE->get_ActiveDocument(&document);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - dte->get_ActiveDocument");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find opened document
|
||||
|
||||
ComPtr<EnvDTE::Documents> documents;
|
||||
result = handle->DTE->get_Documents(&documents);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - DTE->get_Documents");
|
||||
|
||||
long documentsCount;
|
||||
result = documents->get_Count(&documentsCount);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - documents->get_Count");
|
||||
|
||||
for (int i = 1; i <= documentsCount; i++) // They are counting from [1..Count]
|
||||
{
|
||||
ComPtr<EnvDTE::Document> tmp;
|
||||
result = documents->Item(_variant_t(i), &tmp);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - documents->Item");
|
||||
if (tmp == nullptr)
|
||||
continue;
|
||||
|
||||
BSTR tmpPath;
|
||||
result = tmp->get_FullName(&tmpPath);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - tmp->get_FullName");
|
||||
|
||||
if (AreFilePathsEqual(filePath, tmpPath))
|
||||
{
|
||||
result = tmp->Activate();
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - tmp->Activate");
|
||||
|
||||
// Found
|
||||
document = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (document == nullptr)
|
||||
return "Cannot open a file";
|
||||
|
||||
// Check if need to select a given line
|
||||
if (line != 0)
|
||||
{
|
||||
ComPtr<IDispatch> selection;
|
||||
result = document->get_Selection(&selection);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - activeDocument->get_Selection");
|
||||
if (selection == nullptr)
|
||||
return Result::Ok;
|
||||
|
||||
ComPtr<EnvDTE::TextSelection> textSelection;
|
||||
result = selection->QueryInterface(&textSelection);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenFile - selection->QueryInterface");
|
||||
|
||||
textSelection->GotoLine(line, VARIANT_TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
// Bring the window in focus
|
||||
window = nullptr;
|
||||
if (SUCCEEDED(dte->get_MainWindow(&window)))
|
||||
{
|
||||
window->Activate();
|
||||
|
||||
HWND hWnd;
|
||||
window->get_HWnd((LONG*)&hWnd);
|
||||
SetForegroundWindow(hWnd);
|
||||
}
|
||||
*/
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
// Adds a file to the project opened in a running Visual Studio instance.
|
||||
//
|
||||
// @param[in] dte DTE object retrieved from FindRunningInstance() or OpenInstance().
|
||||
// @param[in] filePath Path of the file to add.
|
||||
// @param[in] localPath Path of the file to add relative to the solution folder.
|
||||
static Result AddFile(ConnectionHandle handle, BSTR filePath, const wchar_t* localPath)
|
||||
{
|
||||
HRESULT result;
|
||||
|
||||
ComPtr<EnvDTE::_Solution> solution;
|
||||
result = handle->DTE->get_Solution(&solution);
|
||||
CHECK_VS_RESULT("VisualStudio::OpenInstance - DTE->get_Solution");
|
||||
|
||||
ComPtr<EnvDTE::ProjectItem> projectItem = FindItem(solution, filePath);
|
||||
if (projectItem)
|
||||
{
|
||||
// Already added
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
ComPtr<EnvDTE::Projects> projects;
|
||||
result = solution->get_Projects(&projects);
|
||||
if (FAILED(result))
|
||||
return nullptr;
|
||||
|
||||
long projectsCount = 0;
|
||||
result = projects->get_Count(&projectsCount);
|
||||
if (FAILED(result))
|
||||
return nullptr;
|
||||
|
||||
ComPtr<EnvDTE::Project> project;
|
||||
wchar_t buffer[500];
|
||||
|
||||
// Place .Build.cs scripts into BuildScripts project
|
||||
const int localPathLength = (int)wcslen(localPath);
|
||||
if (localPathLength >= 10 && _wcsicmp(localPath + localPathLength - ARRAY_COUNT(".Build.cs") + 1, TEXT(".Build.cs")) == 0)
|
||||
{
|
||||
for (long projectIndex = 1; projectIndex <= projectsCount; projectIndex++) // They are counting from [1..Count]
|
||||
{
|
||||
result = projects->Item(_variant_t(projectIndex), &project);
|
||||
if (FAILED(result) || !project)
|
||||
continue;
|
||||
|
||||
_bstr_t name;
|
||||
if (SUCCEEDED(project->get_Name(name.GetAddress())) && wcscmp(name, TEXT("BuildScripts")) == 0)
|
||||
break;
|
||||
project = nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: find the project to add script to? for .cs scripts it should be csproj, for c++ vcproj?
|
||||
|
||||
// Find parent folder
|
||||
wchar_t* path = filePath + wcslen(localPath) + 1;
|
||||
int length = (int)wcslen(path);
|
||||
memcpy(buffer, path, length * sizeof(wchar_t));
|
||||
for (int i = length - 1; i >= 0; i--)
|
||||
{
|
||||
if (buffer[i] == '\\' || buffer[i] == '/')
|
||||
{
|
||||
buffer[i] = '\0';
|
||||
length = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const LocalBSTR parentPath(TEXT("MyProject"));
|
||||
projectItem = FindItem(solution, parentPath);
|
||||
// ...
|
||||
}
|
||||
|
||||
// TODO: add file and all missing parent folders to the project
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
class CleanupHelper
|
||||
{
|
||||
public:
|
||||
|
||||
IMessageFilter* oldFilter;
|
||||
VSMessageFilter* newFilter;
|
||||
|
||||
CleanupHelper()
|
||||
{
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
newFilter = new VSMessageFilter();
|
||||
CoRegisterMessageFilter(newFilter, &oldFilter);
|
||||
}
|
||||
|
||||
~CleanupHelper()
|
||||
{
|
||||
CoRegisterMessageFilter(oldFilter, nullptr);
|
||||
CoUninitialize();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
VisualStudio::Result VisualStudio::Result::Ok;
|
||||
|
||||
int VisualStudio::GetVisualStudioVersions(InstanceInfo* infos, int infosCount)
|
||||
{
|
||||
// Try to create the CoCreate the class; if that fails, likely no instances are registered
|
||||
ComPtr<ISetupConfiguration2> query;
|
||||
HRESULT result = CoCreateInstance(__uuidof(SetupConfiguration), nullptr, CLSCTX_ALL, __uuidof(ISetupConfiguration2), (LPVOID*)&query);
|
||||
if (FAILED(result))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the enumerator
|
||||
ComPtr<IEnumSetupInstances> enumSetupInstances;
|
||||
result = query->EnumAllInstances(&enumSetupInstances);
|
||||
if (FAILED(result))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check the state and version of the enumerated instances
|
||||
int32 count = 0;
|
||||
ComPtr<ISetupInstance> instance;
|
||||
while(count < infosCount)
|
||||
{
|
||||
ULONG fetched = 0;
|
||||
result = enumSetupInstances->Next(1, &instance, &fetched);
|
||||
if (FAILED(result) || fetched == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ComPtr<ISetupInstance2> setupInstance2;
|
||||
result = instance->QueryInterface(__uuidof(ISetupInstance2), (LPVOID*)&setupInstance2);
|
||||
if (SUCCEEDED(result))
|
||||
{
|
||||
InstanceState state;
|
||||
result = setupInstance2->GetState(&state);
|
||||
if (SUCCEEDED(result) && (state & eLocal) != 0)
|
||||
{
|
||||
BSTR installationVersion;
|
||||
result = setupInstance2->GetInstallationVersion(&installationVersion);
|
||||
if (SUCCEEDED(result))
|
||||
{
|
||||
BSTR installationPath;
|
||||
result = setupInstance2->GetInstallationPath(&installationPath);
|
||||
if (SUCCEEDED(result))
|
||||
{
|
||||
BSTR productPath;
|
||||
result = setupInstance2->GetProductPath(&productPath);
|
||||
if (SUCCEEDED(result))
|
||||
{
|
||||
auto& info = infos[count++];
|
||||
|
||||
char version[3];
|
||||
version[0] = (char)installationVersion[0];
|
||||
version[1] = (char)installationVersion[1];
|
||||
version[2] = 0;
|
||||
info.VersionMajor = atoi(version);
|
||||
|
||||
swprintf_s(info.ExecutablePath, MAX_PATH, L"%s\\%s", installationPath, productPath);
|
||||
|
||||
wchar_t progID[100];
|
||||
swprintf_s(progID, 100, L"VisualStudio.DTE.%d.0", info.VersionMajor);
|
||||
|
||||
CLSID clsid;
|
||||
CLSIDFromProgID(progID, &clsid);
|
||||
StringFromGUID2(clsid, info.CLSID, 40);
|
||||
|
||||
SysFreeString(productPath);
|
||||
}
|
||||
SysFreeString(installationPath);
|
||||
}
|
||||
SysFreeString(installationVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void VisualStudio::OpenConnection(ConnectionHandle& connection, const wchar_t* clsID, const wchar_t* solutionPath)
|
||||
{
|
||||
connection = new ConnectionInternal(clsID, solutionPath);
|
||||
}
|
||||
|
||||
void VisualStudio::CloseConnection(ConnectionHandle& connection)
|
||||
{
|
||||
if (connection)
|
||||
{
|
||||
delete connection;
|
||||
connection = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool VisualStudio::IsActive(const ConnectionHandle& connection)
|
||||
{
|
||||
// Check if already opened
|
||||
if (connection->IsValid())
|
||||
return true;
|
||||
|
||||
// Try to find active
|
||||
auto e = FindRunningInstance(connection);
|
||||
return connection->DTE != nullptr;
|
||||
}
|
||||
|
||||
VisualStudio::Result VisualStudio::OpenSolution(ConnectionHandle connection)
|
||||
{
|
||||
// Check if already opened
|
||||
if (connection->IsValid())
|
||||
return Result::Ok;
|
||||
|
||||
// Temporary data
|
||||
CleanupHelper helper;
|
||||
HRESULT result;
|
||||
|
||||
// Cache VS version CLSID
|
||||
result = CLSIDFromString(connection->ClsID, &connection->CLSID);
|
||||
CHECK_VS_RESULT("VisualStudio::CLSIDFromString");
|
||||
|
||||
// Get or open VS with solution
|
||||
auto e = FindRunningInstance(connection);
|
||||
if (e.Failed())
|
||||
return e;
|
||||
if (connection->DTE == nullptr)
|
||||
{
|
||||
e = OpenInstance(connection);
|
||||
if (e.Failed())
|
||||
return e;
|
||||
if (connection->DTE == nullptr)
|
||||
return "Cannot open Visual Studio";
|
||||
}
|
||||
|
||||
// Focus VS main window
|
||||
ComPtr<EnvDTE::Window> window;
|
||||
if (SUCCEEDED(connection->DTE->get_MainWindow(&window)))
|
||||
window->Activate();
|
||||
|
||||
return Result::Ok;
|
||||
}
|
||||
|
||||
VisualStudio::Result VisualStudio::OpenFile(ConnectionHandle connection, const wchar_t* path, unsigned int line)
|
||||
{
|
||||
// Ensure to have valid connection
|
||||
auto result = OpenSolution(connection);
|
||||
if (result.Failed())
|
||||
return result;
|
||||
|
||||
// Open file
|
||||
CleanupHelper helper;
|
||||
const LocalBSTR pathBstr(path);
|
||||
return OpenFile(connection, pathBstr.Str, line);
|
||||
}
|
||||
|
||||
VisualStudio::Result VisualStudio::AddFile(ConnectionHandle connection, const wchar_t* path, const wchar_t* localPath)
|
||||
{
|
||||
// Ensure to have valid connection
|
||||
auto result = OpenSolution(connection);
|
||||
if (result.Failed())
|
||||
return result;
|
||||
|
||||
// Add file
|
||||
CleanupHelper helper;
|
||||
LocalBSTR pathBstr(path);
|
||||
return AddFile(connection, pathBstr.Str, localPath);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
|
||||
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
|
||||
#include <string>
|
||||
|
||||
/// <summary>
|
||||
/// Contains various helper classes for interacting with a Visual Studio instance running on this machine.
|
||||
/// </summary>
|
||||
namespace VisualStudio
|
||||
{
|
||||
/// <summary>
|
||||
/// Visual Studio connection operation result
|
||||
/// </summary>
|
||||
struct Result
|
||||
{
|
||||
static Result Ok;
|
||||
|
||||
std::string Message;
|
||||
|
||||
Result()
|
||||
{
|
||||
Message = "";
|
||||
}
|
||||
|
||||
Result(const char* msg)
|
||||
{
|
||||
Message = msg;
|
||||
}
|
||||
|
||||
Result(const std::string& msg)
|
||||
{
|
||||
Message = msg;
|
||||
}
|
||||
|
||||
bool Failed() const
|
||||
{
|
||||
return Message.size() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct InstanceInfo
|
||||
{
|
||||
wchar_t CLSID[40];
|
||||
wchar_t ExecutablePath[MAX_PATH];
|
||||
int VersionMajor;
|
||||
};
|
||||
|
||||
class ConnectionInternal;
|
||||
typedef ConnectionInternal* ConnectionHandle;
|
||||
|
||||
int GetVisualStudioVersions(InstanceInfo* infos, int infosCount);
|
||||
void OpenConnection(ConnectionHandle& connection, const wchar_t* clsID, const wchar_t* solutionPath);
|
||||
void CloseConnection(ConnectionHandle& connection);
|
||||
bool IsActive(const ConnectionHandle& connection);
|
||||
Result OpenSolution(ConnectionHandle connection);
|
||||
Result OpenFile(ConnectionHandle connection, const wchar_t* path, unsigned int line);
|
||||
Result AddFile(ConnectionHandle connection, const wchar_t* path, const wchar_t* localPath);
|
||||
|
||||
/// <summary>
|
||||
/// Visual Studio connection wrapper class
|
||||
/// </summary>
|
||||
class Connection
|
||||
{
|
||||
private:
|
||||
|
||||
ConnectionHandle Handle;
|
||||
|
||||
public:
|
||||
|
||||
Connection(const wchar_t* clsID, const wchar_t* solutionPath)
|
||||
{
|
||||
OpenConnection(Handle, clsID, solutionPath);
|
||||
}
|
||||
|
||||
~Connection()
|
||||
{
|
||||
CloseConnection(Handle);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool IsActive() const
|
||||
{
|
||||
return VisualStudio::IsActive(Handle);
|
||||
}
|
||||
|
||||
Result OpenSolution() const
|
||||
{
|
||||
return VisualStudio::OpenSolution(Handle);
|
||||
}
|
||||
|
||||
Result OpenFile(const wchar_t* path, unsigned int line) const
|
||||
{
|
||||
return VisualStudio::OpenFile(Handle, path, line);
|
||||
}
|
||||
|
||||
Result AddFile(const wchar_t* path, const wchar_t* localPath) const
|
||||
{
|
||||
return VisualStudio::AddFile(Handle, path, localPath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
|
||||
#include "VisualStudioEditor.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Scripting/ScriptsBuilder.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
|
||||
#include "VisualStudioConnection.h"
|
||||
|
||||
VisualStudioEditor::VisualStudioEditor(VisualStudioVersion version, const String& execPath, const String& CLSID)
|
||||
: _version(version)
|
||||
, _execPath(execPath)
|
||||
, _CLSID(CLSID)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case VisualStudioVersion::VS2008:
|
||||
_type = CodeEditorTypes::VS2008;
|
||||
break;
|
||||
case VisualStudioVersion::VS2010:
|
||||
_type = CodeEditorTypes::VS2010;
|
||||
break;
|
||||
case VisualStudioVersion::VS2012:
|
||||
_type = CodeEditorTypes::VS2012;
|
||||
break;
|
||||
case VisualStudioVersion::VS2013:
|
||||
_type = CodeEditorTypes::VS2013;
|
||||
break;
|
||||
case VisualStudioVersion::VS2015:
|
||||
_type = CodeEditorTypes::VS2015;
|
||||
break;
|
||||
case VisualStudioVersion::VS2017:
|
||||
_type = CodeEditorTypes::VS2017;
|
||||
break;
|
||||
case VisualStudioVersion::VS2019:
|
||||
_type = CodeEditorTypes::VS2019;
|
||||
break;
|
||||
default: CRASH;
|
||||
break;
|
||||
}
|
||||
_solutionPath = Globals::ProjectFolder / Editor::Project->Name + TEXT(".sln");
|
||||
}
|
||||
|
||||
void VisualStudioEditor::FindEditors(Array<CodeEditor*>* output)
|
||||
{
|
||||
String installDir;
|
||||
String clsID;
|
||||
String regVsRootNode;
|
||||
|
||||
// Select registry node for
|
||||
if (Platform::Is64BitPlatform())
|
||||
regVsRootNode = TEXT("SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\");
|
||||
else
|
||||
regVsRootNode = TEXT("SOFTWARE\\Microsoft\\VisualStudio\\");
|
||||
|
||||
VisualStudio::InstanceInfo infos[8];
|
||||
const int32 count = VisualStudio::GetVisualStudioVersions(infos, ARRAY_COUNT(infos));
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
auto& info = infos[i];
|
||||
VisualStudioVersion version;
|
||||
switch (info.VersionMajor)
|
||||
{
|
||||
case 16:
|
||||
version = VisualStudioVersion::VS2019;
|
||||
break;
|
||||
case 15:
|
||||
version = VisualStudioVersion::VS2017;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
String executablePath(info.ExecutablePath);
|
||||
if (!FileSystem::FileExists(executablePath))
|
||||
continue;
|
||||
|
||||
// Create editor
|
||||
clsID = info.CLSID;
|
||||
auto editor = New<VisualStudioEditor>(version, executablePath, clsID);
|
||||
output->Add(editor);
|
||||
}
|
||||
|
||||
struct VersionData
|
||||
{
|
||||
VisualStudioVersion Version;
|
||||
const Char* RegistryKey;
|
||||
};
|
||||
|
||||
const VersionData versions[] =
|
||||
{
|
||||
// Order matters
|
||||
{ VisualStudioVersion::VS2015, TEXT("14.0"), },
|
||||
{ VisualStudioVersion::VS2013, TEXT("12.0"), },
|
||||
{ VisualStudioVersion::VS2012, TEXT("11.0"), },
|
||||
{ VisualStudioVersion::VS2012, TEXT("11.0"), },
|
||||
{ VisualStudioVersion::VS2008, TEXT("9.0") },
|
||||
};
|
||||
|
||||
for (int32 i = 0; i < ARRAY_COUNT(versions); i++)
|
||||
{
|
||||
auto registryKey = regVsRootNode + versions[i].RegistryKey;
|
||||
|
||||
// Read install directory
|
||||
if (Platform::ReadRegValue(HKEY_LOCAL_MACHINE, registryKey, TEXT("InstallDir"), &installDir) || installDir.IsEmpty())
|
||||
continue;
|
||||
|
||||
// Ensure that file exists
|
||||
String execPath = installDir + TEXT("devenv.exe");
|
||||
if (!FileSystem::FileExists(execPath))
|
||||
continue;
|
||||
|
||||
// Read version info id
|
||||
clsID.Clear();
|
||||
Platform::ReadRegValue(HKEY_LOCAL_MACHINE, registryKey, TEXT("ThisVersionDTECLSID"), &clsID);
|
||||
|
||||
// Create editor
|
||||
auto editor = New<VisualStudioEditor>(versions[i].Version, execPath, clsID);
|
||||
output->Add(editor);
|
||||
}
|
||||
}
|
||||
|
||||
CodeEditorTypes VisualStudioEditor::GetType() const
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
String VisualStudioEditor::GetName() const
|
||||
{
|
||||
return String(ToString(_version));
|
||||
}
|
||||
|
||||
void VisualStudioEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject();
|
||||
}
|
||||
|
||||
// Open file
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
const auto result = connection.OpenFile(*path, line);
|
||||
if (result.Failed())
|
||||
{
|
||||
LOG(Warning, "Cannot open file \'{0}\':{1}. {2}.", path, line, String(result.Message.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void VisualStudioEditor::OpenSolution()
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject();
|
||||
}
|
||||
|
||||
// Open solution
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
const auto result = connection.OpenSolution();
|
||||
if (result.Failed())
|
||||
{
|
||||
LOG(Warning, "Cannot open solution. {0}", String(result.Message.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void VisualStudioEditor::OnFileAdded(const String& path)
|
||||
{
|
||||
// TODO: finish dynamic files adding to the project - for now just regenerate it
|
||||
ScriptsBuilder::GenerateProject();
|
||||
return;
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Edit solution
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
if (connection.IsActive())
|
||||
{
|
||||
String tmp = path;
|
||||
tmp.Replace('/', '\\');
|
||||
String tmp2 = tmp.Substring(Globals::ProjectSourceFolder.Length() + 1);
|
||||
const auto result = connection.AddFile(*tmp, *tmp2);
|
||||
if (result.Failed())
|
||||
{
|
||||
LOG(Warning, "Cannot add file to project. {0}", String(result.Message.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VisualStudioEditor::UseAsyncForOpen() const
|
||||
{
|
||||
// Need to generate project files if missing first
|
||||
if (!FileSystem::FileExists(_solutionPath))
|
||||
return true;
|
||||
|
||||
// Open in async only when no solution opened
|
||||
const VisualStudio::Connection connection(*_CLSID, *_solutionPath);
|
||||
return !connection.IsActive();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
|
||||
#include "Engine/Core/Enums.h"
|
||||
#include "Editor/Scripting/CodeEditor.h"
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft Visual Studio version types
|
||||
/// </summary>
|
||||
DECLARE_ENUM_7(VisualStudioVersion, VS2008, VS2010, VS2012, VS2013, VS2015, VS2017, VS2019);
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of code editor utility that is using Microsoft Visual Studio.
|
||||
/// </summary>
|
||||
class VisualStudioEditor : public CodeEditor
|
||||
{
|
||||
private:
|
||||
|
||||
VisualStudioVersion _version;
|
||||
CodeEditorTypes _type;
|
||||
String _execPath;
|
||||
String _CLSID;
|
||||
String _solutionPath;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VisualStudioEditor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="version">VS version</param>
|
||||
/// <param name="execPath">Executable file path</param>
|
||||
/// <param name="CLSID">CLSID of VS</param>
|
||||
VisualStudioEditor(VisualStudioVersion version, const String& execPath, const String& CLSID);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets version of Visual Studio
|
||||
/// </summary>
|
||||
/// <returns>VS version</returns>
|
||||
FORCE_INLINE VisualStudioVersion GetVersion() const
|
||||
{
|
||||
return _version;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Try to find installed Visual Studio instances. Adds them to the result list.
|
||||
/// </summary>
|
||||
/// <param name="output">The output editors.</param>
|
||||
static void FindEditors(Array<CodeEditor*>* output);
|
||||
|
||||
public:
|
||||
|
||||
// [CodeEditor]
|
||||
CodeEditorTypes GetType() const override;
|
||||
String GetName() const override;
|
||||
void OpenFile(const String& path, int32 line) override;
|
||||
void OpenSolution() override;
|
||||
void OnFileAdded(const String& path) override;
|
||||
bool UseAsyncForOpen() const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "VisualStudioCodeEditor.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Scripting/ScriptsBuilder.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
|
||||
|
||||
VisualStudioCodeEditor::VisualStudioCodeEditor(const String& execPath)
|
||||
: _execPath(execPath)
|
||||
, _workspacePath(Globals::ProjectFolder / Editor::Project->Name + TEXT(".code-workspace"))
|
||||
{
|
||||
}
|
||||
|
||||
void VisualStudioCodeEditor::FindEditors(Array<CodeEditor*>* output)
|
||||
{
|
||||
#if PLATFORM_WINDOWS
|
||||
String cmd;
|
||||
if (Platform::ReadRegValue(HKEY_CURRENT_USER, TEXT("SOFTWARE\\Classes\\Applications\\Code.exe\\shell\\open\\command"), TEXT(""), &cmd) || cmd.IsEmpty())
|
||||
{
|
||||
if (Platform::ReadRegValue(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Classes\\Applications\\Code.exe\\shell\\open\\command"), TEXT(""), &cmd) || cmd.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
const String path = cmd.Substring(1, cmd.Length() - String(TEXT("\" \"%1\"")).Length() - 1);
|
||||
if (FileSystem::FileExists(path))
|
||||
{
|
||||
output->Add(New<VisualStudioCodeEditor>(path));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
CodeEditorTypes VisualStudioCodeEditor::GetType() const
|
||||
{
|
||||
return CodeEditorTypes::VSCode;
|
||||
}
|
||||
|
||||
String VisualStudioCodeEditor::GetName() const
|
||||
{
|
||||
return TEXT("Visual Studio Code");
|
||||
}
|
||||
|
||||
void VisualStudioCodeEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
// Generate project files if missing
|
||||
if (!FileSystem::FileExists(Globals::ProjectFolder / TEXT(".vscode/tasks.json")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(TEXT("-vscode"));
|
||||
}
|
||||
|
||||
// Open file
|
||||
line = line > 0 ? line : 1;
|
||||
const String args = String::Format(TEXT("\"{0}\" -g \"{1}\":{2}"), _workspacePath, path, line);
|
||||
Platform::StartProcess(_execPath, args, StringView::Empty);
|
||||
}
|
||||
|
||||
void VisualStudioCodeEditor::OpenSolution()
|
||||
{
|
||||
// Generate project files if solution is missing
|
||||
if (!FileSystem::FileExists(Globals::ProjectFolder / TEXT(".vscode/tasks.json")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(TEXT("-vscode"));
|
||||
}
|
||||
|
||||
// Open solution
|
||||
const String args = String::Format(TEXT("\"{0}\""), _workspacePath);
|
||||
Platform::StartProcess(_execPath, args, StringView::Empty);
|
||||
}
|
||||
|
||||
bool VisualStudioCodeEditor::UseAsyncForOpen() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
41
Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h
Normal file
41
Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Editor/Scripting/CodeEditor.h"
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of code editor utility that is using Microsoft Visual Studio Code.
|
||||
/// </summary>
|
||||
class VisualStudioCodeEditor : public CodeEditor
|
||||
{
|
||||
private:
|
||||
|
||||
String _execPath;
|
||||
String _workspacePath;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VisualStudioEditor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="execPath">Executable file path</param>
|
||||
VisualStudioCodeEditor(const String& execPath);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find installed Visual Studio Code instance. Adds them to the result list.
|
||||
/// </summary>
|
||||
/// <param name="output">The output editors.</param>
|
||||
static void FindEditors(Array<CodeEditor*>* output);
|
||||
|
||||
public:
|
||||
|
||||
// [CodeEditor]
|
||||
CodeEditorTypes GetType() const override;
|
||||
String GetName() const override;
|
||||
void OpenFile(const String& path, int32 line) override;
|
||||
void OpenSolution() override;
|
||||
bool UseAsyncForOpen() const override;
|
||||
};
|
||||
Reference in New Issue
Block a user