mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
Actually stage the deletes. Update .gitignore
This commit is contained in:
271
.gitignore
vendored
271
.gitignore
vendored
@@ -1,3 +1,270 @@
|
|||||||
bin
|
syntax: glob
|
||||||
obj
|
|
||||||
|
### VisualStudio ###
|
||||||
|
|
||||||
|
# Project.json lock file
|
||||||
project.lock.json
|
project.lock.json
|
||||||
|
|
||||||
|
# Tool Runtime Dir
|
||||||
|
/[Tt]ools/
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
build/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
msbuild.log
|
||||||
|
msbuild.err
|
||||||
|
msbuild.wrn
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Cross building rootfs
|
||||||
|
cross/rootfs/
|
||||||
|
|
||||||
|
# Visual Studio 2015
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# Visual Studio 2015 Pre-CTP6
|
||||||
|
*.sln.ide
|
||||||
|
*.ide/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
#NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding addin-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
*.nupkg
|
||||||
|
**/packages/*
|
||||||
|
|
||||||
|
# NuGet package restore lockfiles
|
||||||
|
project.lock.json
|
||||||
|
|
||||||
|
# Windows Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Windows Store app package directory
|
||||||
|
AppPackages/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
*.Cache
|
||||||
|
ClientBin/
|
||||||
|
[Ss]tyle[Cc]op.*
|
||||||
|
~$*
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
node_modules/
|
||||||
|
*.metaproj
|
||||||
|
*.metaproj.tmp
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
### MonoDevelop ###
|
||||||
|
|
||||||
|
*.pidb
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
|
||||||
|
# Windows image file caches
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
### Linux ###
|
||||||
|
|
||||||
|
*~
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
### OSX ###
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear on external disk
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# vim temporary files
|
||||||
|
[._]*.s[a-w][a-z]
|
||||||
|
[._]s[a-w][a-z]
|
||||||
|
*.un~
|
||||||
|
Session.vim
|
||||||
|
.netrwhist
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/
|
||||||
|
|||||||
24
ServiceHost/.vscode/launch.json
vendored
24
ServiceHost/.vscode/launch.json
vendored
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": ".NET Core Launch (console)",
|
|
||||||
"type": "coreclr",
|
|
||||||
"request": "launch",
|
|
||||||
"preLaunchTask": "build",
|
|
||||||
"program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/servicehost.dll",
|
|
||||||
"args": [],
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"externalConsole": true,
|
|
||||||
"requireExactSource": false,
|
|
||||||
"stopAtEntry": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": ".NET Core Attach",
|
|
||||||
"type": "coreclr",
|
|
||||||
"request": "attach",
|
|
||||||
"requireExactSource": false,
|
|
||||||
"processId": 17264
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
14
ServiceHost/.vscode/tasks.json
vendored
14
ServiceHost/.vscode/tasks.json
vendored
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.1.0",
|
|
||||||
"command": "dotnet",
|
|
||||||
"isShellCommand": true,
|
|
||||||
"args": [],
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"taskName": "build",
|
|
||||||
"args": [],
|
|
||||||
"isBuildCommand": true,
|
|
||||||
"problemMatcher": "$msCompile"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a class that describes the capabilities of a language
|
|
||||||
/// client. At this time no specific capabilities are listed for
|
|
||||||
/// clients.
|
|
||||||
/// </summary>
|
|
||||||
public class ClientCapabilities
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class CompletionRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<TextDocumentPosition, CompletionItem[]> Type =
|
|
||||||
RequestType<TextDocumentPosition, CompletionItem[]>.Create("textDocument/completion");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CompletionResolveRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<CompletionItem, CompletionItem> Type =
|
|
||||||
RequestType<CompletionItem, CompletionItem>.Create("completionItem/resolve");
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum CompletionItemKind
|
|
||||||
{
|
|
||||||
Text = 1,
|
|
||||||
Method = 2,
|
|
||||||
Function = 3,
|
|
||||||
Constructor = 4,
|
|
||||||
Field = 5,
|
|
||||||
Variable = 6,
|
|
||||||
Class = 7,
|
|
||||||
Interface = 8,
|
|
||||||
Module = 9,
|
|
||||||
Property = 10,
|
|
||||||
Unit = 11,
|
|
||||||
Value = 12,
|
|
||||||
Enum = 13,
|
|
||||||
Keyword = 14,
|
|
||||||
Snippet = 15,
|
|
||||||
Color = 16,
|
|
||||||
File = 17,
|
|
||||||
Reference = 18
|
|
||||||
}
|
|
||||||
|
|
||||||
[DebuggerDisplay("NewText = {NewText}, Range = {Range.Start.Line}:{Range.Start.Character} - {Range.End.Line}:{Range.End.Character}")]
|
|
||||||
public class TextEdit
|
|
||||||
{
|
|
||||||
public Range Range { get; set; }
|
|
||||||
|
|
||||||
public string NewText { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DebuggerDisplay("Kind = {Kind.ToString()}, Label = {Label}, Detail = {Detail}")]
|
|
||||||
public class CompletionItem
|
|
||||||
{
|
|
||||||
public string Label { get; set; }
|
|
||||||
|
|
||||||
public CompletionItemKind? Kind { get; set; }
|
|
||||||
|
|
||||||
public string Detail { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the documentation string for the completion item.
|
|
||||||
/// </summary>
|
|
||||||
public string Documentation { get; set; }
|
|
||||||
|
|
||||||
public string SortText { get; set; }
|
|
||||||
|
|
||||||
public string FilterText { get; set; }
|
|
||||||
|
|
||||||
public string InsertText { get; set; }
|
|
||||||
|
|
||||||
public TextEdit TextEdit { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a custom data field that allows the server to mark
|
|
||||||
/// each completion item with an identifier that will help correlate
|
|
||||||
/// the item to the previous completion request during a completion
|
|
||||||
/// resolve request.
|
|
||||||
/// </summary>
|
|
||||||
public object Data { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class DidChangeConfigurationNotification<TConfig>
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
EventType<DidChangeConfigurationParams<TConfig>> Type =
|
|
||||||
EventType<DidChangeConfigurationParams<TConfig>>.Create("workspace/didChangeConfiguration");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DidChangeConfigurationParams<TConfig>
|
|
||||||
{
|
|
||||||
public TConfig Settings { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class DefinitionRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<TextDocumentPosition, Location[]> Type =
|
|
||||||
RequestType<TextDocumentPosition, Location[]>.Create("textDocument/definition");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class PublishDiagnosticsNotification
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
EventType<PublishDiagnosticsNotification> Type =
|
|
||||||
EventType<PublishDiagnosticsNotification>.Create("textDocument/publishDiagnostics");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the URI for which diagnostic information is reported.
|
|
||||||
/// </summary>
|
|
||||||
public string Uri { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the array of diagnostic information items.
|
|
||||||
/// </summary>
|
|
||||||
public Diagnostic[] Diagnostics { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DiagnosticSeverity
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the diagnostic represents an error.
|
|
||||||
/// </summary>
|
|
||||||
Error = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the diagnostic represents a warning.
|
|
||||||
/// </summary>
|
|
||||||
Warning = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the diagnostic represents an informational message.
|
|
||||||
/// </summary>
|
|
||||||
Information = 3,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the diagnostic represents a hint.
|
|
||||||
/// </summary>
|
|
||||||
Hint = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Diagnostic
|
|
||||||
{
|
|
||||||
public Range Range { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the severity of the diagnostic. If omitted, the
|
|
||||||
/// client should interpret the severity.
|
|
||||||
/// </summary>
|
|
||||||
public DiagnosticSeverity? Severity { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the diagnostic's code (optional).
|
|
||||||
/// </summary>
|
|
||||||
public string Code { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the diagnostic message.
|
|
||||||
/// </summary>
|
|
||||||
public string Message { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public enum DocumentHighlightKind
|
|
||||||
{
|
|
||||||
Text = 1,
|
|
||||||
Read = 2,
|
|
||||||
Write = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DocumentHighlight
|
|
||||||
{
|
|
||||||
public Range Range { get; set; }
|
|
||||||
|
|
||||||
public DocumentHighlightKind Kind { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DocumentHighlightRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<TextDocumentPosition, DocumentHighlight[]> Type =
|
|
||||||
RequestType<TextDocumentPosition, DocumentHighlight[]>.Create("textDocument/documentHighlight");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class ExpandAliasRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<string, string> Type =
|
|
||||||
RequestType<string, string>.Create("SqlTools/expandAlias");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class FindModuleRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<List<PSModuleMessage>, object> Type =
|
|
||||||
RequestType<List<PSModuleMessage>, object>.Create("SqlTools/findModule");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class PSModuleMessage
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class MarkedString
|
|
||||||
{
|
|
||||||
public string Language { get; set; }
|
|
||||||
|
|
||||||
public string Value { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Hover
|
|
||||||
{
|
|
||||||
public MarkedString[] Contents { get; set; }
|
|
||||||
|
|
||||||
public Range? Range { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HoverRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<TextDocumentPosition, Hover> Type =
|
|
||||||
RequestType<TextDocumentPosition, Hover>.Create("textDocument/hover");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class InitializeRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<InitializeRequest, InitializeResult> Type =
|
|
||||||
RequestType<InitializeRequest, InitializeResult>.Create("initialize");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the root path of the editor's open workspace.
|
|
||||||
/// If null it is assumed that a file was opened without having
|
|
||||||
/// a workspace open.
|
|
||||||
/// </summary>
|
|
||||||
public string RootPath { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the capabilities provided by the client (editor).
|
|
||||||
/// </summary>
|
|
||||||
public ClientCapabilities Capabilities { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InitializeResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the capabilities provided by the language server.
|
|
||||||
/// </summary>
|
|
||||||
public ServerCapabilities Capabilities { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InitializeError
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a boolean indicating whether the client should retry
|
|
||||||
/// sending the Initialize request after showing the error to the user.
|
|
||||||
/// </summary>
|
|
||||||
public bool Retry { get; set;}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
class InstallModuleRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<string, object> Type =
|
|
||||||
RequestType<string, object>.Create("SqlTools/installModule");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class ReferencesRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<ReferencesParams, Location[]> Type =
|
|
||||||
RequestType<ReferencesParams, Location[]>.Create("textDocument/references");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ReferencesParams : TextDocumentPosition
|
|
||||||
{
|
|
||||||
public ReferencesContext Context { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ReferencesContext
|
|
||||||
{
|
|
||||||
public bool IncludeDeclaration { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class ServerCapabilities
|
|
||||||
{
|
|
||||||
public TextDocumentSyncKind? TextDocumentSync { get; set; }
|
|
||||||
|
|
||||||
public bool? HoverProvider { get; set; }
|
|
||||||
|
|
||||||
public CompletionOptions CompletionProvider { get; set; }
|
|
||||||
|
|
||||||
public SignatureHelpOptions SignatureHelpProvider { get; set; }
|
|
||||||
|
|
||||||
public bool? DefinitionProvider { get; set; }
|
|
||||||
|
|
||||||
public bool? ReferencesProvider { get; set; }
|
|
||||||
|
|
||||||
public bool? DocumentHighlightProvider { get; set; }
|
|
||||||
|
|
||||||
public bool? DocumentSymbolProvider { get; set; }
|
|
||||||
|
|
||||||
public bool? WorkspaceSymbolProvider { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the document synchronization strategies that a server may support.
|
|
||||||
/// </summary>
|
|
||||||
public enum TextDocumentSyncKind
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that documents should not be synced at all.
|
|
||||||
/// </summary>
|
|
||||||
None = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that document changes are always sent with the full content.
|
|
||||||
/// </summary>
|
|
||||||
Full,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that document changes are sent as incremental changes after
|
|
||||||
/// the initial document content has been sent.
|
|
||||||
/// </summary>
|
|
||||||
Incremental
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CompletionOptions
|
|
||||||
{
|
|
||||||
public bool? ResolveProvider { get; set; }
|
|
||||||
|
|
||||||
public string[] TriggerCharacters { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SignatureHelpOptions
|
|
||||||
{
|
|
||||||
public string[] TriggerCharacters { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class ShowOnlineHelpRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<string, object> Type =
|
|
||||||
RequestType<string, object>.Create("SqlTools/showOnlineHelp");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a message that is sent from the client to request
|
|
||||||
/// that the server shut down.
|
|
||||||
/// </summary>
|
|
||||||
public class ShutdownRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<object, object> Type =
|
|
||||||
RequestType<object, object>.Create("shutdown");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines an event that is sent from the client to notify that
|
|
||||||
/// the client is exiting and the server should as well.
|
|
||||||
/// </summary>
|
|
||||||
public class ExitNotification
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
EventType<object> Type =
|
|
||||||
EventType<object>.Create("exit");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public class SignatureHelpRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<TextDocumentPosition, SignatureHelp> Type =
|
|
||||||
RequestType<TextDocumentPosition, SignatureHelp>.Create("textDocument/signatureHelp");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ParameterInformation
|
|
||||||
{
|
|
||||||
public string Label { get; set; }
|
|
||||||
|
|
||||||
public string Documentation { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SignatureInformation
|
|
||||||
{
|
|
||||||
public string Label { get; set; }
|
|
||||||
|
|
||||||
public string Documentation { get; set; }
|
|
||||||
|
|
||||||
public ParameterInformation[] Parameters { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SignatureHelp
|
|
||||||
{
|
|
||||||
public SignatureInformation[] Signatures { get; set; }
|
|
||||||
|
|
||||||
public int? ActiveSignature { get; set; }
|
|
||||||
|
|
||||||
public int? ActiveParameter { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a base parameter class for identifying a text document.
|
|
||||||
/// </summary>
|
|
||||||
[DebuggerDisplay("TextDocumentIdentifier = {Uri}")]
|
|
||||||
public class TextDocumentIdentifier
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the URI which identifies the path of the
|
|
||||||
/// text document.
|
|
||||||
/// </summary>
|
|
||||||
public string Uri { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a position in a text document.
|
|
||||||
/// </summary>
|
|
||||||
[DebuggerDisplay("TextDocumentPosition = {Position.Line}:{Position.Character}")]
|
|
||||||
public class TextDocumentPosition : TextDocumentIdentifier
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the position in the document.
|
|
||||||
/// </summary>
|
|
||||||
public Position Position { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DidOpenTextDocumentNotification : TextDocumentIdentifier
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
EventType<DidOpenTextDocumentNotification> Type =
|
|
||||||
EventType<DidOpenTextDocumentNotification>.Create("textDocument/didOpen");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the full content of the opened document.
|
|
||||||
/// </summary>
|
|
||||||
public string Text { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DidCloseTextDocumentNotification
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
EventType<TextDocumentIdentifier> Type =
|
|
||||||
EventType<TextDocumentIdentifier>.Create("textDocument/didClose");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DidChangeTextDocumentNotification
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
EventType<DidChangeTextDocumentParams> Type =
|
|
||||||
EventType<DidChangeTextDocumentParams>.Create("textDocument/didChange");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DidChangeTextDocumentParams : TextDocumentIdentifier
|
|
||||||
{
|
|
||||||
public TextDocumentUriChangeEvent TextDocument { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of changes to the document content.
|
|
||||||
/// </summary>
|
|
||||||
public TextDocumentChangeEvent[] ContentChanges { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TextDocumentUriChangeEvent
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Uri of the changed text document
|
|
||||||
/// </summary>
|
|
||||||
public string Uri { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Version of the changed text document
|
|
||||||
/// </summary>
|
|
||||||
public int Version { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TextDocumentChangeEvent
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Range where the document was changed. Will
|
|
||||||
/// be null if the server's TextDocumentSyncKind is Full.
|
|
||||||
/// </summary>
|
|
||||||
public Range? Range { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the length of the Range being replaced in the
|
|
||||||
/// document. Will be null if the server's TextDocumentSyncKind is
|
|
||||||
/// Full.
|
|
||||||
/// </summary>
|
|
||||||
public int? RangeLength { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the new text of the document.
|
|
||||||
/// </summary>
|
|
||||||
public string Text { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DebuggerDisplay("Position = {Line}:{Character}")]
|
|
||||||
public class Position
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the zero-based line number.
|
|
||||||
/// </summary>
|
|
||||||
public int Line { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the zero-based column number.
|
|
||||||
/// </summary>
|
|
||||||
public int Character { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DebuggerDisplay("Start = {Start.Line}:{Start.Character}, End = {End.Line}:{End.Character}")]
|
|
||||||
public struct Range
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the starting position of the range.
|
|
||||||
/// </summary>
|
|
||||||
public Position Start { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the ending position of the range.
|
|
||||||
/// </summary>
|
|
||||||
public Position End { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[DebuggerDisplay("Range = {Range.Start.Line}:{Range.Start.Character} - {Range.End.Line}:{Range.End.Character}, Uri = {Uri}")]
|
|
||||||
public class Location
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the URI indicating the file in which the location refers.
|
|
||||||
/// </summary>
|
|
||||||
public string Uri { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Range indicating the range in which location refers.
|
|
||||||
/// </summary>
|
|
||||||
public Range Range { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum FileChangeType
|
|
||||||
{
|
|
||||||
Created = 1,
|
|
||||||
|
|
||||||
Changed,
|
|
||||||
|
|
||||||
Deleted
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FileEvent
|
|
||||||
{
|
|
||||||
public string Uri { get; set; }
|
|
||||||
|
|
||||||
public FileChangeType Type { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.LanguageServer
|
|
||||||
{
|
|
||||||
public enum SymbolKind
|
|
||||||
{
|
|
||||||
File = 1,
|
|
||||||
Module = 2,
|
|
||||||
Namespace = 3,
|
|
||||||
Package = 4,
|
|
||||||
Class = 5,
|
|
||||||
Method = 6,
|
|
||||||
Property = 7,
|
|
||||||
Field = 8,
|
|
||||||
Constructor = 9,
|
|
||||||
Enum = 10,
|
|
||||||
Interface = 11,
|
|
||||||
Function = 12,
|
|
||||||
Variable = 13,
|
|
||||||
Constant = 14,
|
|
||||||
String = 15,
|
|
||||||
Number = 16,
|
|
||||||
Boolean = 17,
|
|
||||||
Array = 18,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SymbolInformation
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public SymbolKind Kind { get; set; }
|
|
||||||
|
|
||||||
public Location Location { get; set; }
|
|
||||||
|
|
||||||
public string ContainerName { get; set;}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DocumentSymbolRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<TextDocumentIdentifier, SymbolInformation[]> Type =
|
|
||||||
RequestType<TextDocumentIdentifier, SymbolInformation[]>.Create("textDocument/documentSymbol");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WorkspaceSymbolRequest
|
|
||||||
{
|
|
||||||
public static readonly
|
|
||||||
RequestType<WorkspaceSymbolParams, SymbolInformation[]> Type =
|
|
||||||
RequestType<WorkspaceSymbolParams, SymbolInformation[]>.Create("workspace/symbol");
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WorkspaceSymbolParams
|
|
||||||
{
|
|
||||||
public string Query { get; set;}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Session;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.LanguageSupport
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Main class for Language Service functionality
|
|
||||||
/// </summary>
|
|
||||||
public class LanguageService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the current SQL Tools context
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
private SqlToolsContext Context { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for the Language Service class
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
public LanguageService(SqlToolsContext context)
|
|
||||||
{
|
|
||||||
this.Context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of semantic diagnostic marks for the provided script file
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFile"></param>
|
|
||||||
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
|
||||||
{
|
|
||||||
// the commented out snippet is an example of how to create a error marker
|
|
||||||
// semanticMarkers = new ScriptFileMarker[1];
|
|
||||||
// semanticMarkers[0] = new ScriptFileMarker()
|
|
||||||
// {
|
|
||||||
// Message = "Error message",
|
|
||||||
// Level = ScriptFileMarkerLevel.Error,
|
|
||||||
// ScriptRegion = new ScriptRegion()
|
|
||||||
// {
|
|
||||||
// File = scriptFile.FilePath,
|
|
||||||
// StartLineNumber = 2,
|
|
||||||
// StartColumnNumber = 2,
|
|
||||||
// StartOffset = 0,
|
|
||||||
// EndLineNumber = 4,
|
|
||||||
// EndColumnNumber = 10,
|
|
||||||
// EndOffset = 0
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
return new ScriptFileMarker[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Serializers;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a base implementation for servers and their clients over a
|
|
||||||
/// single kind of communication channel.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ChannelBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a boolean that is true if the channel is connected or false if not.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsConnected { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the MessageReader for reading messages from the channel.
|
|
||||||
/// </summary>
|
|
||||||
public MessageReader MessageReader { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the MessageWriter for writing messages to the channel.
|
|
||||||
/// </summary>
|
|
||||||
public MessageWriter MessageWriter { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts the channel and initializes the MessageDispatcher.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="messageProtocolType">The type of message protocol used by the channel.</param>
|
|
||||||
public void Start(MessageProtocolType messageProtocolType)
|
|
||||||
{
|
|
||||||
IMessageSerializer messageSerializer = null;
|
|
||||||
if (messageProtocolType == MessageProtocolType.LanguageServer)
|
|
||||||
{
|
|
||||||
messageSerializer = new JsonRpcMessageSerializer();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
messageSerializer = new V8MessageSerializer();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Initialize(messageSerializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a Task that allows the consumer of the ChannelBase
|
|
||||||
/// implementation to wait until a connection has been made to
|
|
||||||
/// the opposite endpoint whether it's a client or server.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A Task to be awaited until a connection is made.</returns>
|
|
||||||
public abstract Task WaitForConnection();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops the channel.
|
|
||||||
/// </summary>
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
this.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A method to be implemented by subclasses to handle the
|
|
||||||
/// actual initialization of the channel and the creation and
|
|
||||||
/// assignment of the MessageReader and MessageWriter properties.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="messageSerializer">The IMessageSerializer to use for message serialization.</param>
|
|
||||||
protected abstract void Initialize(IMessageSerializer messageSerializer);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A method to be implemented by subclasses to handle shutdown
|
|
||||||
/// of the channel once Stop is called.
|
|
||||||
/// </summary>
|
|
||||||
protected abstract void Shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a client implementation for the standard I/O channel.
|
|
||||||
/// Launches the server process and then attaches to its console
|
|
||||||
/// streams.
|
|
||||||
/// </summary>
|
|
||||||
public class StdioClientChannel : ChannelBase
|
|
||||||
{
|
|
||||||
private string serviceProcessPath;
|
|
||||||
private string serviceProcessArguments;
|
|
||||||
|
|
||||||
private Stream inputStream;
|
|
||||||
private Stream outputStream;
|
|
||||||
private Process serviceProcess;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the process ID of the server process.
|
|
||||||
/// </summary>
|
|
||||||
public int ProcessId { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of the StdioClient.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serverProcessPath">The full path to the server process executable.</param>
|
|
||||||
/// <param name="serverProcessArguments">Optional arguments to pass to the service process executable.</param>
|
|
||||||
public StdioClientChannel(
|
|
||||||
string serverProcessPath,
|
|
||||||
params string[] serverProcessArguments)
|
|
||||||
{
|
|
||||||
this.serviceProcessPath = serverProcessPath;
|
|
||||||
|
|
||||||
if (serverProcessArguments != null)
|
|
||||||
{
|
|
||||||
this.serviceProcessArguments =
|
|
||||||
string.Join(
|
|
||||||
" ",
|
|
||||||
serverProcessArguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Initialize(IMessageSerializer messageSerializer)
|
|
||||||
{
|
|
||||||
this.serviceProcess = new Process
|
|
||||||
{
|
|
||||||
StartInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = this.serviceProcessPath,
|
|
||||||
Arguments = this.serviceProcessArguments,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardInput = true,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
StandardOutputEncoding = Encoding.UTF8,
|
|
||||||
},
|
|
||||||
EnableRaisingEvents = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Start the process
|
|
||||||
this.serviceProcess.Start();
|
|
||||||
this.ProcessId = this.serviceProcess.Id;
|
|
||||||
|
|
||||||
// Open the standard input/output streams
|
|
||||||
this.inputStream = this.serviceProcess.StandardOutput.BaseStream;
|
|
||||||
this.outputStream = this.serviceProcess.StandardInput.BaseStream;
|
|
||||||
|
|
||||||
// Set up the message reader and writer
|
|
||||||
this.MessageReader =
|
|
||||||
new MessageReader(
|
|
||||||
this.inputStream,
|
|
||||||
messageSerializer);
|
|
||||||
|
|
||||||
this.MessageWriter =
|
|
||||||
new MessageWriter(
|
|
||||||
this.outputStream,
|
|
||||||
messageSerializer);
|
|
||||||
|
|
||||||
this.IsConnected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task WaitForConnection()
|
|
||||||
{
|
|
||||||
// We're always connected immediately in the stdio channel
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Shutdown()
|
|
||||||
{
|
|
||||||
if (this.inputStream != null)
|
|
||||||
{
|
|
||||||
this.inputStream.Dispose();
|
|
||||||
this.inputStream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.outputStream != null)
|
|
||||||
{
|
|
||||||
this.outputStream.Dispose();
|
|
||||||
this.outputStream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.MessageReader != null)
|
|
||||||
{
|
|
||||||
this.MessageReader = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.MessageWriter != null)
|
|
||||||
{
|
|
||||||
this.MessageWriter = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.serviceProcess.Kill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a server implementation for the standard I/O channel.
|
|
||||||
/// When started in a process, attaches to the console I/O streams
|
|
||||||
/// to communicate with the client that launched the process.
|
|
||||||
/// </summary>
|
|
||||||
public class StdioServerChannel : ChannelBase
|
|
||||||
{
|
|
||||||
private Stream inputStream;
|
|
||||||
private Stream outputStream;
|
|
||||||
|
|
||||||
protected override void Initialize(IMessageSerializer messageSerializer)
|
|
||||||
{
|
|
||||||
#if !NanoServer
|
|
||||||
// Ensure that the console is using UTF-8 encoding
|
|
||||||
System.Console.InputEncoding = Encoding.UTF8;
|
|
||||||
System.Console.OutputEncoding = Encoding.UTF8;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Open the standard input/output streams
|
|
||||||
this.inputStream = System.Console.OpenStandardInput();
|
|
||||||
this.outputStream = System.Console.OpenStandardOutput();
|
|
||||||
|
|
||||||
// Set up the reader and writer
|
|
||||||
this.MessageReader =
|
|
||||||
new MessageReader(
|
|
||||||
this.inputStream,
|
|
||||||
messageSerializer);
|
|
||||||
|
|
||||||
this.MessageWriter =
|
|
||||||
new MessageWriter(
|
|
||||||
this.outputStream,
|
|
||||||
messageSerializer);
|
|
||||||
|
|
||||||
this.IsConnected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task WaitForConnection()
|
|
||||||
{
|
|
||||||
// We're always connected immediately in the stdio channel
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Shutdown()
|
|
||||||
{
|
|
||||||
// No default implementation needed, streams will be
|
|
||||||
// disposed on process shutdown.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
public static class Constants
|
|
||||||
{
|
|
||||||
public const string ContentLengthFormatString = "Content-Length: {0}\r\n\r\n";
|
|
||||||
public static readonly JsonSerializerSettings JsonSerializerSettings;
|
|
||||||
|
|
||||||
static Constants()
|
|
||||||
{
|
|
||||||
JsonSerializerSettings = new JsonSerializerSettings();
|
|
||||||
|
|
||||||
// Camel case all object properties
|
|
||||||
JsonSerializerSettings.ContractResolver =
|
|
||||||
new CamelCasePropertyNamesContractResolver();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides context for a received event so that handlers
|
|
||||||
/// can write events back to the channel.
|
|
||||||
/// </summary>
|
|
||||||
public class EventContext
|
|
||||||
{
|
|
||||||
private MessageWriter messageWriter;
|
|
||||||
|
|
||||||
public EventContext(MessageWriter messageWriter)
|
|
||||||
{
|
|
||||||
this.messageWriter = messageWriter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendEvent<TParams>(
|
|
||||||
EventType<TParams> eventType,
|
|
||||||
TParams eventParams)
|
|
||||||
{
|
|
||||||
await this.messageWriter.WriteEvent(
|
|
||||||
eventType,
|
|
||||||
eventParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines an event type with a particular method name.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TParams">The parameter type for this event.</typeparam>
|
|
||||||
public class EventType<TParams>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the method name for the event type.
|
|
||||||
/// </summary>
|
|
||||||
public string MethodName { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an EventType instance with the given parameter type and method name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="methodName">The method name of the event.</param>
|
|
||||||
/// <returns>A new EventType instance for the defined type.</returns>
|
|
||||||
public static EventType<TParams> Create(string methodName)
|
|
||||||
{
|
|
||||||
return new EventType<TParams>()
|
|
||||||
{
|
|
||||||
MethodName = methodName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
internal interface IMessageSender
|
|
||||||
{
|
|
||||||
Task SendEvent<TParams>(
|
|
||||||
EventType<TParams> eventType,
|
|
||||||
TParams eventParams);
|
|
||||||
|
|
||||||
Task<TResult> SendRequest<TParams, TResult>(
|
|
||||||
RequestType<TParams, TResult> requestType,
|
|
||||||
TParams requestParams,
|
|
||||||
bool waitForResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines a common interface for message serializers.
|
|
||||||
/// </summary>
|
|
||||||
public interface IMessageSerializer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Serializes a Message to a JObject.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">The message to be serialized.</param>
|
|
||||||
/// <returns>A JObject which contains the JSON representation of the message.</returns>
|
|
||||||
JObject SerializeMessage(Message message);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserializes a JObject to a Messsage.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="messageJson">The JObject containing the JSON representation of the message.</param>
|
|
||||||
/// <returns>The Message that was represented by the JObject.</returns>
|
|
||||||
Message DeserializeMessage(JObject messageJson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines all possible message types.
|
|
||||||
/// </summary>
|
|
||||||
public enum MessageType
|
|
||||||
{
|
|
||||||
Unknown,
|
|
||||||
Request,
|
|
||||||
Response,
|
|
||||||
Event
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides common details for protocol messages of any format.
|
|
||||||
/// </summary>
|
|
||||||
[DebuggerDisplay("MessageType = {MessageType.ToString()}, Method = {Method}, Id = {Id}")]
|
|
||||||
public class Message
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the message type.
|
|
||||||
/// </summary>
|
|
||||||
public MessageType MessageType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the message's sequence ID.
|
|
||||||
/// </summary>
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the message's method/command name.
|
|
||||||
/// </summary>
|
|
||||||
public string Method { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a JToken containing the contents of the message.
|
|
||||||
/// </summary>
|
|
||||||
public JToken Contents { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a JToken containing error details.
|
|
||||||
/// </summary>
|
|
||||||
public JToken Error { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a message with an Unknown type.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A message with Unknown type.</returns>
|
|
||||||
public static Message Unknown()
|
|
||||||
{
|
|
||||||
return new Message
|
|
||||||
{
|
|
||||||
MessageType = MessageType.Unknown
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a message with a Request type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The sequence ID of the request.</param>
|
|
||||||
/// <param name="method">The method name of the request.</param>
|
|
||||||
/// <param name="contents">The contents of the request.</param>
|
|
||||||
/// <returns>A message with a Request type.</returns>
|
|
||||||
public static Message Request(string id, string method, JToken contents)
|
|
||||||
{
|
|
||||||
return new Message
|
|
||||||
{
|
|
||||||
MessageType = MessageType.Request,
|
|
||||||
Id = id,
|
|
||||||
Method = method,
|
|
||||||
Contents = contents
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a message with a Response type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The sequence ID of the original request.</param>
|
|
||||||
/// <param name="method">The method name of the original request.</param>
|
|
||||||
/// <param name="contents">The contents of the response.</param>
|
|
||||||
/// <returns>A message with a Response type.</returns>
|
|
||||||
public static Message Response(string id, string method, JToken contents)
|
|
||||||
{
|
|
||||||
return new Message
|
|
||||||
{
|
|
||||||
MessageType = MessageType.Response,
|
|
||||||
Id = id,
|
|
||||||
Method = method,
|
|
||||||
Contents = contents
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a message with a Response type and error details.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The sequence ID of the original request.</param>
|
|
||||||
/// <param name="method">The method name of the original request.</param>
|
|
||||||
/// <param name="error">The error details of the response.</param>
|
|
||||||
/// <returns>A message with a Response type and error details.</returns>
|
|
||||||
public static Message ResponseError(string id, string method, JToken error)
|
|
||||||
{
|
|
||||||
return new Message
|
|
||||||
{
|
|
||||||
MessageType = MessageType.Response,
|
|
||||||
Id = id,
|
|
||||||
Method = method,
|
|
||||||
Error = error
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a message with an Event type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">The method name of the event.</param>
|
|
||||||
/// <param name="contents">The contents of the event.</param>
|
|
||||||
/// <returns>A message with an Event type.</returns>
|
|
||||||
public static Message Event(string method, JToken contents)
|
|
||||||
{
|
|
||||||
return new Message
|
|
||||||
{
|
|
||||||
MessageType = MessageType.Event,
|
|
||||||
Method = method,
|
|
||||||
Contents = contents
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
public class MessageDispatcher
|
|
||||||
{
|
|
||||||
#region Fields
|
|
||||||
|
|
||||||
private ChannelBase protocolChannel;
|
|
||||||
|
|
||||||
private AsyncContextThread messageLoopThread;
|
|
||||||
|
|
||||||
private Dictionary<string, Func<Message, MessageWriter, Task>> requestHandlers =
|
|
||||||
new Dictionary<string, Func<Message, MessageWriter, Task>>();
|
|
||||||
|
|
||||||
private Dictionary<string, Func<Message, MessageWriter, Task>> eventHandlers =
|
|
||||||
new Dictionary<string, Func<Message, MessageWriter, Task>>();
|
|
||||||
|
|
||||||
private Action<Message> responseHandler;
|
|
||||||
|
|
||||||
private CancellationTokenSource messageLoopCancellationToken =
|
|
||||||
new CancellationTokenSource();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
public SynchronizationContext SynchronizationContext { get; private set; }
|
|
||||||
|
|
||||||
public bool InMessageLoopThread
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
// We're in the same thread as the message loop if the
|
|
||||||
// current synchronization context equals the one we
|
|
||||||
// know.
|
|
||||||
return SynchronizationContext.Current == this.SynchronizationContext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MessageReader MessageReader { get; private set; }
|
|
||||||
|
|
||||||
protected MessageWriter MessageWriter { get; private set; }
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
public MessageDispatcher(ChannelBase protocolChannel)
|
|
||||||
{
|
|
||||||
this.protocolChannel = protocolChannel;
|
|
||||||
this.MessageReader = protocolChannel.MessageReader;
|
|
||||||
this.MessageWriter = protocolChannel.MessageWriter;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
// Start the main message loop thread. The Task is
|
|
||||||
// not explicitly awaited because it is running on
|
|
||||||
// an independent background thread.
|
|
||||||
this.messageLoopThread = new AsyncContextThread("Message Dispatcher");
|
|
||||||
this.messageLoopThread
|
|
||||||
.Run(() => this.ListenForMessages(this.messageLoopCancellationToken.Token))
|
|
||||||
.ContinueWith(this.OnListenTaskCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
// Stop the message loop thread
|
|
||||||
if (this.messageLoopThread != null)
|
|
||||||
{
|
|
||||||
this.messageLoopCancellationToken.Cancel();
|
|
||||||
this.messageLoopThread.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetRequestHandler<TParams, TResult>(
|
|
||||||
RequestType<TParams, TResult> requestType,
|
|
||||||
Func<TParams, RequestContext<TResult>, Task> requestHandler)
|
|
||||||
{
|
|
||||||
this.SetRequestHandler(
|
|
||||||
requestType,
|
|
||||||
requestHandler,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetRequestHandler<TParams, TResult>(
|
|
||||||
RequestType<TParams, TResult> requestType,
|
|
||||||
Func<TParams, RequestContext<TResult>, Task> requestHandler,
|
|
||||||
bool overrideExisting)
|
|
||||||
{
|
|
||||||
if (overrideExisting)
|
|
||||||
{
|
|
||||||
// Remove the existing handler so a new one can be set
|
|
||||||
this.requestHandlers.Remove(requestType.MethodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requestHandlers.Add(
|
|
||||||
requestType.MethodName,
|
|
||||||
(requestMessage, messageWriter) =>
|
|
||||||
{
|
|
||||||
var requestContext =
|
|
||||||
new RequestContext<TResult>(
|
|
||||||
requestMessage,
|
|
||||||
messageWriter);
|
|
||||||
|
|
||||||
TParams typedParams = default(TParams);
|
|
||||||
if (requestMessage.Contents != null)
|
|
||||||
{
|
|
||||||
// TODO: Catch parse errors!
|
|
||||||
typedParams = requestMessage.Contents.ToObject<TParams>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestHandler(typedParams, requestContext);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEventHandler<TParams>(
|
|
||||||
EventType<TParams> eventType,
|
|
||||||
Func<TParams, EventContext, Task> eventHandler)
|
|
||||||
{
|
|
||||||
this.SetEventHandler(
|
|
||||||
eventType,
|
|
||||||
eventHandler,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEventHandler<TParams>(
|
|
||||||
EventType<TParams> eventType,
|
|
||||||
Func<TParams, EventContext, Task> eventHandler,
|
|
||||||
bool overrideExisting)
|
|
||||||
{
|
|
||||||
if (overrideExisting)
|
|
||||||
{
|
|
||||||
// Remove the existing handler so a new one can be set
|
|
||||||
this.eventHandlers.Remove(eventType.MethodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.eventHandlers.Add(
|
|
||||||
eventType.MethodName,
|
|
||||||
(eventMessage, messageWriter) =>
|
|
||||||
{
|
|
||||||
var eventContext = new EventContext(messageWriter);
|
|
||||||
|
|
||||||
TParams typedParams = default(TParams);
|
|
||||||
if (eventMessage.Contents != null)
|
|
||||||
{
|
|
||||||
// TODO: Catch parse errors!
|
|
||||||
typedParams = eventMessage.Contents.ToObject<TParams>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return eventHandler(typedParams, eventContext);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetResponseHandler(Action<Message> responseHandler)
|
|
||||||
{
|
|
||||||
this.responseHandler = responseHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Events
|
|
||||||
|
|
||||||
public event EventHandler<Exception> UnhandledException;
|
|
||||||
|
|
||||||
protected void OnUnhandledException(Exception unhandledException)
|
|
||||||
{
|
|
||||||
if (this.UnhandledException != null)
|
|
||||||
{
|
|
||||||
this.UnhandledException(this, unhandledException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private async Task ListenForMessages(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
this.SynchronizationContext = SynchronizationContext.Current;
|
|
||||||
|
|
||||||
// Run the message loop
|
|
||||||
bool isRunning = true;
|
|
||||||
while (isRunning && !cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Message newMessage = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Read a message from the channel
|
|
||||||
newMessage = await this.MessageReader.ReadMessage();
|
|
||||||
}
|
|
||||||
catch (MessageParseException e)
|
|
||||||
{
|
|
||||||
// TODO: Write an error response
|
|
||||||
|
|
||||||
Logger.Write(
|
|
||||||
LogLevel.Error,
|
|
||||||
"Could not parse a message that was received:\r\n\r\n" +
|
|
||||||
e.ToString());
|
|
||||||
|
|
||||||
// Continue the loop
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
catch (EndOfStreamException)
|
|
||||||
{
|
|
||||||
// The stream has ended, end the message loop
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
var b = e.Message;
|
|
||||||
newMessage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The message could be null if there was an error parsing the
|
|
||||||
// previous message. In this case, do not try to dispatch it.
|
|
||||||
if (newMessage != null)
|
|
||||||
{
|
|
||||||
// Process the message
|
|
||||||
await this.DispatchMessage(
|
|
||||||
newMessage,
|
|
||||||
this.MessageWriter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task DispatchMessage(
|
|
||||||
Message messageToDispatch,
|
|
||||||
MessageWriter messageWriter)
|
|
||||||
{
|
|
||||||
Task handlerToAwait = null;
|
|
||||||
|
|
||||||
if (messageToDispatch.MessageType == MessageType.Request)
|
|
||||||
{
|
|
||||||
Func<Message, MessageWriter, Task> requestHandler = null;
|
|
||||||
if (this.requestHandlers.TryGetValue(messageToDispatch.Method, out requestHandler))
|
|
||||||
{
|
|
||||||
handlerToAwait = requestHandler(messageToDispatch, messageWriter);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: Message not supported error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (messageToDispatch.MessageType == MessageType.Response)
|
|
||||||
{
|
|
||||||
if (this.responseHandler != null)
|
|
||||||
{
|
|
||||||
this.responseHandler(messageToDispatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (messageToDispatch.MessageType == MessageType.Event)
|
|
||||||
{
|
|
||||||
Func<Message, MessageWriter, Task> eventHandler = null;
|
|
||||||
if (this.eventHandlers.TryGetValue(messageToDispatch.Method, out eventHandler))
|
|
||||||
{
|
|
||||||
handlerToAwait = eventHandler(messageToDispatch, messageWriter);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: Message not supported error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: Return message not supported
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handlerToAwait != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await handlerToAwait;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
// Some tasks may be cancelled due to legitimate
|
|
||||||
// timeouts so don't let those exceptions go higher.
|
|
||||||
}
|
|
||||||
catch (AggregateException e)
|
|
||||||
{
|
|
||||||
if (!(e.InnerExceptions[0] is TaskCanceledException))
|
|
||||||
{
|
|
||||||
// Cancelled tasks aren't a problem, so rethrow
|
|
||||||
// anything that isn't a TaskCanceledException
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnListenTaskCompleted(Task listenTask)
|
|
||||||
{
|
|
||||||
if (listenTask.IsFaulted)
|
|
||||||
{
|
|
||||||
this.OnUnhandledException(listenTask.Exception);
|
|
||||||
}
|
|
||||||
else if (listenTask.IsCompleted || listenTask.IsCanceled)
|
|
||||||
{
|
|
||||||
// TODO: Dispose of anything?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
public class MessageParseException : Exception
|
|
||||||
{
|
|
||||||
public string OriginalMessageText { get; private set; }
|
|
||||||
|
|
||||||
public MessageParseException(
|
|
||||||
string originalMessageText,
|
|
||||||
string errorMessage,
|
|
||||||
params object[] errorMessageArgs)
|
|
||||||
: base(string.Format(errorMessage, errorMessageArgs))
|
|
||||||
{
|
|
||||||
this.OriginalMessageText = originalMessageText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the possible message protocol types.
|
|
||||||
/// </summary>
|
|
||||||
public enum MessageProtocolType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Identifies the language server message protocol.
|
|
||||||
/// </summary>
|
|
||||||
LanguageServer,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Identifies the debug adapter message protocol.
|
|
||||||
/// </summary>
|
|
||||||
DebugAdapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
public class MessageReader
|
|
||||||
{
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
public const int DefaultBufferSize = 8192;
|
|
||||||
public const double BufferResizeTrigger = 0.25;
|
|
||||||
|
|
||||||
private const int CR = 0x0D;
|
|
||||||
private const int LF = 0x0A;
|
|
||||||
private static string[] NewLineDelimiters = new string[] { Environment.NewLine };
|
|
||||||
|
|
||||||
private Stream inputStream;
|
|
||||||
private IMessageSerializer messageSerializer;
|
|
||||||
private Encoding messageEncoding;
|
|
||||||
|
|
||||||
private ReadState readState;
|
|
||||||
private bool needsMoreData = true;
|
|
||||||
private int readOffset;
|
|
||||||
private int bufferEndOffset;
|
|
||||||
private byte[] messageBuffer = new byte[DefaultBufferSize];
|
|
||||||
|
|
||||||
private int expectedContentLength;
|
|
||||||
private Dictionary<string, string> messageHeaders;
|
|
||||||
|
|
||||||
enum ReadState
|
|
||||||
{
|
|
||||||
Headers,
|
|
||||||
Content
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
public MessageReader(
|
|
||||||
Stream inputStream,
|
|
||||||
IMessageSerializer messageSerializer,
|
|
||||||
Encoding messageEncoding = null)
|
|
||||||
{
|
|
||||||
Validate.IsNotNull("streamReader", inputStream);
|
|
||||||
Validate.IsNotNull("messageSerializer", messageSerializer);
|
|
||||||
|
|
||||||
this.inputStream = inputStream;
|
|
||||||
this.messageSerializer = messageSerializer;
|
|
||||||
|
|
||||||
this.messageEncoding = messageEncoding;
|
|
||||||
if (messageEncoding == null)
|
|
||||||
{
|
|
||||||
this.messageEncoding = Encoding.UTF8;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.messageBuffer = new byte[DefaultBufferSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
public async Task<Message> ReadMessage()
|
|
||||||
{
|
|
||||||
string messageContent = null;
|
|
||||||
|
|
||||||
// Do we need to read more data or can we process the existing buffer?
|
|
||||||
while (!this.needsMoreData || await this.ReadNextChunk())
|
|
||||||
{
|
|
||||||
// Clear the flag since we should have what we need now
|
|
||||||
this.needsMoreData = false;
|
|
||||||
|
|
||||||
// Do we need to look for message headers?
|
|
||||||
if (this.readState == ReadState.Headers &&
|
|
||||||
!this.TryReadMessageHeaders())
|
|
||||||
{
|
|
||||||
// If we don't have enough data to read headers yet, keep reading
|
|
||||||
this.needsMoreData = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we need to look for message content?
|
|
||||||
if (this.readState == ReadState.Content &&
|
|
||||||
!this.TryReadMessageContent(out messageContent))
|
|
||||||
{
|
|
||||||
// If we don't have enough data yet to construct the content, keep reading
|
|
||||||
this.needsMoreData = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We've read a message now, break out of the loop
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the JObject for the JSON content
|
|
||||||
JObject messageObject = JObject.Parse(messageContent);
|
|
||||||
|
|
||||||
// Load the message
|
|
||||||
Logger.Write(
|
|
||||||
LogLevel.Verbose,
|
|
||||||
string.Format(
|
|
||||||
"READ MESSAGE:\r\n\r\n{0}",
|
|
||||||
messageObject.ToString(Formatting.Indented)));
|
|
||||||
|
|
||||||
// Return the parsed message
|
|
||||||
return this.messageSerializer.DeserializeMessage(messageObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private async Task<bool> ReadNextChunk()
|
|
||||||
{
|
|
||||||
// Do we need to resize the buffer? See if less than 1/4 of the space is left.
|
|
||||||
if (((double)(this.messageBuffer.Length - this.bufferEndOffset) / this.messageBuffer.Length) < 0.25)
|
|
||||||
{
|
|
||||||
// Double the size of the buffer
|
|
||||||
Array.Resize(
|
|
||||||
ref this.messageBuffer,
|
|
||||||
this.messageBuffer.Length * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the next chunk into the message buffer
|
|
||||||
int readLength =
|
|
||||||
await this.inputStream.ReadAsync(
|
|
||||||
this.messageBuffer,
|
|
||||||
this.bufferEndOffset,
|
|
||||||
this.messageBuffer.Length - this.bufferEndOffset);
|
|
||||||
|
|
||||||
this.bufferEndOffset += readLength;
|
|
||||||
|
|
||||||
if (readLength == 0)
|
|
||||||
{
|
|
||||||
// If ReadAsync returns 0 then it means that the stream was
|
|
||||||
// closed unexpectedly (usually due to the client application
|
|
||||||
// ending suddenly). For now, just terminate the language
|
|
||||||
// server immediately.
|
|
||||||
// TODO: Provide a more graceful shutdown path
|
|
||||||
throw new EndOfStreamException(
|
|
||||||
"MessageReader's input stream ended unexpectedly, terminating.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryReadMessageHeaders()
|
|
||||||
{
|
|
||||||
int scanOffset = this.readOffset;
|
|
||||||
|
|
||||||
// Scan for the final double-newline that marks the
|
|
||||||
// end of the header lines
|
|
||||||
while (scanOffset + 3 < this.bufferEndOffset &&
|
|
||||||
(this.messageBuffer[scanOffset] != CR ||
|
|
||||||
this.messageBuffer[scanOffset + 1] != LF ||
|
|
||||||
this.messageBuffer[scanOffset + 2] != CR ||
|
|
||||||
this.messageBuffer[scanOffset + 3] != LF))
|
|
||||||
{
|
|
||||||
scanOffset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No header or body separator found (e.g CRLFCRLF)
|
|
||||||
if (scanOffset + 3 >= this.bufferEndOffset)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.messageHeaders = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
var headers =
|
|
||||||
Encoding.ASCII
|
|
||||||
.GetString(this.messageBuffer, this.readOffset, scanOffset)
|
|
||||||
.Split(NewLineDelimiters, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
// Read each header and store it in the dictionary
|
|
||||||
foreach (var header in headers)
|
|
||||||
{
|
|
||||||
int currentLength = header.IndexOf(':');
|
|
||||||
if (currentLength == -1)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Message header must separate key and value using :");
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = header.Substring(0, currentLength);
|
|
||||||
var value = header.Substring(currentLength + 1).Trim();
|
|
||||||
this.messageHeaders[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure a Content-Length header was present, otherwise it
|
|
||||||
// is a fatal error
|
|
||||||
string contentLengthString = null;
|
|
||||||
if (!this.messageHeaders.TryGetValue("Content-Length", out contentLengthString))
|
|
||||||
{
|
|
||||||
throw new MessageParseException("", "Fatal error: Content-Length header must be provided.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the content length to an integer
|
|
||||||
if (!int.TryParse(contentLengthString, out this.expectedContentLength))
|
|
||||||
{
|
|
||||||
throw new MessageParseException("", "Fatal error: Content-Length value is not an integer.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip past the headers plus the newline characters
|
|
||||||
this.readOffset += scanOffset + 4;
|
|
||||||
|
|
||||||
// Done reading headers, now read content
|
|
||||||
this.readState = ReadState.Content;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryReadMessageContent(out string messageContent)
|
|
||||||
{
|
|
||||||
messageContent = null;
|
|
||||||
|
|
||||||
// Do we have enough bytes to reach the expected length?
|
|
||||||
if ((this.bufferEndOffset - this.readOffset) < this.expectedContentLength)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the message contents to a string using the specified encoding
|
|
||||||
messageContent =
|
|
||||||
this.messageEncoding.GetString(
|
|
||||||
this.messageBuffer,
|
|
||||||
this.readOffset,
|
|
||||||
this.expectedContentLength);
|
|
||||||
|
|
||||||
// Move the remaining bytes to the front of the buffer for the next message
|
|
||||||
var remainingByteCount = this.bufferEndOffset - (this.expectedContentLength + this.readOffset);
|
|
||||||
Buffer.BlockCopy(
|
|
||||||
this.messageBuffer,
|
|
||||||
this.expectedContentLength + this.readOffset,
|
|
||||||
this.messageBuffer,
|
|
||||||
0,
|
|
||||||
remainingByteCount);
|
|
||||||
|
|
||||||
// Reset the offsets for the next read
|
|
||||||
this.readOffset = 0;
|
|
||||||
this.bufferEndOffset = remainingByteCount;
|
|
||||||
|
|
||||||
// Done reading content, now look for headers
|
|
||||||
this.readState = ReadState.Headers;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
public class MessageWriter
|
|
||||||
{
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
private Stream outputStream;
|
|
||||||
private IMessageSerializer messageSerializer;
|
|
||||||
private AsyncLock writeLock = new AsyncLock();
|
|
||||||
|
|
||||||
private JsonSerializer contentSerializer =
|
|
||||||
JsonSerializer.Create(
|
|
||||||
Constants.JsonSerializerSettings);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
public MessageWriter(
|
|
||||||
Stream outputStream,
|
|
||||||
IMessageSerializer messageSerializer)
|
|
||||||
{
|
|
||||||
Validate.IsNotNull("streamWriter", outputStream);
|
|
||||||
Validate.IsNotNull("messageSerializer", messageSerializer);
|
|
||||||
|
|
||||||
this.outputStream = outputStream;
|
|
||||||
this.messageSerializer = messageSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
// TODO: This method should be made protected or private
|
|
||||||
|
|
||||||
public async Task WriteMessage(Message messageToWrite)
|
|
||||||
{
|
|
||||||
Validate.IsNotNull("messageToWrite", messageToWrite);
|
|
||||||
|
|
||||||
// Serialize the message
|
|
||||||
JObject messageObject =
|
|
||||||
this.messageSerializer.SerializeMessage(
|
|
||||||
messageToWrite);
|
|
||||||
|
|
||||||
// Log the JSON representation of the message
|
|
||||||
Logger.Write(
|
|
||||||
LogLevel.Verbose,
|
|
||||||
string.Format(
|
|
||||||
"WRITE MESSAGE:\r\n\r\n{0}",
|
|
||||||
JsonConvert.SerializeObject(
|
|
||||||
messageObject,
|
|
||||||
Formatting.Indented,
|
|
||||||
Constants.JsonSerializerSettings)));
|
|
||||||
|
|
||||||
string serializedMessage =
|
|
||||||
JsonConvert.SerializeObject(
|
|
||||||
messageObject,
|
|
||||||
Constants.JsonSerializerSettings);
|
|
||||||
|
|
||||||
byte[] messageBytes = Encoding.UTF8.GetBytes(serializedMessage);
|
|
||||||
byte[] headerBytes =
|
|
||||||
Encoding.ASCII.GetBytes(
|
|
||||||
string.Format(
|
|
||||||
Constants.ContentLengthFormatString,
|
|
||||||
messageBytes.Length));
|
|
||||||
|
|
||||||
// Make sure only one call is writing at a time. You might be thinking
|
|
||||||
// "Why not use a normal lock?" We use an AsyncLock here so that the
|
|
||||||
// message loop doesn't get blocked while waiting for I/O to complete.
|
|
||||||
using (await this.writeLock.LockAsync())
|
|
||||||
{
|
|
||||||
// Send the message
|
|
||||||
await this.outputStream.WriteAsync(headerBytes, 0, headerBytes.Length);
|
|
||||||
await this.outputStream.WriteAsync(messageBytes, 0, messageBytes.Length);
|
|
||||||
await this.outputStream.FlushAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WriteRequest<TParams, TResult>(
|
|
||||||
RequestType<TParams, TResult> requestType,
|
|
||||||
TParams requestParams,
|
|
||||||
int requestId)
|
|
||||||
{
|
|
||||||
// Allow null content
|
|
||||||
JToken contentObject =
|
|
||||||
requestParams != null ?
|
|
||||||
JToken.FromObject(requestParams, contentSerializer) :
|
|
||||||
null;
|
|
||||||
|
|
||||||
await this.WriteMessage(
|
|
||||||
Message.Request(
|
|
||||||
requestId.ToString(),
|
|
||||||
requestType.MethodName,
|
|
||||||
contentObject));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WriteResponse<TResult>(TResult resultContent, string method, string requestId)
|
|
||||||
{
|
|
||||||
// Allow null content
|
|
||||||
JToken contentObject =
|
|
||||||
resultContent != null ?
|
|
||||||
JToken.FromObject(resultContent, contentSerializer) :
|
|
||||||
null;
|
|
||||||
|
|
||||||
await this.WriteMessage(
|
|
||||||
Message.Response(
|
|
||||||
requestId,
|
|
||||||
method,
|
|
||||||
contentObject));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WriteEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
|
|
||||||
{
|
|
||||||
// Allow null content
|
|
||||||
JToken contentObject =
|
|
||||||
eventParams != null ?
|
|
||||||
JToken.FromObject(eventParams, contentSerializer) :
|
|
||||||
null;
|
|
||||||
|
|
||||||
await this.WriteMessage(
|
|
||||||
Message.Event(
|
|
||||||
eventType.MethodName,
|
|
||||||
contentObject));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides behavior for a client or server endpoint that
|
|
||||||
/// communicates using the specified protocol.
|
|
||||||
/// </summary>
|
|
||||||
public class ProtocolEndpoint : IMessageSender
|
|
||||||
{
|
|
||||||
private bool isStarted;
|
|
||||||
private int currentMessageId;
|
|
||||||
private ChannelBase protocolChannel;
|
|
||||||
private MessageProtocolType messageProtocolType;
|
|
||||||
private TaskCompletionSource<bool> endpointExitedTask;
|
|
||||||
private SynchronizationContext originalSynchronizationContext;
|
|
||||||
|
|
||||||
private Dictionary<string, TaskCompletionSource<Message>> pendingRequests =
|
|
||||||
new Dictionary<string, TaskCompletionSource<Message>>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the MessageDispatcher which allows registration of
|
|
||||||
/// handlers for requests, responses, and events that are
|
|
||||||
/// transmitted through the channel.
|
|
||||||
/// </summary>
|
|
||||||
protected MessageDispatcher MessageDispatcher { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of the protocol server using the
|
|
||||||
/// specified channel for communication.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="protocolChannel">
|
|
||||||
/// The channel to use for communication with the connected endpoint.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="messageProtocolType">
|
|
||||||
/// The type of message protocol used by the endpoint.
|
|
||||||
/// </param>
|
|
||||||
public ProtocolEndpoint(
|
|
||||||
ChannelBase protocolChannel,
|
|
||||||
MessageProtocolType messageProtocolType)
|
|
||||||
{
|
|
||||||
this.protocolChannel = protocolChannel;
|
|
||||||
this.messageProtocolType = messageProtocolType;
|
|
||||||
this.originalSynchronizationContext = SynchronizationContext.Current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts the language server client and sends the Initialize method.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A Task that can be awaited for initialization to complete.</returns>
|
|
||||||
public async Task Start()
|
|
||||||
{
|
|
||||||
if (!this.isStarted)
|
|
||||||
{
|
|
||||||
// Start the provided protocol channel
|
|
||||||
this.protocolChannel.Start(this.messageProtocolType);
|
|
||||||
|
|
||||||
// Start the message dispatcher
|
|
||||||
this.MessageDispatcher = new MessageDispatcher(this.protocolChannel);
|
|
||||||
|
|
||||||
// Set the handler for any message responses that come back
|
|
||||||
this.MessageDispatcher.SetResponseHandler(this.HandleResponse);
|
|
||||||
|
|
||||||
// Listen for unhandled exceptions from the dispatcher
|
|
||||||
this.MessageDispatcher.UnhandledException += MessageDispatcher_UnhandledException;
|
|
||||||
|
|
||||||
// Notify implementation about endpoint start
|
|
||||||
await this.OnStart();
|
|
||||||
|
|
||||||
// Wait for connection and notify the implementor
|
|
||||||
// NOTE: This task is not meant to be awaited.
|
|
||||||
Task waitTask =
|
|
||||||
this.protocolChannel
|
|
||||||
.WaitForConnection()
|
|
||||||
.ContinueWith(
|
|
||||||
async (t) =>
|
|
||||||
{
|
|
||||||
// Start the MessageDispatcher
|
|
||||||
this.MessageDispatcher.Start();
|
|
||||||
await this.OnConnect();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Endpoint is now started
|
|
||||||
this.isStarted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WaitForExit()
|
|
||||||
{
|
|
||||||
this.endpointExitedTask = new TaskCompletionSource<bool>();
|
|
||||||
this.endpointExitedTask.Task.Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Stop()
|
|
||||||
{
|
|
||||||
if (this.isStarted)
|
|
||||||
{
|
|
||||||
// Make sure no future calls try to stop the endpoint during shutdown
|
|
||||||
this.isStarted = false;
|
|
||||||
|
|
||||||
// Stop the implementation first
|
|
||||||
await this.OnStop();
|
|
||||||
|
|
||||||
// Stop the dispatcher and channel
|
|
||||||
this.MessageDispatcher.Stop();
|
|
||||||
this.protocolChannel.Stop();
|
|
||||||
|
|
||||||
// Notify anyone waiting for exit
|
|
||||||
if (this.endpointExitedTask != null)
|
|
||||||
{
|
|
||||||
this.endpointExitedTask.SetResult(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Message Sending
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a request to the server
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TParams"></typeparam>
|
|
||||||
/// <typeparam name="TResult"></typeparam>
|
|
||||||
/// <param name="requestType"></param>
|
|
||||||
/// <param name="requestParams"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<TResult> SendRequest<TParams, TResult>(
|
|
||||||
RequestType<TParams, TResult> requestType,
|
|
||||||
TParams requestParams)
|
|
||||||
{
|
|
||||||
return this.SendRequest(requestType, requestParams, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<TResult> SendRequest<TParams, TResult>(
|
|
||||||
RequestType<TParams, TResult> requestType,
|
|
||||||
TParams requestParams,
|
|
||||||
bool waitForResponse)
|
|
||||||
{
|
|
||||||
if (!this.protocolChannel.IsConnected)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("SendRequest called when ProtocolChannel was not yet connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentMessageId++;
|
|
||||||
|
|
||||||
TaskCompletionSource<Message> responseTask = null;
|
|
||||||
|
|
||||||
if (waitForResponse)
|
|
||||||
{
|
|
||||||
responseTask = new TaskCompletionSource<Message>();
|
|
||||||
this.pendingRequests.Add(
|
|
||||||
this.currentMessageId.ToString(),
|
|
||||||
responseTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.protocolChannel.MessageWriter.WriteRequest<TParams, TResult>(
|
|
||||||
requestType,
|
|
||||||
requestParams,
|
|
||||||
this.currentMessageId);
|
|
||||||
|
|
||||||
if (responseTask != null)
|
|
||||||
{
|
|
||||||
var responseMessage = await responseTask.Task;
|
|
||||||
|
|
||||||
return
|
|
||||||
responseMessage.Contents != null ?
|
|
||||||
responseMessage.Contents.ToObject<TResult>() :
|
|
||||||
default(TResult);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: Better default value here?
|
|
||||||
return default(TResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends an event to the channel's endpoint.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TParams">The event parameter type.</typeparam>
|
|
||||||
/// <param name="eventType">The type of event being sent.</param>
|
|
||||||
/// <param name="eventParams">The event parameters being sent.</param>
|
|
||||||
/// <returns>A Task that tracks completion of the send operation.</returns>
|
|
||||||
public Task SendEvent<TParams>(
|
|
||||||
EventType<TParams> eventType,
|
|
||||||
TParams eventParams)
|
|
||||||
{
|
|
||||||
if (!this.protocolChannel.IsConnected)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("SendEvent called when ProtocolChannel was not yet connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some events could be raised from a different thread.
|
|
||||||
// To ensure that messages are written serially, dispatch
|
|
||||||
// dispatch the SendEvent call to the message loop thread.
|
|
||||||
|
|
||||||
if (!this.MessageDispatcher.InMessageLoopThread)
|
|
||||||
{
|
|
||||||
TaskCompletionSource<bool> writeTask = new TaskCompletionSource<bool>();
|
|
||||||
|
|
||||||
this.MessageDispatcher.SynchronizationContext.Post(
|
|
||||||
async (obj) =>
|
|
||||||
{
|
|
||||||
await this.protocolChannel.MessageWriter.WriteEvent(
|
|
||||||
eventType,
|
|
||||||
eventParams);
|
|
||||||
|
|
||||||
writeTask.SetResult(true);
|
|
||||||
}, null);
|
|
||||||
|
|
||||||
return writeTask.Task;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return this.protocolChannel.MessageWriter.WriteEvent(
|
|
||||||
eventType,
|
|
||||||
eventParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Message Handling
|
|
||||||
|
|
||||||
public void SetRequestHandler<TParams, TResult>(
|
|
||||||
RequestType<TParams, TResult> requestType,
|
|
||||||
Func<TParams, RequestContext<TResult>, Task> requestHandler)
|
|
||||||
{
|
|
||||||
this.MessageDispatcher.SetRequestHandler(
|
|
||||||
requestType,
|
|
||||||
requestHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEventHandler<TParams>(
|
|
||||||
EventType<TParams> eventType,
|
|
||||||
Func<TParams, EventContext, Task> eventHandler)
|
|
||||||
{
|
|
||||||
this.MessageDispatcher.SetEventHandler(
|
|
||||||
eventType,
|
|
||||||
eventHandler,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEventHandler<TParams>(
|
|
||||||
EventType<TParams> eventType,
|
|
||||||
Func<TParams, EventContext, Task> eventHandler,
|
|
||||||
bool overrideExisting)
|
|
||||||
{
|
|
||||||
this.MessageDispatcher.SetEventHandler(
|
|
||||||
eventType,
|
|
||||||
eventHandler,
|
|
||||||
overrideExisting);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleResponse(Message responseMessage)
|
|
||||||
{
|
|
||||||
TaskCompletionSource<Message> pendingRequestTask = null;
|
|
||||||
|
|
||||||
if (this.pendingRequests.TryGetValue(responseMessage.Id, out pendingRequestTask))
|
|
||||||
{
|
|
||||||
pendingRequestTask.SetResult(responseMessage);
|
|
||||||
this.pendingRequests.Remove(responseMessage.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Subclass Lifetime Methods
|
|
||||||
|
|
||||||
protected virtual Task OnStart()
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Task OnConnect()
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Task OnStop()
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Event Handlers
|
|
||||||
|
|
||||||
private void MessageDispatcher_UnhandledException(object sender, Exception e)
|
|
||||||
{
|
|
||||||
if (this.endpointExitedTask != null)
|
|
||||||
{
|
|
||||||
this.endpointExitedTask.SetException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (this.originalSynchronizationContext != null)
|
|
||||||
{
|
|
||||||
this.originalSynchronizationContext.Post(o => { throw e; }, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
public class RequestContext<TResult>
|
|
||||||
{
|
|
||||||
private Message requestMessage;
|
|
||||||
private MessageWriter messageWriter;
|
|
||||||
|
|
||||||
public RequestContext(Message requestMessage, MessageWriter messageWriter)
|
|
||||||
{
|
|
||||||
this.requestMessage = requestMessage;
|
|
||||||
this.messageWriter = messageWriter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendResult(TResult resultDetails)
|
|
||||||
{
|
|
||||||
await this.messageWriter.WriteResponse<TResult>(
|
|
||||||
resultDetails,
|
|
||||||
requestMessage.Method,
|
|
||||||
requestMessage.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
|
|
||||||
{
|
|
||||||
await this.messageWriter.WriteEvent(
|
|
||||||
eventType,
|
|
||||||
eventParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendError(object errorDetails)
|
|
||||||
{
|
|
||||||
await this.messageWriter.WriteMessage(
|
|
||||||
Message.ResponseError(
|
|
||||||
requestMessage.Id,
|
|
||||||
requestMessage.Method,
|
|
||||||
JToken.FromObject(errorDetails)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol
|
|
||||||
{
|
|
||||||
[DebuggerDisplay("RequestType MethodName = {MethodName}")]
|
|
||||||
public class RequestType<TParams, TResult>
|
|
||||||
{
|
|
||||||
public string MethodName { get; private set; }
|
|
||||||
|
|
||||||
public static RequestType<TParams, TResult> Create(string typeName)
|
|
||||||
{
|
|
||||||
return new RequestType<TParams, TResult>()
|
|
||||||
{
|
|
||||||
MethodName = typeName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Serializers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Serializes messages in the JSON RPC format. Used primarily
|
|
||||||
/// for language servers.
|
|
||||||
/// </summary>
|
|
||||||
public class JsonRpcMessageSerializer : IMessageSerializer
|
|
||||||
{
|
|
||||||
public JObject SerializeMessage(Message message)
|
|
||||||
{
|
|
||||||
JObject messageObject = new JObject();
|
|
||||||
|
|
||||||
messageObject.Add("jsonrpc", JToken.FromObject("2.0"));
|
|
||||||
|
|
||||||
if (message.MessageType == MessageType.Request)
|
|
||||||
{
|
|
||||||
messageObject.Add("id", JToken.FromObject(message.Id));
|
|
||||||
messageObject.Add("method", message.Method);
|
|
||||||
messageObject.Add("params", message.Contents);
|
|
||||||
}
|
|
||||||
else if (message.MessageType == MessageType.Event)
|
|
||||||
{
|
|
||||||
messageObject.Add("method", message.Method);
|
|
||||||
messageObject.Add("params", message.Contents);
|
|
||||||
}
|
|
||||||
else if (message.MessageType == MessageType.Response)
|
|
||||||
{
|
|
||||||
messageObject.Add("id", JToken.FromObject(message.Id));
|
|
||||||
|
|
||||||
if (message.Error != null)
|
|
||||||
{
|
|
||||||
// Write error
|
|
||||||
messageObject.Add("error", message.Error);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Write result
|
|
||||||
messageObject.Add("result", message.Contents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Message DeserializeMessage(JObject messageJson)
|
|
||||||
{
|
|
||||||
// TODO: Check for jsonrpc version
|
|
||||||
|
|
||||||
JToken token = null;
|
|
||||||
if (messageJson.TryGetValue("id", out token))
|
|
||||||
{
|
|
||||||
// Message is a Request or Response
|
|
||||||
string messageId = token.ToString();
|
|
||||||
|
|
||||||
if (messageJson.TryGetValue("result", out token))
|
|
||||||
{
|
|
||||||
return Message.Response(messageId, null, token);
|
|
||||||
}
|
|
||||||
else if (messageJson.TryGetValue("error", out token))
|
|
||||||
{
|
|
||||||
return Message.ResponseError(messageId, null, token);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
JToken messageParams = null;
|
|
||||||
messageJson.TryGetValue("params", out messageParams);
|
|
||||||
|
|
||||||
if (!messageJson.TryGetValue("method", out token))
|
|
||||||
{
|
|
||||||
// TODO: Throw parse error
|
|
||||||
}
|
|
||||||
|
|
||||||
return Message.Request(messageId, token.ToString(), messageParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Messages without an id are events
|
|
||||||
JToken messageParams = token;
|
|
||||||
messageJson.TryGetValue("params", out messageParams);
|
|
||||||
|
|
||||||
if (!messageJson.TryGetValue("method", out token))
|
|
||||||
{
|
|
||||||
// TODO: Throw parse error
|
|
||||||
}
|
|
||||||
|
|
||||||
return Message.Event(token.ToString(), messageParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Serializers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Serializes messages in the V8 format. Used primarily for debug adapters.
|
|
||||||
/// </summary>
|
|
||||||
public class V8MessageSerializer : IMessageSerializer
|
|
||||||
{
|
|
||||||
public JObject SerializeMessage(Message message)
|
|
||||||
{
|
|
||||||
JObject messageObject = new JObject();
|
|
||||||
|
|
||||||
if (message.MessageType == MessageType.Request)
|
|
||||||
{
|
|
||||||
messageObject.Add("type", JToken.FromObject("request"));
|
|
||||||
messageObject.Add("seq", JToken.FromObject(message.Id));
|
|
||||||
messageObject.Add("command", message.Method);
|
|
||||||
messageObject.Add("arguments", message.Contents);
|
|
||||||
}
|
|
||||||
else if (message.MessageType == MessageType.Event)
|
|
||||||
{
|
|
||||||
messageObject.Add("type", JToken.FromObject("event"));
|
|
||||||
messageObject.Add("event", message.Method);
|
|
||||||
messageObject.Add("body", message.Contents);
|
|
||||||
}
|
|
||||||
else if (message.MessageType == MessageType.Response)
|
|
||||||
{
|
|
||||||
messageObject.Add("type", JToken.FromObject("response"));
|
|
||||||
messageObject.Add("request_seq", JToken.FromObject(message.Id));
|
|
||||||
messageObject.Add("command", message.Method);
|
|
||||||
|
|
||||||
if (message.Error != null)
|
|
||||||
{
|
|
||||||
// Write error
|
|
||||||
messageObject.Add("success", JToken.FromObject(false));
|
|
||||||
messageObject.Add("message", message.Error);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Write result
|
|
||||||
messageObject.Add("success", JToken.FromObject(true));
|
|
||||||
messageObject.Add("body", message.Contents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Message DeserializeMessage(JObject messageJson)
|
|
||||||
{
|
|
||||||
JToken token = null;
|
|
||||||
|
|
||||||
if (messageJson.TryGetValue("type", out token))
|
|
||||||
{
|
|
||||||
string messageType = token.ToString();
|
|
||||||
|
|
||||||
if (string.Equals("request", messageType, StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
return Message.Request(
|
|
||||||
messageJson.GetValue("seq").ToString(),
|
|
||||||
messageJson.GetValue("command").ToString(),
|
|
||||||
messageJson.GetValue("arguments"));
|
|
||||||
}
|
|
||||||
else if (string.Equals("response", messageType, StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
if (messageJson.TryGetValue("success", out token))
|
|
||||||
{
|
|
||||||
// Was the response for a successful request?
|
|
||||||
if (token.ToObject<bool>() == true)
|
|
||||||
{
|
|
||||||
return Message.Response(
|
|
||||||
messageJson.GetValue("request_seq").ToString(),
|
|
||||||
messageJson.GetValue("command").ToString(),
|
|
||||||
messageJson.GetValue("body"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Message.ResponseError(
|
|
||||||
messageJson.GetValue("request_seq").ToString(),
|
|
||||||
messageJson.GetValue("command").ToString(),
|
|
||||||
messageJson.GetValue("message"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: Parse error
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (string.Equals("event", messageType, StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
return Message.Event(
|
|
||||||
messageJson.GetValue("event").ToString(),
|
|
||||||
messageJson.GetValue("body"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Message.Unknown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Message.Unknown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
using System;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.Server;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Session;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceHost
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Main application class for SQL Tools API Service Host executable
|
|
||||||
/// </summary>
|
|
||||||
class Program
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Main entry point into the SQL Tools API Service Host
|
|
||||||
/// </summary>
|
|
||||||
static void Main(string[] args)
|
|
||||||
{
|
|
||||||
// turn on Verbose logging during early development
|
|
||||||
// we need to switch to Normal when preparing for public preview
|
|
||||||
Logger.Initialize(minimumLogLevel: LogLevel.Verbose);
|
|
||||||
Logger.Write(LogLevel.Normal, "Starting SQL Tools Service Host");
|
|
||||||
|
|
||||||
const string hostName = "SQL Tools Service Host";
|
|
||||||
const string hostProfileId = "SQLToolsService";
|
|
||||||
Version hostVersion = new Version(1,0);
|
|
||||||
|
|
||||||
// set up the host details and profile paths
|
|
||||||
var hostDetails = new HostDetails(hostName, hostProfileId, hostVersion);
|
|
||||||
var profilePaths = new ProfilePaths(hostProfileId, "baseAllUsersPath", "baseCurrentUserPath");
|
|
||||||
|
|
||||||
// create and run the language server
|
|
||||||
var languageServer = new LanguageServer(hostDetails, profilePaths);
|
|
||||||
languageServer.Start().Wait();
|
|
||||||
languageServer.WaitForExit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("SqlTools Editor Services Host Protocol Library")]
|
|
||||||
[assembly: AssemblyDescription("Provides message types and client/server APIs for the SqlTools Editor Services JSON protocol.")]
|
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("Microsoft")]
|
|
||||||
[assembly: AssemblyProduct("SqlTools Editor Services")]
|
|
||||||
[assembly: AssemblyCopyright("<22> Microsoft Corporation. All rights reserved.")]
|
|
||||||
[assembly: AssemblyTrademark("")]
|
|
||||||
[assembly: AssemblyCulture("")]
|
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
|
|
||||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
|
||||||
[assembly: Guid("78caf6c3-5955-4b15-a302-2bd6b7871d5b")]
|
|
||||||
|
|
||||||
// Version information for an assembly consists of the following four values:
|
|
||||||
//
|
|
||||||
// Major Version
|
|
||||||
// Minor Version
|
|
||||||
// Build Number
|
|
||||||
// Revision
|
|
||||||
//
|
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||||
// by using the '*' as shown below:
|
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyInformationalVersion("1.0.0.0")]
|
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Microsoft.SqlTools.EditorServices.Test.Protocol")]
|
|
||||||
@@ -1,517 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.LanguageServer;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Session;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Linq;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.Server
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// SQL Tools VS Code Language Server request handler
|
|
||||||
/// </summary>
|
|
||||||
public class LanguageServer : LanguageServerBase
|
|
||||||
{
|
|
||||||
private static CancellationTokenSource existingRequestCancellation;
|
|
||||||
|
|
||||||
private LanguageServerSettings currentSettings = new LanguageServerSettings();
|
|
||||||
|
|
||||||
private EditorSession editorSession;
|
|
||||||
|
|
||||||
/// <param name="hostDetails">
|
|
||||||
/// Provides details about the host application.
|
|
||||||
/// </param>
|
|
||||||
public LanguageServer(HostDetails hostDetails, ProfilePaths profilePaths)
|
|
||||||
: base(new StdioServerChannel())
|
|
||||||
{
|
|
||||||
this.editorSession = new EditorSession();
|
|
||||||
this.editorSession.StartSession(hostDetails, profilePaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize the VS Code request/response callbacks
|
|
||||||
/// </summary>
|
|
||||||
protected override void Initialize()
|
|
||||||
{
|
|
||||||
// Register all supported message types
|
|
||||||
this.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest);
|
|
||||||
this.SetEventHandler(DidChangeTextDocumentNotification.Type, this.HandleDidChangeTextDocumentNotification);
|
|
||||||
this.SetEventHandler(DidOpenTextDocumentNotification.Type, this.HandleDidOpenTextDocumentNotification);
|
|
||||||
this.SetEventHandler(DidCloseTextDocumentNotification.Type, this.HandleDidCloseTextDocumentNotification);
|
|
||||||
this.SetEventHandler(DidChangeConfigurationNotification<LanguageServerSettingsWrapper>.Type, this.HandleDidChangeConfigurationNotification);
|
|
||||||
|
|
||||||
this.SetRequestHandler(DefinitionRequest.Type, this.HandleDefinitionRequest);
|
|
||||||
this.SetRequestHandler(ReferencesRequest.Type, this.HandleReferencesRequest);
|
|
||||||
this.SetRequestHandler(CompletionRequest.Type, this.HandleCompletionRequest);
|
|
||||||
this.SetRequestHandler(CompletionResolveRequest.Type, this.HandleCompletionResolveRequest);
|
|
||||||
this.SetRequestHandler(SignatureHelpRequest.Type, this.HandleSignatureHelpRequest);
|
|
||||||
this.SetRequestHandler(DocumentHighlightRequest.Type, this.HandleDocumentHighlightRequest);
|
|
||||||
this.SetRequestHandler(HoverRequest.Type, this.HandleHoverRequest);
|
|
||||||
this.SetRequestHandler(DocumentSymbolRequest.Type, this.HandleDocumentSymbolRequest);
|
|
||||||
this.SetRequestHandler(WorkspaceSymbolRequest.Type, this.HandleWorkspaceSymbolRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the shutdown event for the Language Server
|
|
||||||
/// </summary>
|
|
||||||
protected override async Task Shutdown()
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Normal, "Language service is shutting down...");
|
|
||||||
|
|
||||||
if (this.editorSession != null)
|
|
||||||
{
|
|
||||||
this.editorSession.Dispose();
|
|
||||||
this.editorSession = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the initialization request
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="initializeParams"></param>
|
|
||||||
/// <param name="requestContext"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected async Task HandleInitializeRequest(
|
|
||||||
InitializeRequest initializeParams,
|
|
||||||
RequestContext<InitializeResult> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDidChangeTextDocumentNotification");
|
|
||||||
|
|
||||||
// Grab the workspace path from the parameters
|
|
||||||
editorSession.Workspace.WorkspacePath = initializeParams.RootPath;
|
|
||||||
|
|
||||||
await requestContext.SendResult(
|
|
||||||
new InitializeResult
|
|
||||||
{
|
|
||||||
Capabilities = new ServerCapabilities
|
|
||||||
{
|
|
||||||
TextDocumentSync = TextDocumentSyncKind.Incremental,
|
|
||||||
DefinitionProvider = true,
|
|
||||||
ReferencesProvider = true,
|
|
||||||
DocumentHighlightProvider = true,
|
|
||||||
DocumentSymbolProvider = true,
|
|
||||||
WorkspaceSymbolProvider = true,
|
|
||||||
HoverProvider = true,
|
|
||||||
CompletionProvider = new CompletionOptions
|
|
||||||
{
|
|
||||||
ResolveProvider = true,
|
|
||||||
TriggerCharacters = new string[] { ".", "-", ":", "\\" }
|
|
||||||
},
|
|
||||||
SignatureHelpProvider = new SignatureHelpOptions
|
|
||||||
{
|
|
||||||
TriggerCharacters = new string[] { " " } // TODO: Other characters here?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles text document change events
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="textChangeParams"></param>
|
|
||||||
/// <param name="eventContext"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected Task HandleDidChangeTextDocumentNotification(
|
|
||||||
DidChangeTextDocumentParams textChangeParams,
|
|
||||||
EventContext eventContext)
|
|
||||||
{
|
|
||||||
StringBuilder msg = new StringBuilder();
|
|
||||||
msg.Append("HandleDidChangeTextDocumentNotification");
|
|
||||||
List<ScriptFile> changedFiles = new List<ScriptFile>();
|
|
||||||
|
|
||||||
// A text change notification can batch multiple change requests
|
|
||||||
foreach (var textChange in textChangeParams.ContentChanges)
|
|
||||||
{
|
|
||||||
string fileUri = textChangeParams.TextDocument.Uri;
|
|
||||||
msg.AppendLine();
|
|
||||||
msg.Append(" File: ");
|
|
||||||
msg.Append(fileUri);
|
|
||||||
|
|
||||||
ScriptFile changedFile = editorSession.Workspace.GetFile(fileUri);
|
|
||||||
|
|
||||||
changedFile.ApplyChange(
|
|
||||||
GetFileChangeDetails(
|
|
||||||
textChange.Range.Value,
|
|
||||||
textChange.Text));
|
|
||||||
|
|
||||||
changedFiles.Add(changedFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Write(LogLevel.Verbose, msg.ToString());
|
|
||||||
|
|
||||||
this.RunScriptDiagnostics(
|
|
||||||
changedFiles.ToArray(),
|
|
||||||
editorSession,
|
|
||||||
eventContext);
|
|
||||||
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Task HandleDidOpenTextDocumentNotification(
|
|
||||||
DidOpenTextDocumentNotification openParams,
|
|
||||||
EventContext eventContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDidOpenTextDocumentNotification");
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Task HandleDidCloseTextDocumentNotification(
|
|
||||||
TextDocumentIdentifier closeParams,
|
|
||||||
EventContext eventContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDidCloseTextDocumentNotification");
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the configuration change event
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configChangeParams"></param>
|
|
||||||
/// <param name="eventContext"></param>
|
|
||||||
protected async Task HandleDidChangeConfigurationNotification(
|
|
||||||
DidChangeConfigurationParams<LanguageServerSettingsWrapper> configChangeParams,
|
|
||||||
EventContext eventContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDidChangeConfigurationNotification");
|
|
||||||
|
|
||||||
bool oldLoadProfiles = this.currentSettings.EnableProfileLoading;
|
|
||||||
bool oldScriptAnalysisEnabled =
|
|
||||||
this.currentSettings.ScriptAnalysis.Enable.HasValue;
|
|
||||||
string oldScriptAnalysisSettingsPath =
|
|
||||||
this.currentSettings.ScriptAnalysis.SettingsPath;
|
|
||||||
|
|
||||||
this.currentSettings.Update(
|
|
||||||
configChangeParams.Settings.SqlTools,
|
|
||||||
this.editorSession.Workspace.WorkspacePath);
|
|
||||||
|
|
||||||
// If script analysis settings have changed we need to clear & possibly update the current diagnostic records.
|
|
||||||
if ((oldScriptAnalysisEnabled != this.currentSettings.ScriptAnalysis.Enable))
|
|
||||||
{
|
|
||||||
// If the user just turned off script analysis or changed the settings path, send a diagnostics
|
|
||||||
// event to clear the analysis markers that they already have.
|
|
||||||
if (!this.currentSettings.ScriptAnalysis.Enable.Value)
|
|
||||||
{
|
|
||||||
ScriptFileMarker[] emptyAnalysisDiagnostics = new ScriptFileMarker[0];
|
|
||||||
|
|
||||||
foreach (var scriptFile in editorSession.Workspace.GetOpenedFiles())
|
|
||||||
{
|
|
||||||
await PublishScriptDiagnostics(
|
|
||||||
scriptFile,
|
|
||||||
emptyAnalysisDiagnostics,
|
|
||||||
eventContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await this.RunScriptDiagnostics(
|
|
||||||
this.editorSession.Workspace.GetOpenedFiles(),
|
|
||||||
this.editorSession,
|
|
||||||
eventContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task HandleDefinitionRequest(
|
|
||||||
TextDocumentPosition textDocumentPosition,
|
|
||||||
RequestContext<Location[]> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDefinitionRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task HandleReferencesRequest(
|
|
||||||
ReferencesParams referencesParams,
|
|
||||||
RequestContext<Location[]> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleReferencesRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task HandleCompletionRequest(
|
|
||||||
TextDocumentPosition textDocumentPosition,
|
|
||||||
RequestContext<CompletionItem[]> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleCompletionRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task HandleCompletionResolveRequest(
|
|
||||||
CompletionItem completionItem,
|
|
||||||
RequestContext<CompletionItem> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleCompletionResolveRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task HandleSignatureHelpRequest(
|
|
||||||
TextDocumentPosition textDocumentPosition,
|
|
||||||
RequestContext<SignatureHelp> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleSignatureHelpRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task HandleDocumentHighlightRequest(
|
|
||||||
TextDocumentPosition textDocumentPosition,
|
|
||||||
RequestContext<DocumentHighlight[]> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDocumentHighlightRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task HandleHoverRequest(
|
|
||||||
TextDocumentPosition textDocumentPosition,
|
|
||||||
RequestContext<Hover> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleHoverRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task HandleDocumentSymbolRequest(
|
|
||||||
TextDocumentIdentifier textDocumentIdentifier,
|
|
||||||
RequestContext<SymbolInformation[]> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDocumentSymbolRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task HandleWorkspaceSymbolRequest(
|
|
||||||
WorkspaceSymbolParams workspaceSymbolParams,
|
|
||||||
RequestContext<SymbolInformation[]> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleWorkspaceSymbolRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs script diagnostics on changed files
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filesToAnalyze"></param>
|
|
||||||
/// <param name="editorSession"></param>
|
|
||||||
/// <param name="eventContext"></param>
|
|
||||||
private Task RunScriptDiagnostics(
|
|
||||||
ScriptFile[] filesToAnalyze,
|
|
||||||
EditorSession editorSession,
|
|
||||||
EventContext eventContext)
|
|
||||||
{
|
|
||||||
if (!this.currentSettings.ScriptAnalysis.Enable.Value)
|
|
||||||
{
|
|
||||||
// If the user has disabled script analysis, skip it entirely
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's an existing task, attempt to cancel it
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (existingRequestCancellation != null)
|
|
||||||
{
|
|
||||||
// Try to cancel the request
|
|
||||||
existingRequestCancellation.Cancel();
|
|
||||||
|
|
||||||
// If cancellation didn't throw an exception,
|
|
||||||
// clean up the existing token
|
|
||||||
existingRequestCancellation.Dispose();
|
|
||||||
existingRequestCancellation = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Write(
|
|
||||||
LogLevel.Error,
|
|
||||||
string.Format(
|
|
||||||
"Exception while cancelling analysis task:\n\n{0}",
|
|
||||||
e.ToString()));
|
|
||||||
|
|
||||||
TaskCompletionSource<bool> cancelTask = new TaskCompletionSource<bool>();
|
|
||||||
cancelTask.SetCanceled();
|
|
||||||
return cancelTask.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a fresh cancellation token and then start the task.
|
|
||||||
// We create this on a different TaskScheduler so that we
|
|
||||||
// don't block the main message loop thread.
|
|
||||||
existingRequestCancellation = new CancellationTokenSource();
|
|
||||||
Task.Factory.StartNew(
|
|
||||||
() =>
|
|
||||||
DelayThenInvokeDiagnostics(
|
|
||||||
750,
|
|
||||||
filesToAnalyze,
|
|
||||||
editorSession,
|
|
||||||
eventContext,
|
|
||||||
existingRequestCancellation.Token),
|
|
||||||
CancellationToken.None,
|
|
||||||
TaskCreationOptions.None,
|
|
||||||
TaskScheduler.Default);
|
|
||||||
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Actually run the script diagnostics after waiting for some small delay
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="delayMilliseconds"></param>
|
|
||||||
/// <param name="filesToAnalyze"></param>
|
|
||||||
/// <param name="editorSession"></param>
|
|
||||||
/// <param name="eventContext"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
private static async Task DelayThenInvokeDiagnostics(
|
|
||||||
int delayMilliseconds,
|
|
||||||
ScriptFile[] filesToAnalyze,
|
|
||||||
EditorSession editorSession,
|
|
||||||
EventContext eventContext,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
// First of all, wait for the desired delay period before
|
|
||||||
// analyzing the provided list of files
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(delayMilliseconds, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
// If the task is cancelled, exit directly
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we've made it past the delay period then we don't care
|
|
||||||
// about the cancellation token anymore. This could happen
|
|
||||||
// when the user stops typing for long enough that the delay
|
|
||||||
// period ends but then starts typing while analysis is going
|
|
||||||
// on. It makes sense to send back the results from the first
|
|
||||||
// delay period while the second one is ticking away.
|
|
||||||
|
|
||||||
// Get the requested files
|
|
||||||
foreach (ScriptFile scriptFile in filesToAnalyze)
|
|
||||||
{
|
|
||||||
ScriptFileMarker[] semanticMarkers = null;
|
|
||||||
if (editorSession.LanguageService != null)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "Analyzing script file: " + scriptFile.FilePath);
|
|
||||||
semanticMarkers = editorSession.LanguageService.GetSemanticMarkers(scriptFile);
|
|
||||||
Logger.Write(LogLevel.Verbose, "Analysis complete.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Semantic markers aren't available if the AnalysisService
|
|
||||||
// isn't available
|
|
||||||
semanticMarkers = new ScriptFileMarker[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
await PublishScriptDiagnostics(
|
|
||||||
scriptFile,
|
|
||||||
semanticMarkers,
|
|
||||||
eventContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send the diagnostic results back to the host application
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFile"></param>
|
|
||||||
/// <param name="semanticMarkers"></param>
|
|
||||||
/// <param name="eventContext"></param>
|
|
||||||
private static async Task PublishScriptDiagnostics(
|
|
||||||
ScriptFile scriptFile,
|
|
||||||
ScriptFileMarker[] semanticMarkers,
|
|
||||||
EventContext eventContext)
|
|
||||||
{
|
|
||||||
var allMarkers = scriptFile.SyntaxMarkers != null
|
|
||||||
? scriptFile.SyntaxMarkers.Concat(semanticMarkers)
|
|
||||||
: semanticMarkers;
|
|
||||||
|
|
||||||
// Always send syntax and semantic errors. We want to
|
|
||||||
// make sure no out-of-date markers are being displayed.
|
|
||||||
await eventContext.SendEvent(
|
|
||||||
PublishDiagnosticsNotification.Type,
|
|
||||||
new PublishDiagnosticsNotification
|
|
||||||
{
|
|
||||||
Uri = scriptFile.ClientFilePath,
|
|
||||||
Diagnostics =
|
|
||||||
allMarkers
|
|
||||||
.Select(GetDiagnosticFromMarker)
|
|
||||||
.ToArray()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFileMarker"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static Diagnostic GetDiagnosticFromMarker(ScriptFileMarker scriptFileMarker)
|
|
||||||
{
|
|
||||||
return new Diagnostic
|
|
||||||
{
|
|
||||||
Severity = MapDiagnosticSeverity(scriptFileMarker.Level),
|
|
||||||
Message = scriptFileMarker.Message,
|
|
||||||
Range = new Range
|
|
||||||
{
|
|
||||||
// TODO: What offsets should I use?
|
|
||||||
Start = new Position
|
|
||||||
{
|
|
||||||
Line = scriptFileMarker.ScriptRegion.StartLineNumber - 1,
|
|
||||||
Character = scriptFileMarker.ScriptRegion.StartColumnNumber - 1
|
|
||||||
},
|
|
||||||
End = new Position
|
|
||||||
{
|
|
||||||
Line = scriptFileMarker.ScriptRegion.EndLineNumber - 1,
|
|
||||||
Character = scriptFileMarker.ScriptRegion.EndColumnNumber - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Map ScriptFileMarker severity to Diagnostic severity
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="markerLevel"></param>
|
|
||||||
private static DiagnosticSeverity MapDiagnosticSeverity(ScriptFileMarkerLevel markerLevel)
|
|
||||||
{
|
|
||||||
switch (markerLevel)
|
|
||||||
{
|
|
||||||
case ScriptFileMarkerLevel.Error:
|
|
||||||
return DiagnosticSeverity.Error;
|
|
||||||
|
|
||||||
case ScriptFileMarkerLevel.Warning:
|
|
||||||
return DiagnosticSeverity.Warning;
|
|
||||||
|
|
||||||
case ScriptFileMarkerLevel.Information:
|
|
||||||
return DiagnosticSeverity.Information;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return DiagnosticSeverity.Error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Switch from 0-based offsets to 1 based offsets
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="changeRange"></param>
|
|
||||||
/// <param name="insertString"></param>
|
|
||||||
private static FileChange GetFileChangeDetails(Range changeRange, string insertString)
|
|
||||||
{
|
|
||||||
// The protocol's positions are zero-based so add 1 to all offsets
|
|
||||||
return new FileChange
|
|
||||||
{
|
|
||||||
InsertString = insertString,
|
|
||||||
Line = changeRange.Start.Line + 1,
|
|
||||||
Offset = changeRange.Start.Character + 1,
|
|
||||||
EndLine = changeRange.End.Line + 1,
|
|
||||||
EndOffset = changeRange.End.Character + 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.LanguageServer;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.Server
|
|
||||||
{
|
|
||||||
public abstract class LanguageServerBase : ProtocolEndpoint
|
|
||||||
{
|
|
||||||
private bool isStarted;
|
|
||||||
private ChannelBase serverChannel;
|
|
||||||
private TaskCompletionSource<bool> serverExitedTask;
|
|
||||||
|
|
||||||
public LanguageServerBase(ChannelBase serverChannel) :
|
|
||||||
base(serverChannel, MessageProtocolType.LanguageServer)
|
|
||||||
{
|
|
||||||
this.serverChannel = serverChannel;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Task OnStart()
|
|
||||||
{
|
|
||||||
// Register handlers for server lifetime messages
|
|
||||||
this.SetRequestHandler(ShutdownRequest.Type, this.HandleShutdownRequest);
|
|
||||||
this.SetEventHandler(ExitNotification.Type, this.HandleExitNotification);
|
|
||||||
|
|
||||||
// Initialize the implementation class
|
|
||||||
this.Initialize();
|
|
||||||
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnStop()
|
|
||||||
{
|
|
||||||
await this.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Overridden by the subclass to provide initialization
|
|
||||||
/// logic after the server channel is started.
|
|
||||||
/// </summary>
|
|
||||||
protected abstract void Initialize();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Can be overridden by the subclass to provide shutdown
|
|
||||||
/// logic before the server exits. Subclasses do not need
|
|
||||||
/// to invoke or return the value of the base implementation.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual Task Shutdown()
|
|
||||||
{
|
|
||||||
// No default implementation yet.
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleShutdownRequest(
|
|
||||||
object shutdownParams,
|
|
||||||
RequestContext<object> requestContext)
|
|
||||||
{
|
|
||||||
// Allow the implementor to shut down gracefully
|
|
||||||
await this.Shutdown();
|
|
||||||
|
|
||||||
await requestContext.SendResult(new object());
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleExitNotification(
|
|
||||||
object exitParams,
|
|
||||||
EventContext eventContext)
|
|
||||||
{
|
|
||||||
// Stop the server channel
|
|
||||||
await this.Stop();
|
|
||||||
|
|
||||||
// Notify any waiter that the server has exited
|
|
||||||
if (this.serverExitedTask != null)
|
|
||||||
{
|
|
||||||
this.serverExitedTask.SetResult(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
#if false
|
|
||||||
using Microsoft.SqlTools.EditorServices.Extensions;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.LanguageServer;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.Server
|
|
||||||
{
|
|
||||||
internal class LanguageServerEditorOperations : IEditorOperations
|
|
||||||
{
|
|
||||||
private EditorSession editorSession;
|
|
||||||
private IMessageSender messageSender;
|
|
||||||
|
|
||||||
public LanguageServerEditorOperations(
|
|
||||||
EditorSession editorSession,
|
|
||||||
IMessageSender messageSender)
|
|
||||||
{
|
|
||||||
this.editorSession = editorSession;
|
|
||||||
this.messageSender = messageSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<EditorContext> GetEditorContext()
|
|
||||||
{
|
|
||||||
ClientEditorContext clientContext =
|
|
||||||
await this.messageSender.SendRequest(
|
|
||||||
GetEditorContextRequest.Type,
|
|
||||||
new GetEditorContextRequest(),
|
|
||||||
true);
|
|
||||||
|
|
||||||
return this.ConvertClientEditorContext(clientContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InsertText(string filePath, string text, BufferRange insertRange)
|
|
||||||
{
|
|
||||||
await this.messageSender.SendRequest(
|
|
||||||
InsertTextRequest.Type,
|
|
||||||
new InsertTextRequest
|
|
||||||
{
|
|
||||||
FilePath = filePath,
|
|
||||||
InsertText = text,
|
|
||||||
InsertRange =
|
|
||||||
new Range
|
|
||||||
{
|
|
||||||
Start = new Position
|
|
||||||
{
|
|
||||||
Line = insertRange.Start.Line - 1,
|
|
||||||
Character = insertRange.Start.Column - 1
|
|
||||||
},
|
|
||||||
End = new Position
|
|
||||||
{
|
|
||||||
Line = insertRange.End.Line - 1,
|
|
||||||
Character = insertRange.End.Column - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
// TODO: Set the last param back to true!
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SetSelection(BufferRange selectionRange)
|
|
||||||
{
|
|
||||||
return this.messageSender.SendRequest(
|
|
||||||
SetSelectionRequest.Type,
|
|
||||||
new SetSelectionRequest
|
|
||||||
{
|
|
||||||
SelectionRange =
|
|
||||||
new Range
|
|
||||||
{
|
|
||||||
Start = new Position
|
|
||||||
{
|
|
||||||
Line = selectionRange.Start.Line - 1,
|
|
||||||
Character = selectionRange.Start.Column - 1
|
|
||||||
},
|
|
||||||
End = new Position
|
|
||||||
{
|
|
||||||
Line = selectionRange.End.Line - 1,
|
|
||||||
Character = selectionRange.End.Column - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public EditorContext ConvertClientEditorContext(
|
|
||||||
ClientEditorContext clientContext)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
new EditorContext(
|
|
||||||
this,
|
|
||||||
this.editorSession.Workspace.GetFile(clientContext.CurrentFilePath),
|
|
||||||
new BufferPosition(
|
|
||||||
clientContext.CursorPosition.Line + 1,
|
|
||||||
clientContext.CursorPosition.Character + 1),
|
|
||||||
new BufferRange(
|
|
||||||
clientContext.SelectionRange.Start.Line + 1,
|
|
||||||
clientContext.SelectionRange.Start.Character + 1,
|
|
||||||
clientContext.SelectionRange.End.Line + 1,
|
|
||||||
clientContext.SelectionRange.End.Character + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task OpenFile(string filePath)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
this.messageSender.SendRequest(
|
|
||||||
OpenFileRequest.Type,
|
|
||||||
filePath,
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Protocol.Server
|
|
||||||
{
|
|
||||||
public class LanguageServerSettings
|
|
||||||
{
|
|
||||||
public bool EnableProfileLoading { get; set; }
|
|
||||||
|
|
||||||
public ScriptAnalysisSettings ScriptAnalysis { get; set; }
|
|
||||||
|
|
||||||
public LanguageServerSettings()
|
|
||||||
{
|
|
||||||
this.ScriptAnalysis = new ScriptAnalysisSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(LanguageServerSettings settings, string workspaceRootPath)
|
|
||||||
{
|
|
||||||
if (settings != null)
|
|
||||||
{
|
|
||||||
this.EnableProfileLoading = settings.EnableProfileLoading;
|
|
||||||
this.ScriptAnalysis.Update(settings.ScriptAnalysis, workspaceRootPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class ScriptAnalysisSettings
|
|
||||||
{
|
|
||||||
public bool? Enable { get; set; }
|
|
||||||
|
|
||||||
public string SettingsPath { get; set; }
|
|
||||||
|
|
||||||
public ScriptAnalysisSettings()
|
|
||||||
{
|
|
||||||
this.Enable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(ScriptAnalysisSettings settings, string workspaceRootPath)
|
|
||||||
{
|
|
||||||
if (settings != null)
|
|
||||||
{
|
|
||||||
this.Enable = settings.Enable;
|
|
||||||
|
|
||||||
string settingsPath = settings.SettingsPath;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(settingsPath))
|
|
||||||
{
|
|
||||||
settingsPath = null;
|
|
||||||
}
|
|
||||||
else if (!Path.IsPathRooted(settingsPath))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(workspaceRootPath))
|
|
||||||
{
|
|
||||||
// The workspace root path could be an empty string
|
|
||||||
// when the user has opened a SqlTools script file
|
|
||||||
// without opening an entire folder (workspace) first.
|
|
||||||
// In this case we should just log an error and let
|
|
||||||
// the specified settings path go through even though
|
|
||||||
// it will fail to load.
|
|
||||||
Logger.Write(
|
|
||||||
LogLevel.Error,
|
|
||||||
"Could not resolve Script Analyzer settings path due to null or empty workspaceRootPath.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
settingsPath = Path.GetFullPath(Path.Combine(workspaceRootPath, settingsPath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.SettingsPath = settingsPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class LanguageServerSettingsWrapper
|
|
||||||
{
|
|
||||||
// NOTE: This property is capitalized as 'SqlTools' because the
|
|
||||||
// mode name sent from the client is written as 'SqlTools' and
|
|
||||||
// JSON.net is using camelCasing.
|
|
||||||
|
|
||||||
public LanguageServerSettings SqlTools { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.SqlTools.EditorServices.Session;
|
|
||||||
using Microsoft.SqlTools.LanguageSupport;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Manages a single session for all editor services. This
|
|
||||||
/// includes managing all open script files for the session.
|
|
||||||
/// </summary>
|
|
||||||
public class EditorSession : IDisposable
|
|
||||||
{
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the Workspace instance for this session.
|
|
||||||
/// </summary>
|
|
||||||
public Workspace Workspace { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the Language Service
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public LanguageService LanguageService { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the SqlToolsContext instance for this session.
|
|
||||||
/// </summary>
|
|
||||||
public SqlToolsContext SqlToolsContext { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts the session using the provided IConsoleHost implementation
|
|
||||||
/// for the ConsoleService.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hostDetails">
|
|
||||||
/// Provides details about the host application.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="profilePaths">
|
|
||||||
/// An object containing the profile paths for the session.
|
|
||||||
/// </param>
|
|
||||||
public void StartSession(HostDetails hostDetails, ProfilePaths profilePaths)
|
|
||||||
{
|
|
||||||
// Initialize all services
|
|
||||||
this.SqlToolsContext = new SqlToolsContext(hostDetails, profilePaths);
|
|
||||||
this.LanguageService = new LanguageService(this.SqlToolsContext);
|
|
||||||
|
|
||||||
// Create a workspace to contain open files
|
|
||||||
this.Workspace = new Workspace(this.SqlToolsContext.SqlToolsVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region IDisposable Implementation
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes of any Runspaces that were created for the
|
|
||||||
/// services used in this session.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Session
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains details about the current host application (most
|
|
||||||
/// likely the editor which is using the host process).
|
|
||||||
/// </summary>
|
|
||||||
public class HostDetails
|
|
||||||
{
|
|
||||||
#region Constants
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The default host name for SqlTools Editor Services. Used
|
|
||||||
/// if no host name is specified by the host application.
|
|
||||||
/// </summary>
|
|
||||||
public const string DefaultHostName = "SqlTools Editor Services Host";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The default host ID for SqlTools Editor Services. Used
|
|
||||||
/// for the host-specific profile path if no host ID is specified.
|
|
||||||
/// </summary>
|
|
||||||
public const string DefaultHostProfileId = "Microsoft.SqlToolsEditorServices";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The default host version for SqlTools Editor Services. If
|
|
||||||
/// no version is specified by the host application, we use 0.0.0
|
|
||||||
/// to indicate a lack of version.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Version DefaultHostVersion = new Version("0.0.0");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The default host details in a HostDetails object.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly HostDetails Default = new HostDetails(null, null, null);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the host.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the profile ID of the host, used to determine the
|
|
||||||
/// host-specific profile path.
|
|
||||||
/// </summary>
|
|
||||||
public string ProfileId { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the version of the host.
|
|
||||||
/// </summary>
|
|
||||||
public Version Version { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an instance of the HostDetails class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">
|
|
||||||
/// The display name for the host, typically in the form of
|
|
||||||
/// "[Application Name] Host".
|
|
||||||
/// </param>
|
|
||||||
/// <param name="profileId">
|
|
||||||
/// The identifier of the SqlTools host to use for its profile path.
|
|
||||||
/// loaded. Used to resolve a profile path of the form 'X_profile.ps1'
|
|
||||||
/// where 'X' represents the value of hostProfileId. If null, a default
|
|
||||||
/// will be used.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="version">The host application's version.</param>
|
|
||||||
public HostDetails(
|
|
||||||
string name,
|
|
||||||
string profileId,
|
|
||||||
Version version)
|
|
||||||
{
|
|
||||||
this.Name = name ?? DefaultHostName;
|
|
||||||
this.ProfileId = profileId ?? DefaultHostProfileId;
|
|
||||||
this.Version = version ?? DefaultHostVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Enumerates the types of output lines that will be sent
|
|
||||||
/// to an IConsoleHost implementation.
|
|
||||||
/// </summary>
|
|
||||||
public enum OutputType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A normal output line, usually written with the or Write-Host or
|
|
||||||
/// Write-Output cmdlets.
|
|
||||||
/// </summary>
|
|
||||||
Normal,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A debug output line, written with the Write-Debug cmdlet.
|
|
||||||
/// </summary>
|
|
||||||
Debug,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A verbose output line, written with the Write-Verbose cmdlet.
|
|
||||||
/// </summary>
|
|
||||||
Verbose,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A warning output line, written with the Write-Warning cmdlet.
|
|
||||||
/// </summary>
|
|
||||||
Warning,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An error output line, written with the Write-Error cmdlet or
|
|
||||||
/// as a result of some error during SqlTools pipeline execution.
|
|
||||||
/// </summary>
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides details about output that has been written to the
|
|
||||||
/// SqlTools host.
|
|
||||||
/// </summary>
|
|
||||||
public class OutputWrittenEventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the text of the output.
|
|
||||||
/// </summary>
|
|
||||||
public string OutputText { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the type of the output.
|
|
||||||
/// </summary>
|
|
||||||
public OutputType OutputType { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a boolean which indicates whether a newline
|
|
||||||
/// should be written after the output.
|
|
||||||
/// </summary>
|
|
||||||
public bool IncludeNewLine { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the foreground color of the output text.
|
|
||||||
/// </summary>
|
|
||||||
public ConsoleColor ForegroundColor { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the background color of the output text.
|
|
||||||
/// </summary>
|
|
||||||
public ConsoleColor BackgroundColor { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an instance of the OutputWrittenEventArgs class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputText">The text of the output.</param>
|
|
||||||
/// <param name="includeNewLine">A boolean which indicates whether a newline should be written after the output.</param>
|
|
||||||
/// <param name="outputType">The type of the output.</param>
|
|
||||||
/// <param name="foregroundColor">The foreground color of the output text.</param>
|
|
||||||
/// <param name="backgroundColor">The background color of the output text.</param>
|
|
||||||
public OutputWrittenEventArgs(
|
|
||||||
string outputText,
|
|
||||||
bool includeNewLine,
|
|
||||||
OutputType outputType,
|
|
||||||
ConsoleColor foregroundColor,
|
|
||||||
ConsoleColor backgroundColor)
|
|
||||||
{
|
|
||||||
this.OutputText = outputText;
|
|
||||||
this.IncludeNewLine = includeNewLine;
|
|
||||||
this.OutputType = outputType;
|
|
||||||
this.ForegroundColor = foregroundColor;
|
|
||||||
this.BackgroundColor = backgroundColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Session
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides profile path resolution behavior relative to the name
|
|
||||||
/// of a particular SqlTools host.
|
|
||||||
/// </summary>
|
|
||||||
public class ProfilePaths
|
|
||||||
{
|
|
||||||
#region Constants
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The file name for the "all hosts" profile. Also used as the
|
|
||||||
/// suffix for the host-specific profile filenames.
|
|
||||||
/// </summary>
|
|
||||||
public const string AllHostsProfileName = "profile.ps1";
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the profile path for all users, all hosts.
|
|
||||||
/// </summary>
|
|
||||||
public string AllUsersAllHosts { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the profile path for all users, current host.
|
|
||||||
/// </summary>
|
|
||||||
public string AllUsersCurrentHost { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the profile path for the current user, all hosts.
|
|
||||||
/// </summary>
|
|
||||||
public string CurrentUserAllHosts { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the profile path for the current user and host.
|
|
||||||
/// </summary>
|
|
||||||
public string CurrentUserCurrentHost { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the ProfilePaths class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hostProfileId">
|
|
||||||
/// The identifier of the host used in the host-specific X_profile.ps1 filename.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="baseAllUsersPath">The base path to use for constructing AllUsers profile paths.</param>
|
|
||||||
/// <param name="baseCurrentUserPath">The base path to use for constructing CurrentUser profile paths.</param>
|
|
||||||
public ProfilePaths(
|
|
||||||
string hostProfileId,
|
|
||||||
string baseAllUsersPath,
|
|
||||||
string baseCurrentUserPath)
|
|
||||||
{
|
|
||||||
this.Initialize(hostProfileId, baseAllUsersPath, baseCurrentUserPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Initialize(
|
|
||||||
string hostProfileId,
|
|
||||||
string baseAllUsersPath,
|
|
||||||
string baseCurrentUserPath)
|
|
||||||
{
|
|
||||||
string currentHostProfileName =
|
|
||||||
string.Format(
|
|
||||||
"{0}_{1}",
|
|
||||||
hostProfileId,
|
|
||||||
AllHostsProfileName);
|
|
||||||
|
|
||||||
this.AllUsersCurrentHost = Path.Combine(baseAllUsersPath, currentHostProfileName);
|
|
||||||
this.CurrentUserCurrentHost = Path.Combine(baseCurrentUserPath, currentHostProfileName);
|
|
||||||
this.AllUsersAllHosts = Path.Combine(baseAllUsersPath, AllHostsProfileName);
|
|
||||||
this.CurrentUserAllHosts = Path.Combine(baseCurrentUserPath, AllHostsProfileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the list of profile paths that exist on the filesystem.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An IEnumerable of profile path strings to be loaded.</returns>
|
|
||||||
public IEnumerable<string> GetLoadableProfilePaths()
|
|
||||||
{
|
|
||||||
var profilePaths =
|
|
||||||
new string[]
|
|
||||||
{
|
|
||||||
this.AllUsersAllHosts,
|
|
||||||
this.AllUsersCurrentHost,
|
|
||||||
this.CurrentUserAllHosts,
|
|
||||||
this.CurrentUserCurrentHost
|
|
||||||
};
|
|
||||||
|
|
||||||
return profilePaths.Where(p => File.Exists(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Session
|
|
||||||
{
|
|
||||||
public class SqlToolsContext
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the PowerShell version of the current runspace.
|
|
||||||
/// </summary>
|
|
||||||
public Version SqlToolsVersion
|
|
||||||
{
|
|
||||||
get; private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SqlToolsContext(HostDetails hostDetails, ProfilePaths profilePaths)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Utility
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Simplifies the setup of a SynchronizationContext for the use
|
|
||||||
/// of async calls in the current thread.
|
|
||||||
/// </summary>
|
|
||||||
public static class AsyncContext
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Starts a new ThreadSynchronizationContext, attaches it to
|
|
||||||
/// the thread, and then runs the given async main function.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="asyncMainFunc">
|
|
||||||
/// The Task-returning Func which represents the "main" function
|
|
||||||
/// for the thread.
|
|
||||||
/// </param>
|
|
||||||
public static void Start(Func<Task> asyncMainFunc)
|
|
||||||
{
|
|
||||||
// Is there already a synchronization context?
|
|
||||||
if (SynchronizationContext.Current != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"A SynchronizationContext is already assigned on this thread.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and register a synchronization context for this thread
|
|
||||||
var threadSyncContext = new ThreadSynchronizationContext();
|
|
||||||
SynchronizationContext.SetSynchronizationContext(threadSyncContext);
|
|
||||||
|
|
||||||
// Get the main task and act on its completion
|
|
||||||
Task asyncMainTask = asyncMainFunc();
|
|
||||||
asyncMainTask.ContinueWith(
|
|
||||||
t => threadSyncContext.EndLoop(),
|
|
||||||
TaskScheduler.Default);
|
|
||||||
|
|
||||||
// Start the synchronization context's request loop and
|
|
||||||
// wait for the main task to complete
|
|
||||||
threadSyncContext.RunLoopOnCurrentThread();
|
|
||||||
asyncMainTask.GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Utility
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a simplified interface for creating a new thread
|
|
||||||
/// and establishing an AsyncContext in it.
|
|
||||||
/// </summary>
|
|
||||||
public class AsyncContextThread
|
|
||||||
{
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
private Task threadTask;
|
|
||||||
private string threadName;
|
|
||||||
private CancellationTokenSource threadCancellationToken =
|
|
||||||
new CancellationTokenSource();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the AsyncContextThread class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="threadName">
|
|
||||||
/// The name of the thread for debugging purposes.
|
|
||||||
/// </param>
|
|
||||||
public AsyncContextThread(string threadName)
|
|
||||||
{
|
|
||||||
this.threadName = threadName;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs a task on the AsyncContextThread.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="taskReturningFunc">
|
|
||||||
/// A Func which returns the task to be run on the thread.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A Task which can be used to monitor the thread for completion.
|
|
||||||
/// </returns>
|
|
||||||
public Task Run(Func<Task> taskReturningFunc)
|
|
||||||
{
|
|
||||||
// Start up a long-running task with the action as the
|
|
||||||
// main entry point for the thread
|
|
||||||
this.threadTask =
|
|
||||||
Task.Factory.StartNew(
|
|
||||||
() =>
|
|
||||||
{
|
|
||||||
// Set the thread's name to help with debugging
|
|
||||||
Thread.CurrentThread.Name = "AsyncContextThread: " + this.threadName;
|
|
||||||
|
|
||||||
// Set up an AsyncContext to run the task
|
|
||||||
AsyncContext.Start(taskReturningFunc);
|
|
||||||
},
|
|
||||||
this.threadCancellationToken.Token,
|
|
||||||
TaskCreationOptions.LongRunning,
|
|
||||||
TaskScheduler.Default);
|
|
||||||
|
|
||||||
return this.threadTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops the thread task.
|
|
||||||
/// </summary>
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
this.threadCancellationToken.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Utility
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a simple wrapper over a SemaphoreSlim to allow
|
|
||||||
/// synchronization locking inside of async calls. Cannot be
|
|
||||||
/// used recursively.
|
|
||||||
/// </summary>
|
|
||||||
public class AsyncLock
|
|
||||||
{
|
|
||||||
#region Fields
|
|
||||||
|
|
||||||
private Task<IDisposable> lockReleaseTask;
|
|
||||||
private SemaphoreSlim lockSemaphore = new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the AsyncLock class.
|
|
||||||
/// </summary>
|
|
||||||
public AsyncLock()
|
|
||||||
{
|
|
||||||
this.lockReleaseTask =
|
|
||||||
Task.FromResult(
|
|
||||||
(IDisposable)new LockReleaser(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Locks
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A task which has an IDisposable</returns>
|
|
||||||
public Task<IDisposable> LockAsync()
|
|
||||||
{
|
|
||||||
return this.LockAsync(CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Obtains or waits for a lock which can be used to synchronize
|
|
||||||
/// access to a resource. The wait may be cancelled with the
|
|
||||||
/// given CancellationToken.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">
|
|
||||||
/// A CancellationToken which can be used to cancel the lock.
|
|
||||||
/// </param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Task<IDisposable> LockAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
Task waitTask = lockSemaphore.WaitAsync(cancellationToken);
|
|
||||||
|
|
||||||
return waitTask.IsCompleted ?
|
|
||||||
this.lockReleaseTask :
|
|
||||||
waitTask.ContinueWith(
|
|
||||||
(t, releaser) =>
|
|
||||||
{
|
|
||||||
return (IDisposable)releaser;
|
|
||||||
},
|
|
||||||
this.lockReleaseTask.Result,
|
|
||||||
cancellationToken,
|
|
||||||
TaskContinuationOptions.ExecuteSynchronously,
|
|
||||||
TaskScheduler.Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Private Classes
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides an IDisposable wrapper around an AsyncLock so
|
|
||||||
/// that it can easily be used inside of a 'using' block.
|
|
||||||
/// </summary>
|
|
||||||
private class LockReleaser : IDisposable
|
|
||||||
{
|
|
||||||
private AsyncLock lockToRelease;
|
|
||||||
|
|
||||||
internal LockReleaser(AsyncLock lockToRelease)
|
|
||||||
{
|
|
||||||
this.lockToRelease = lockToRelease;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.lockToRelease.lockSemaphore.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Utility
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a synchronized queue which can be used from within async
|
|
||||||
/// operations. This is primarily used for producer/consumer scenarios.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of item contained in the queue.</typeparam>
|
|
||||||
public class AsyncQueue<T>
|
|
||||||
{
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
private AsyncLock queueLock = new AsyncLock();
|
|
||||||
private Queue<T> itemQueue;
|
|
||||||
private Queue<TaskCompletionSource<T>> requestQueue;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if the queue is currently empty.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEmpty { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an empty instance of the AsyncQueue class.
|
|
||||||
/// </summary>
|
|
||||||
public AsyncQueue() : this(Enumerable.Empty<T>())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an instance of the AsyncQueue class, pre-populated
|
|
||||||
/// with the given collection of items.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="initialItems">
|
|
||||||
/// An IEnumerable containing the initial items with which the queue will
|
|
||||||
/// be populated.
|
|
||||||
/// </param>
|
|
||||||
public AsyncQueue(IEnumerable<T> initialItems)
|
|
||||||
{
|
|
||||||
this.itemQueue = new Queue<T>(initialItems);
|
|
||||||
this.requestQueue = new Queue<TaskCompletionSource<T>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enqueues an item onto the end of the queue.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item to be added to the queue.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// A Task which can be awaited until the synchronized enqueue
|
|
||||||
/// operation completes.
|
|
||||||
/// </returns>
|
|
||||||
public async Task EnqueueAsync(T item)
|
|
||||||
{
|
|
||||||
using (await queueLock.LockAsync())
|
|
||||||
{
|
|
||||||
TaskCompletionSource<T> requestTaskSource = null;
|
|
||||||
|
|
||||||
// Are any requests waiting?
|
|
||||||
while (this.requestQueue.Count > 0)
|
|
||||||
{
|
|
||||||
// Is the next request cancelled already?
|
|
||||||
requestTaskSource = this.requestQueue.Dequeue();
|
|
||||||
if (!requestTaskSource.Task.IsCanceled)
|
|
||||||
{
|
|
||||||
// Dispatch the item
|
|
||||||
requestTaskSource.SetResult(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No more requests waiting, queue the item for a later request
|
|
||||||
this.itemQueue.Enqueue(item);
|
|
||||||
this.IsEmpty = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dequeues an item from the queue or waits asynchronously
|
|
||||||
/// until an item is available.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// A Task which can be awaited until a value can be dequeued.
|
|
||||||
/// </returns>
|
|
||||||
public Task<T> DequeueAsync()
|
|
||||||
{
|
|
||||||
return this.DequeueAsync(CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dequeues an item from the queue or waits asynchronously
|
|
||||||
/// until an item is available. The wait can be cancelled
|
|
||||||
/// using the given CancellationToken.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">
|
|
||||||
/// A CancellationToken with which a dequeue wait can be cancelled.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A Task which can be awaited until a value can be dequeued.
|
|
||||||
/// </returns>
|
|
||||||
public async Task<T> DequeueAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
Task<T> requestTask;
|
|
||||||
|
|
||||||
using (await queueLock.LockAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
if (this.itemQueue.Count > 0)
|
|
||||||
{
|
|
||||||
// Items are waiting to be taken so take one immediately
|
|
||||||
T item = this.itemQueue.Dequeue();
|
|
||||||
this.IsEmpty = this.itemQueue.Count == 0;
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Queue the request for the next item
|
|
||||||
var requestTaskSource = new TaskCompletionSource<T>();
|
|
||||||
this.requestQueue.Enqueue(requestTaskSource);
|
|
||||||
|
|
||||||
// Register the wait task for cancel notifications
|
|
||||||
cancellationToken.Register(
|
|
||||||
() => requestTaskSource.TrySetCanceled());
|
|
||||||
|
|
||||||
requestTask = requestTaskSource.Task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the request task to complete outside of the lock
|
|
||||||
return await requestTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Utility
|
|
||||||
{
|
|
||||||
internal static class ObjectExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Extension to evaluate an object's ToString() method in an exception safe way. This will
|
|
||||||
/// extension method will not throw.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The object on which to call ToString()</param>
|
|
||||||
/// <returns>The ToString() return value or a suitable error message is that throws.</returns>
|
|
||||||
public static string SafeToString(this object obj)
|
|
||||||
{
|
|
||||||
string str;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
str = obj.ToString();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
str = $"<Error converting poperty value to string - {ex.Message}>";
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Utility
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the level indicators for log messages.
|
|
||||||
/// </summary>
|
|
||||||
public enum LogLevel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates a verbose log message.
|
|
||||||
/// </summary>
|
|
||||||
Verbose,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates a normal, non-verbose log message.
|
|
||||||
/// </summary>
|
|
||||||
Normal,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates a warning message.
|
|
||||||
/// </summary>
|
|
||||||
Warning,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates an error message.
|
|
||||||
/// </summary>
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a simple logging interface. May be replaced with a
|
|
||||||
/// more robust solution at a later date.
|
|
||||||
/// </summary>
|
|
||||||
public static class Logger
|
|
||||||
{
|
|
||||||
private static LogWriter logWriter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the Logger for the current session.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="logFilePath">
|
|
||||||
/// Optional. Specifies the path at which log messages will be written.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="minimumLogLevel">
|
|
||||||
/// Optional. Specifies the minimum log message level to write to the log file.
|
|
||||||
/// </param>
|
|
||||||
public static void Initialize(
|
|
||||||
string logFilePath = "SqlToolsService.log",
|
|
||||||
LogLevel minimumLogLevel = LogLevel.Normal)
|
|
||||||
{
|
|
||||||
if (logWriter != null)
|
|
||||||
{
|
|
||||||
logWriter.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Parameterize this
|
|
||||||
logWriter =
|
|
||||||
new LogWriter(
|
|
||||||
minimumLogLevel,
|
|
||||||
logFilePath,
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Closes the Logger.
|
|
||||||
/// </summary>
|
|
||||||
public static void Close()
|
|
||||||
{
|
|
||||||
if (logWriter != null)
|
|
||||||
{
|
|
||||||
logWriter.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes a message to the log file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="logLevel">The level at which the message will be written.</param>
|
|
||||||
/// <param name="logMessage">The message text to be written.</param>
|
|
||||||
/// <param name="callerName">The name of the calling method.</param>
|
|
||||||
/// <param name="callerSourceFile">The source file path where the calling method exists.</param>
|
|
||||||
/// <param name="callerLineNumber">The line number of the calling method.</param>
|
|
||||||
public static void Write(
|
|
||||||
LogLevel logLevel,
|
|
||||||
string logMessage,
|
|
||||||
[CallerMemberName] string callerName = null,
|
|
||||||
[CallerFilePath] string callerSourceFile = null,
|
|
||||||
[CallerLineNumber] int callerLineNumber = 0)
|
|
||||||
{
|
|
||||||
if (logWriter != null)
|
|
||||||
{
|
|
||||||
logWriter.Write(
|
|
||||||
logLevel,
|
|
||||||
logMessage,
|
|
||||||
callerName,
|
|
||||||
callerSourceFile,
|
|
||||||
callerLineNumber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class LogWriter : IDisposable
|
|
||||||
{
|
|
||||||
private TextWriter textWriter;
|
|
||||||
private LogLevel minimumLogLevel = LogLevel.Verbose;
|
|
||||||
|
|
||||||
public LogWriter(LogLevel minimumLogLevel, string logFilePath, bool deleteExisting)
|
|
||||||
{
|
|
||||||
this.minimumLogLevel = minimumLogLevel;
|
|
||||||
|
|
||||||
// Ensure that we have a usable log file path
|
|
||||||
if (!Path.IsPathRooted(logFilePath))
|
|
||||||
{
|
|
||||||
logFilePath =
|
|
||||||
Path.Combine(
|
|
||||||
AppContext.BaseDirectory,
|
|
||||||
logFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!this.TryOpenLogFile(logFilePath, deleteExisting))
|
|
||||||
{
|
|
||||||
// If the log file couldn't be opened at this location,
|
|
||||||
// try opening it in a more reliable path
|
|
||||||
this.TryOpenLogFile(
|
|
||||||
Path.Combine(
|
|
||||||
Environment.GetEnvironmentVariable("TEMP"),
|
|
||||||
Path.GetFileName(logFilePath)),
|
|
||||||
deleteExisting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Write(
|
|
||||||
LogLevel logLevel,
|
|
||||||
string logMessage,
|
|
||||||
string callerName = null,
|
|
||||||
string callerSourceFile = null,
|
|
||||||
int callerLineNumber = 0)
|
|
||||||
{
|
|
||||||
if (this.textWriter != null &&
|
|
||||||
logLevel >= this.minimumLogLevel)
|
|
||||||
{
|
|
||||||
// Print the timestamp and log level
|
|
||||||
this.textWriter.WriteLine(
|
|
||||||
"{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n",
|
|
||||||
DateTime.Now,
|
|
||||||
logLevel.ToString().ToUpper(),
|
|
||||||
callerName,
|
|
||||||
callerLineNumber,
|
|
||||||
callerSourceFile);
|
|
||||||
|
|
||||||
// Print out indented message lines
|
|
||||||
foreach (var messageLine in logMessage.Split('\n'))
|
|
||||||
{
|
|
||||||
this.textWriter.WriteLine(" " + messageLine.TrimEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish with a newline and flush the writer
|
|
||||||
this.textWriter.WriteLine();
|
|
||||||
this.textWriter.Flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (this.textWriter != null)
|
|
||||||
{
|
|
||||||
this.textWriter.Flush();
|
|
||||||
this.textWriter.Dispose();
|
|
||||||
this.textWriter = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryOpenLogFile(
|
|
||||||
string logFilePath,
|
|
||||||
bool deleteExisting)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Make sure the log directory exists
|
|
||||||
Directory.CreateDirectory(
|
|
||||||
Path.GetDirectoryName(
|
|
||||||
logFilePath));
|
|
||||||
|
|
||||||
// Open the log file for writing with UTF8 encoding
|
|
||||||
this.textWriter =
|
|
||||||
new StreamWriter(
|
|
||||||
new FileStream(
|
|
||||||
logFilePath,
|
|
||||||
deleteExisting ?
|
|
||||||
FileMode.Create :
|
|
||||||
FileMode.Append),
|
|
||||||
Encoding.UTF8);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
if (e is UnauthorizedAccessException ||
|
|
||||||
e is IOException)
|
|
||||||
{
|
|
||||||
// This exception is thrown when we can't open the file
|
|
||||||
// at the path in logFilePath. Return false to indicate
|
|
||||||
// that the log file couldn't be created.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unexpected exception, rethrow it
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Utility
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a SynchronizationContext implementation that can be used
|
|
||||||
/// in console applications or any thread which doesn't have its
|
|
||||||
/// own SynchronizationContext.
|
|
||||||
/// </summary>
|
|
||||||
public class ThreadSynchronizationContext : SynchronizationContext
|
|
||||||
{
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
private BlockingCollection<Tuple<SendOrPostCallback, object>> requestQueue =
|
|
||||||
new BlockingCollection<Tuple<SendOrPostCallback, object>>();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Posts a request for execution to the SynchronizationContext.
|
|
||||||
/// This will be executed on the SynchronizationContext's thread.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="callback">
|
|
||||||
/// The callback to be invoked on the SynchronizationContext's thread.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="state">
|
|
||||||
/// A state object to pass along to the callback when executed through
|
|
||||||
/// the SynchronizationContext.
|
|
||||||
/// </param>
|
|
||||||
public override void Post(SendOrPostCallback callback, object state)
|
|
||||||
{
|
|
||||||
// Add the request to the queue
|
|
||||||
this.requestQueue.Add(
|
|
||||||
new Tuple<SendOrPostCallback, object>(
|
|
||||||
callback, state));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts the SynchronizationContext message loop on the current thread.
|
|
||||||
/// </summary>
|
|
||||||
public void RunLoopOnCurrentThread()
|
|
||||||
{
|
|
||||||
Tuple<SendOrPostCallback, object> request;
|
|
||||||
|
|
||||||
while (this.requestQueue.TryTake(out request, Timeout.Infinite))
|
|
||||||
{
|
|
||||||
// Invoke the request's callback
|
|
||||||
request.Item1(request.Item2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ends the SynchronizationContext message loop.
|
|
||||||
/// </summary>
|
|
||||||
public void EndLoop()
|
|
||||||
{
|
|
||||||
// Tell the blocking queue that we're done
|
|
||||||
this.requestQueue.CompleteAdding();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices.Utility
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides common validation methods to simplify method
|
|
||||||
/// parameter checks.
|
|
||||||
/// </summary>
|
|
||||||
public static class Validate
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Throws ArgumentNullException if value is null.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
|
||||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
|
||||||
public static void IsNotNull(string parameterName, object valueToCheck)
|
|
||||||
{
|
|
||||||
if (valueToCheck == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(parameterName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Throws ArgumentOutOfRangeException if the value is outside
|
|
||||||
/// of the given lower and upper limits.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
|
||||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
|
||||||
/// <param name="lowerLimit">The lower limit which the value should not be less than.</param>
|
|
||||||
/// <param name="upperLimit">The upper limit which the value should not be greater than.</param>
|
|
||||||
public static void IsWithinRange(
|
|
||||||
string parameterName,
|
|
||||||
int valueToCheck,
|
|
||||||
int lowerLimit,
|
|
||||||
int upperLimit)
|
|
||||||
{
|
|
||||||
// TODO: Debug assert here if lowerLimit >= upperLimit
|
|
||||||
|
|
||||||
if (valueToCheck < lowerLimit || valueToCheck > upperLimit)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(
|
|
||||||
parameterName,
|
|
||||||
valueToCheck,
|
|
||||||
string.Format(
|
|
||||||
"Value is not between {0} and {1}",
|
|
||||||
lowerLimit,
|
|
||||||
upperLimit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Throws ArgumentOutOfRangeException if the value is greater than or equal
|
|
||||||
/// to the given upper limit.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
|
||||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
|
||||||
/// <param name="upperLimit">The upper limit which the value should be less than.</param>
|
|
||||||
public static void IsLessThan(
|
|
||||||
string parameterName,
|
|
||||||
int valueToCheck,
|
|
||||||
int upperLimit)
|
|
||||||
{
|
|
||||||
if (valueToCheck >= upperLimit)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(
|
|
||||||
parameterName,
|
|
||||||
valueToCheck,
|
|
||||||
string.Format(
|
|
||||||
"Value is greater than or equal to {0}",
|
|
||||||
upperLimit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Throws ArgumentOutOfRangeException if the value is less than or equal
|
|
||||||
/// to the given lower limit.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
|
||||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
|
||||||
/// <param name="lowerLimit">The lower limit which the value should be greater than.</param>
|
|
||||||
public static void IsGreaterThan(
|
|
||||||
string parameterName,
|
|
||||||
int valueToCheck,
|
|
||||||
int lowerLimit)
|
|
||||||
{
|
|
||||||
if (valueToCheck < lowerLimit)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(
|
|
||||||
parameterName,
|
|
||||||
valueToCheck,
|
|
||||||
string.Format(
|
|
||||||
"Value is less than or equal to {0}",
|
|
||||||
lowerLimit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Throws ArgumentException if the value is equal to the undesired value.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TValue">The type of value to be validated.</typeparam>
|
|
||||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
|
||||||
/// <param name="undesiredValue">The value that valueToCheck should not equal.</param>
|
|
||||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
|
||||||
public static void IsNotEqual<TValue>(
|
|
||||||
string parameterName,
|
|
||||||
TValue valueToCheck,
|
|
||||||
TValue undesiredValue)
|
|
||||||
{
|
|
||||||
if (EqualityComparer<TValue>.Default.Equals(valueToCheck, undesiredValue))
|
|
||||||
{
|
|
||||||
throw new ArgumentException(
|
|
||||||
string.Format(
|
|
||||||
"The given value '{0}' should not equal '{1}'",
|
|
||||||
valueToCheck,
|
|
||||||
undesiredValue),
|
|
||||||
parameterName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Throws ArgumentException if the value is null, an empty string,
|
|
||||||
/// or a string containing only whitespace.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
|
||||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
|
||||||
public static void IsNotNullOrEmptyString(string parameterName, string valueToCheck)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(valueToCheck))
|
|
||||||
{
|
|
||||||
throw new ArgumentException(
|
|
||||||
"Parameter contains a null, empty, or whitespace string.",
|
|
||||||
parameterName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides details about a position in a file buffer. All
|
|
||||||
/// positions are expressed in 1-based positions (i.e. the
|
|
||||||
/// first line and column in the file is position 1,1).
|
|
||||||
/// </summary>
|
|
||||||
[DebuggerDisplay("Position = {Line}:{Column}")]
|
|
||||||
public class BufferPosition
|
|
||||||
{
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides an instance that represents a position that has not been set.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BufferPosition None = new BufferPosition(-1, -1);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the line number of the position in the buffer.
|
|
||||||
/// </summary>
|
|
||||||
public int Line { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the column number of the position in the buffer.
|
|
||||||
/// </summary>
|
|
||||||
public int Column { get; private set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the BufferPosition class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="line">The line number of the position.</param>
|
|
||||||
/// <param name="column">The column number of the position.</param>
|
|
||||||
public BufferPosition(int line, int column)
|
|
||||||
{
|
|
||||||
this.Line = line;
|
|
||||||
this.Column = column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compares two instances of the BufferPosition class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The object to which this instance will be compared.</param>
|
|
||||||
/// <returns>True if the positions are equal, false otherwise.</returns>
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
if (!(obj is BufferPosition))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferPosition other = (BufferPosition)obj;
|
|
||||||
|
|
||||||
return
|
|
||||||
this.Line == other.Line &&
|
|
||||||
this.Column == other.Column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates a unique hash code that represents this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A hash code representing this instance.</returns>
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return this.Line.GetHashCode() ^ this.Column.GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compares two positions to check if one is greater than the other.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="positionOne">The first position to compare.</param>
|
|
||||||
/// <param name="positionTwo">The second position to compare.</param>
|
|
||||||
/// <returns>True if positionOne is greater than positionTwo.</returns>
|
|
||||||
public static bool operator >(BufferPosition positionOne, BufferPosition positionTwo)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
(positionOne != null && positionTwo == null) ||
|
|
||||||
(positionOne.Line > positionTwo.Line) ||
|
|
||||||
(positionOne.Line == positionTwo.Line &&
|
|
||||||
positionOne.Column > positionTwo.Column);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compares two positions to check if one is less than the other.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="positionOne">The first position to compare.</param>
|
|
||||||
/// <param name="positionTwo">The second position to compare.</param>
|
|
||||||
/// <returns>True if positionOne is less than positionTwo.</returns>
|
|
||||||
public static bool operator <(BufferPosition positionOne, BufferPosition positionTwo)
|
|
||||||
{
|
|
||||||
return positionTwo > positionOne;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides details about a range between two positions in
|
|
||||||
/// a file buffer.
|
|
||||||
/// </summary>
|
|
||||||
[DebuggerDisplay("Start = {Start.Line}:{Start.Column}, End = {End.Line}:{End.Column}")]
|
|
||||||
public class BufferRange
|
|
||||||
{
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides an instance that represents a range that has not been set.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly BufferRange None = new BufferRange(0, 0, 0, 0);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the start position of the range in the buffer.
|
|
||||||
/// </summary>
|
|
||||||
public BufferPosition Start { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the end position of the range in the buffer.
|
|
||||||
/// </summary>
|
|
||||||
public BufferPosition End { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if the current range is non-zero, i.e.
|
|
||||||
/// contains valid start and end positions.
|
|
||||||
/// </summary>
|
|
||||||
public bool HasRange
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this.Equals(BufferRange.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the BufferRange class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="start">The start position of the range.</param>
|
|
||||||
/// <param name="end">The end position of the range.</param>
|
|
||||||
public BufferRange(BufferPosition start, BufferPosition end)
|
|
||||||
{
|
|
||||||
if (start > end)
|
|
||||||
{
|
|
||||||
throw new ArgumentException(
|
|
||||||
string.Format(
|
|
||||||
"Start position ({0}, {1}) must come before or be equal to the end position ({2}, {3}).",
|
|
||||||
start.Line, start.Column,
|
|
||||||
end.Line, end.Column));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Start = start;
|
|
||||||
this.End = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the BufferRange class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startLine">The 1-based starting line number of the range.</param>
|
|
||||||
/// <param name="startColumn">The 1-based starting column number of the range.</param>
|
|
||||||
/// <param name="endLine">The 1-based ending line number of the range.</param>
|
|
||||||
/// <param name="endColumn">The 1-based ending column number of the range.</param>
|
|
||||||
public BufferRange(
|
|
||||||
int startLine,
|
|
||||||
int startColumn,
|
|
||||||
int endLine,
|
|
||||||
int endColumn)
|
|
||||||
{
|
|
||||||
this.Start = new BufferPosition(startLine, startColumn);
|
|
||||||
this.End = new BufferPosition(endLine, endColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compares two instances of the BufferRange class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The object to which this instance will be compared.</param>
|
|
||||||
/// <returns>True if the ranges are equal, false otherwise.</returns>
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
if (!(obj is BufferRange))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferRange other = (BufferRange)obj;
|
|
||||||
|
|
||||||
return
|
|
||||||
this.Start.Equals(other.Start) &&
|
|
||||||
this.End.Equals(other.End);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates a unique hash code that represents this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A hash code representing this instance.</returns>
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return this.Start.GetHashCode() ^ this.End.GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains details relating to a content change in an open file.
|
|
||||||
/// </summary>
|
|
||||||
public class FileChange
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The string which is to be inserted in the file.
|
|
||||||
/// </summary>
|
|
||||||
public string InsertString { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The 1-based line number where the change starts.
|
|
||||||
/// </summary>
|
|
||||||
public int Line { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The 1-based column offset where the change starts.
|
|
||||||
/// </summary>
|
|
||||||
public int Offset { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The 1-based line number where the change ends.
|
|
||||||
/// </summary>
|
|
||||||
public int EndLine { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The 1-based column offset where the change ends.
|
|
||||||
/// </summary>
|
|
||||||
public int EndOffset { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides details and operations for a buffer position in a
|
|
||||||
/// specific file.
|
|
||||||
/// </summary>
|
|
||||||
public class FilePosition : BufferPosition
|
|
||||||
{
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
private ScriptFile scriptFile;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new FilePosition instance for the 1-based line and
|
|
||||||
/// column numbers in the specified file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFile">The ScriptFile in which the position is located.</param>
|
|
||||||
/// <param name="line">The 1-based line number in the file.</param>
|
|
||||||
/// <param name="column">The 1-based column number in the file.</param>
|
|
||||||
public FilePosition(
|
|
||||||
ScriptFile scriptFile,
|
|
||||||
int line,
|
|
||||||
int column)
|
|
||||||
: base(line, column)
|
|
||||||
{
|
|
||||||
this.scriptFile = scriptFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new FilePosition instance for the specified file by
|
|
||||||
/// copying the specified BufferPosition
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFile">The ScriptFile in which the position is located.</param>
|
|
||||||
/// <param name="copiedPosition">The original BufferPosition from which the line and column will be copied.</param>
|
|
||||||
public FilePosition(
|
|
||||||
ScriptFile scriptFile,
|
|
||||||
BufferPosition copiedPosition)
|
|
||||||
: this(scriptFile, copiedPosition.Line, copiedPosition.Column)
|
|
||||||
{
|
|
||||||
scriptFile.ValidatePosition(copiedPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a FilePosition relative to this position by adding the
|
|
||||||
/// provided line and column offset relative to the contents of
|
|
||||||
/// the current file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lineOffset">The line offset to add to this position.</param>
|
|
||||||
/// <param name="columnOffset">The column offset to add to this position.</param>
|
|
||||||
/// <returns>A new FilePosition instance for the calculated position.</returns>
|
|
||||||
public FilePosition AddOffset(int lineOffset, int columnOffset)
|
|
||||||
{
|
|
||||||
return this.scriptFile.CalculatePosition(
|
|
||||||
this,
|
|
||||||
lineOffset,
|
|
||||||
columnOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a FilePosition for the line and column position
|
|
||||||
/// of the beginning of the current line after any initial
|
|
||||||
/// whitespace for indentation.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A new FilePosition instance for the calculated position.</returns>
|
|
||||||
public FilePosition GetLineStart()
|
|
||||||
{
|
|
||||||
string scriptLine = scriptFile.FileLines[this.Line - 1];
|
|
||||||
|
|
||||||
int lineStartColumn = 1;
|
|
||||||
for (int i = 0; i < scriptLine.Length; i++)
|
|
||||||
{
|
|
||||||
if (!char.IsWhiteSpace(scriptLine[i]))
|
|
||||||
{
|
|
||||||
lineStartColumn = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FilePosition(this.scriptFile, this.Line, lineStartColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a FilePosition for the line and column position
|
|
||||||
/// of the end of the current line.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A new FilePosition instance for the calculated position.</returns>
|
|
||||||
public FilePosition GetLineEnd()
|
|
||||||
{
|
|
||||||
string scriptLine = scriptFile.FileLines[this.Line - 1];
|
|
||||||
return new FilePosition(this.scriptFile, this.Line, scriptLine.Length + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,538 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains the details and contents of an open script file.
|
|
||||||
/// </summary>
|
|
||||||
public class ScriptFile
|
|
||||||
{
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
private Version SqlToolsVersion;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a unique string that identifies this file. At this time,
|
|
||||||
/// this property returns a normalized version of the value stored
|
|
||||||
/// in the FilePath property.
|
|
||||||
/// </summary>
|
|
||||||
public string Id
|
|
||||||
{
|
|
||||||
get { return this.FilePath.ToLower(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path at which this file resides.
|
|
||||||
/// </summary>
|
|
||||||
public string FilePath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path which the editor client uses to identify this file.
|
|
||||||
/// </summary>
|
|
||||||
public string ClientFilePath { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a boolean that determines whether
|
|
||||||
/// semantic analysis should be enabled for this file.
|
|
||||||
/// For internal use only.
|
|
||||||
/// </summary>
|
|
||||||
internal bool IsAnalysisEnabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a boolean that determines whether this file is
|
|
||||||
/// in-memory or not (either unsaved or non-file content).
|
|
||||||
/// </summary>
|
|
||||||
public bool IsInMemory { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a string containing the full contents of the file.
|
|
||||||
/// </summary>
|
|
||||||
public string Contents
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return string.Join("\r\n", this.FileLines);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a BufferRange that represents the entire content
|
|
||||||
/// range of the file.
|
|
||||||
/// </summary>
|
|
||||||
public BufferRange FileRange { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the list of syntax markers found by parsing this
|
|
||||||
/// file's contents.
|
|
||||||
/// </summary>
|
|
||||||
public ScriptFileMarker[] SyntaxMarkers
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the list of strings for each line of the file.
|
|
||||||
/// </summary>
|
|
||||||
internal IList<string> FileLines
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the array of filepaths dot sourced in this ScriptFile
|
|
||||||
/// </summary>
|
|
||||||
public string[] ReferencedFiles
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new ScriptFile instance by reading file contents from
|
|
||||||
/// the given TextReader.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">The path at which the script file resides.</param>
|
|
||||||
/// <param name="clientFilePath">The path which the client uses to identify the file.</param>
|
|
||||||
/// <param name="textReader">The TextReader to use for reading the file's contents.</param>
|
|
||||||
/// <param name="SqlToolsVersion">The version of SqlTools for which the script is being parsed.</param>
|
|
||||||
public ScriptFile(
|
|
||||||
string filePath,
|
|
||||||
string clientFilePath,
|
|
||||||
TextReader textReader,
|
|
||||||
Version SqlToolsVersion)
|
|
||||||
{
|
|
||||||
this.FilePath = filePath;
|
|
||||||
this.ClientFilePath = clientFilePath;
|
|
||||||
this.IsAnalysisEnabled = true;
|
|
||||||
this.IsInMemory = Workspace.IsPathInMemory(filePath);
|
|
||||||
this.SqlToolsVersion = SqlToolsVersion;
|
|
||||||
|
|
||||||
this.SetFileContents(textReader.ReadToEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new ScriptFile instance with the specified file contents.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">The path at which the script file resides.</param>
|
|
||||||
/// <param name="clientFilePath">The path which the client uses to identify the file.</param>
|
|
||||||
/// <param name="initialBuffer">The initial contents of the script file.</param>
|
|
||||||
/// <param name="SqlToolsVersion">The version of SqlTools for which the script is being parsed.</param>
|
|
||||||
public ScriptFile(
|
|
||||||
string filePath,
|
|
||||||
string clientFilePath,
|
|
||||||
string initialBuffer,
|
|
||||||
Version SqlToolsVersion)
|
|
||||||
{
|
|
||||||
this.FilePath = filePath;
|
|
||||||
this.ClientFilePath = clientFilePath;
|
|
||||||
this.IsAnalysisEnabled = true;
|
|
||||||
this.SqlToolsVersion = SqlToolsVersion;
|
|
||||||
|
|
||||||
this.SetFileContents(initialBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a line from the file's contents.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lineNumber">The 1-based line number in the file.</param>
|
|
||||||
/// <returns>The complete line at the given line number.</returns>
|
|
||||||
public string GetLine(int lineNumber)
|
|
||||||
{
|
|
||||||
Validate.IsWithinRange(
|
|
||||||
"lineNumber", lineNumber,
|
|
||||||
1, this.FileLines.Count + 1);
|
|
||||||
|
|
||||||
return this.FileLines[lineNumber - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a range of lines from the file's contents.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bufferRange">The buffer range from which lines will be extracted.</param>
|
|
||||||
/// <returns>An array of strings from the specified range of the file.</returns>
|
|
||||||
public string[] GetLinesInRange(BufferRange bufferRange)
|
|
||||||
{
|
|
||||||
this.ValidatePosition(bufferRange.Start);
|
|
||||||
this.ValidatePosition(bufferRange.End);
|
|
||||||
|
|
||||||
List<string> linesInRange = new List<string>();
|
|
||||||
|
|
||||||
int startLine = bufferRange.Start.Line,
|
|
||||||
endLine = bufferRange.End.Line;
|
|
||||||
|
|
||||||
for (int line = startLine; line <= endLine; line++)
|
|
||||||
{
|
|
||||||
string currentLine = this.FileLines[line - 1];
|
|
||||||
int startColumn =
|
|
||||||
line == startLine
|
|
||||||
? bufferRange.Start.Column
|
|
||||||
: 1;
|
|
||||||
int endColumn =
|
|
||||||
line == endLine
|
|
||||||
? bufferRange.End.Column
|
|
||||||
: currentLine.Length + 1;
|
|
||||||
|
|
||||||
currentLine =
|
|
||||||
currentLine.Substring(
|
|
||||||
startColumn - 1,
|
|
||||||
endColumn - startColumn);
|
|
||||||
|
|
||||||
linesInRange.Add(currentLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
return linesInRange.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Throws ArgumentOutOfRangeException if the given position is outside
|
|
||||||
/// of the file's buffer extents.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bufferPosition">The position in the buffer to be validated.</param>
|
|
||||||
public void ValidatePosition(BufferPosition bufferPosition)
|
|
||||||
{
|
|
||||||
this.ValidatePosition(
|
|
||||||
bufferPosition.Line,
|
|
||||||
bufferPosition.Column);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Throws ArgumentOutOfRangeException if the given position is outside
|
|
||||||
/// of the file's buffer extents.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="line">The 1-based line to be validated.</param>
|
|
||||||
/// <param name="column">The 1-based column to be validated.</param>
|
|
||||||
public void ValidatePosition(int line, int column)
|
|
||||||
{
|
|
||||||
if (line < 1 || line > this.FileLines.Count + 1)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException("Position is outside of file line range.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// The maximum column is either one past the length of the string
|
|
||||||
// or 1 if the string is empty.
|
|
||||||
string lineString = this.FileLines[line - 1];
|
|
||||||
int maxColumn = lineString.Length > 0 ? lineString.Length + 1 : 1;
|
|
||||||
|
|
||||||
if (column < 1 || column > maxColumn)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(
|
|
||||||
string.Format(
|
|
||||||
"Position is outside of column range for line {0}.",
|
|
||||||
line));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies the provided FileChange to the file's contents
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fileChange">The FileChange to apply to the file's contents.</param>
|
|
||||||
public void ApplyChange(FileChange fileChange)
|
|
||||||
{
|
|
||||||
this.ValidatePosition(fileChange.Line, fileChange.Offset);
|
|
||||||
this.ValidatePosition(fileChange.EndLine, fileChange.EndOffset);
|
|
||||||
|
|
||||||
// Break up the change lines
|
|
||||||
string[] changeLines = fileChange.InsertString.Split('\n');
|
|
||||||
|
|
||||||
// Get the first fragment of the first line
|
|
||||||
string firstLineFragment =
|
|
||||||
this.FileLines[fileChange.Line - 1]
|
|
||||||
.Substring(0, fileChange.Offset - 1);
|
|
||||||
|
|
||||||
// Get the last fragment of the last line
|
|
||||||
string endLine = this.FileLines[fileChange.EndLine - 1];
|
|
||||||
string lastLineFragment =
|
|
||||||
endLine.Substring(
|
|
||||||
fileChange.EndOffset - 1,
|
|
||||||
(this.FileLines[fileChange.EndLine - 1].Length - fileChange.EndOffset) + 1);
|
|
||||||
|
|
||||||
// Remove the old lines
|
|
||||||
for (int i = 0; i <= fileChange.EndLine - fileChange.Line; i++)
|
|
||||||
{
|
|
||||||
this.FileLines.RemoveAt(fileChange.Line - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build and insert the new lines
|
|
||||||
int currentLineNumber = fileChange.Line;
|
|
||||||
for (int changeIndex = 0; changeIndex < changeLines.Length; changeIndex++)
|
|
||||||
{
|
|
||||||
// Since we split the lines above using \n, make sure to
|
|
||||||
// trim the ending \r's off as well.
|
|
||||||
string finalLine = changeLines[changeIndex].TrimEnd('\r');
|
|
||||||
|
|
||||||
// Should we add first or last line fragments?
|
|
||||||
if (changeIndex == 0)
|
|
||||||
{
|
|
||||||
// Append the first line fragment
|
|
||||||
finalLine = firstLineFragment + finalLine;
|
|
||||||
}
|
|
||||||
if (changeIndex == changeLines.Length - 1)
|
|
||||||
{
|
|
||||||
// Append the last line fragment
|
|
||||||
finalLine = finalLine + lastLineFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.FileLines.Insert(currentLineNumber - 1, finalLine);
|
|
||||||
currentLineNumber++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the script again to be up-to-date
|
|
||||||
this.ParseFileContents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the zero-based character offset of a given
|
|
||||||
/// line and column position in the file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lineNumber">The 1-based line number from which the offset is calculated.</param>
|
|
||||||
/// <param name="columnNumber">The 1-based column number from which the offset is calculated.</param>
|
|
||||||
/// <returns>The zero-based offset for the given file position.</returns>
|
|
||||||
public int GetOffsetAtPosition(int lineNumber, int columnNumber)
|
|
||||||
{
|
|
||||||
Validate.IsWithinRange("lineNumber", lineNumber, 1, this.FileLines.Count);
|
|
||||||
Validate.IsGreaterThan("columnNumber", columnNumber, 0);
|
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
for(int i = 0; i < lineNumber; i++)
|
|
||||||
{
|
|
||||||
if (i == lineNumber - 1)
|
|
||||||
{
|
|
||||||
// Subtract 1 to account for 1-based column numbering
|
|
||||||
offset += columnNumber - 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Add an offset to account for the current platform's newline characters
|
|
||||||
offset += this.FileLines[i].Length + Environment.NewLine.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates a FilePosition relative to a starting BufferPosition
|
|
||||||
/// using the given 1-based line and column offset.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="originalPosition">The original BufferPosition from which an new position should be calculated.</param>
|
|
||||||
/// <param name="lineOffset">The 1-based line offset added to the original position in this file.</param>
|
|
||||||
/// <param name="columnOffset">The 1-based column offset added to the original position in this file.</param>
|
|
||||||
/// <returns>A new FilePosition instance with the resulting line and column number.</returns>
|
|
||||||
public FilePosition CalculatePosition(
|
|
||||||
BufferPosition originalPosition,
|
|
||||||
int lineOffset,
|
|
||||||
int columnOffset)
|
|
||||||
{
|
|
||||||
int newLine = originalPosition.Line + lineOffset,
|
|
||||||
newColumn = originalPosition.Column + columnOffset;
|
|
||||||
|
|
||||||
this.ValidatePosition(newLine, newColumn);
|
|
||||||
|
|
||||||
string scriptLine = this.FileLines[newLine - 1];
|
|
||||||
newColumn = Math.Min(scriptLine.Length + 1, newColumn);
|
|
||||||
|
|
||||||
return new FilePosition(this, newLine, newColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the 1-based line and column number position based
|
|
||||||
/// on the given buffer offset.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bufferOffset">The buffer offset to convert.</param>
|
|
||||||
/// <returns>A new BufferPosition containing the position of the offset.</returns>
|
|
||||||
public BufferPosition GetPositionAtOffset(int bufferOffset)
|
|
||||||
{
|
|
||||||
BufferRange bufferRange =
|
|
||||||
GetRangeBetweenOffsets(
|
|
||||||
bufferOffset, bufferOffset);
|
|
||||||
|
|
||||||
return bufferRange.Start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the 1-based line and column number range based on
|
|
||||||
/// the given start and end buffer offsets.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startOffset">The start offset of the range.</param>
|
|
||||||
/// <param name="endOffset">The end offset of the range.</param>
|
|
||||||
/// <returns>A new BufferRange containing the positions in the offset range.</returns>
|
|
||||||
public BufferRange GetRangeBetweenOffsets(int startOffset, int endOffset)
|
|
||||||
{
|
|
||||||
bool foundStart = false;
|
|
||||||
int currentOffset = 0;
|
|
||||||
int searchedOffset = startOffset;
|
|
||||||
|
|
||||||
BufferPosition startPosition = new BufferPosition(0, 0);
|
|
||||||
BufferPosition endPosition = startPosition;
|
|
||||||
|
|
||||||
int line = 0;
|
|
||||||
while (line < this.FileLines.Count)
|
|
||||||
{
|
|
||||||
if (searchedOffset <= currentOffset + this.FileLines[line].Length)
|
|
||||||
{
|
|
||||||
int column = searchedOffset - currentOffset;
|
|
||||||
|
|
||||||
// Have we already found the start position?
|
|
||||||
if (foundStart)
|
|
||||||
{
|
|
||||||
// Assign the end position and end the search
|
|
||||||
endPosition = new BufferPosition(line + 1, column + 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
startPosition = new BufferPosition(line + 1, column + 1);
|
|
||||||
|
|
||||||
// Do we only need to find the start position?
|
|
||||||
if (startOffset == endOffset)
|
|
||||||
{
|
|
||||||
endPosition = startPosition;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Since the end offset can be on the same line,
|
|
||||||
// skip the line increment and continue searching
|
|
||||||
// for the end position
|
|
||||||
foundStart = true;
|
|
||||||
searchedOffset = endOffset;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increase the current offset and include newline length
|
|
||||||
currentOffset += this.FileLines[line].Length + Environment.NewLine.Length;
|
|
||||||
line++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BufferRange(startPosition, endPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private void SetFileContents(string fileContents)
|
|
||||||
{
|
|
||||||
// Split the file contents into lines and trim
|
|
||||||
// any carriage returns from the strings.
|
|
||||||
this.FileLines =
|
|
||||||
fileContents
|
|
||||||
.Split('\n')
|
|
||||||
.Select(line => line.TrimEnd('\r'))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Parse the contents to get syntax tree and errors
|
|
||||||
this.ParseFileContents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parses the current file contents to get the AST, tokens,
|
|
||||||
/// and parse errors.
|
|
||||||
/// </summary>
|
|
||||||
private void ParseFileContents()
|
|
||||||
{
|
|
||||||
#if false
|
|
||||||
ParseError[] parseErrors = null;
|
|
||||||
|
|
||||||
// First, get the updated file range
|
|
||||||
int lineCount = this.FileLines.Count;
|
|
||||||
if (lineCount > 0)
|
|
||||||
{
|
|
||||||
this.FileRange =
|
|
||||||
new BufferRange(
|
|
||||||
new BufferPosition(1, 1),
|
|
||||||
new BufferPosition(
|
|
||||||
lineCount + 1,
|
|
||||||
this.FileLines[lineCount - 1].Length + 1));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.FileRange = BufferRange.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
#if SqlToolsv5r2
|
|
||||||
// This overload appeared with Windows 10 Update 1
|
|
||||||
if (this.SqlToolsVersion.Major >= 5 &&
|
|
||||||
this.SqlToolsVersion.Build >= 10586)
|
|
||||||
{
|
|
||||||
// Include the file path so that module relative
|
|
||||||
// paths are evaluated correctly
|
|
||||||
this.ScriptAst =
|
|
||||||
Parser.ParseInput(
|
|
||||||
this.Contents,
|
|
||||||
this.FilePath,
|
|
||||||
out this.scriptTokens,
|
|
||||||
out parseErrors);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.ScriptAst =
|
|
||||||
Parser.ParseInput(
|
|
||||||
this.Contents,
|
|
||||||
out this.scriptTokens,
|
|
||||||
out parseErrors);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
this.ScriptAst =
|
|
||||||
Parser.ParseInput(
|
|
||||||
this.Contents,
|
|
||||||
out this.scriptTokens,
|
|
||||||
out parseErrors);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
catch (RuntimeException ex)
|
|
||||||
{
|
|
||||||
var parseError =
|
|
||||||
new ParseError(
|
|
||||||
null,
|
|
||||||
ex.ErrorRecord.FullyQualifiedErrorId,
|
|
||||||
ex.Message);
|
|
||||||
|
|
||||||
parseErrors = new[] { parseError };
|
|
||||||
this.scriptTokens = new Token[0];
|
|
||||||
this.ScriptAst = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate parse errors into syntax markers
|
|
||||||
this.SyntaxMarkers =
|
|
||||||
parseErrors
|
|
||||||
.Select(ScriptFileMarker.FromParseError)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
//Get all dot sourced referenced files and store them
|
|
||||||
this.ReferencedFiles =
|
|
||||||
AstOperations.FindDotSourcedIncludes(this.ScriptAst);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the message level of a script file marker.
|
|
||||||
/// </summary>
|
|
||||||
public enum ScriptFileMarkerLevel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The marker represents an informational message.
|
|
||||||
/// </summary>
|
|
||||||
Information = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The marker represents a warning message.
|
|
||||||
/// </summary>
|
|
||||||
Warning,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The marker represents an error message.
|
|
||||||
/// </summary>
|
|
||||||
Error
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains details about a marker that should be displayed
|
|
||||||
/// for the a script file. The marker information could come
|
|
||||||
/// from syntax parsing or semantic analysis of the script.
|
|
||||||
/// </summary>
|
|
||||||
public class ScriptFileMarker
|
|
||||||
{
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the marker's message string.
|
|
||||||
/// </summary>
|
|
||||||
public string Message { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the marker's message level.
|
|
||||||
/// </summary>
|
|
||||||
public ScriptFileMarkerLevel Level { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the ScriptRegion where the marker should appear.
|
|
||||||
/// </summary>
|
|
||||||
public ScriptRegion ScriptRegion { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
//using System.Management.Automation.Language;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains details about a specific region of text in script file.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class ScriptRegion
|
|
||||||
{
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the file path of the script file in which this region is contained.
|
|
||||||
/// </summary>
|
|
||||||
public string File { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the text that is contained within the region.
|
|
||||||
/// </summary>
|
|
||||||
public string Text { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the starting line number of the region.
|
|
||||||
/// </summary>
|
|
||||||
public int StartLineNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the starting column number of the region.
|
|
||||||
/// </summary>
|
|
||||||
public int StartColumnNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the starting file offset of the region.
|
|
||||||
/// </summary>
|
|
||||||
public int StartOffset { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the ending line number of the region.
|
|
||||||
/// </summary>
|
|
||||||
public int EndLineNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the ending column number of the region.
|
|
||||||
/// </summary>
|
|
||||||
public int EndColumnNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the ending file offset of the region.
|
|
||||||
/// </summary>
|
|
||||||
public int EndOffset { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
#if false
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the ScriptRegion class from an
|
|
||||||
/// instance of an IScriptExtent implementation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptExtent">
|
|
||||||
/// The IScriptExtent to copy into the ScriptRegion.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A new ScriptRegion instance with the same details as the IScriptExtent.
|
|
||||||
/// </returns>
|
|
||||||
public static ScriptRegion Create(IScriptExtent scriptExtent)
|
|
||||||
{
|
|
||||||
return new ScriptRegion
|
|
||||||
{
|
|
||||||
File = scriptExtent.File,
|
|
||||||
Text = scriptExtent.Text,
|
|
||||||
StartLineNumber = scriptExtent.StartLineNumber,
|
|
||||||
StartColumnNumber = scriptExtent.StartColumnNumber,
|
|
||||||
StartOffset = scriptExtent.StartOffset,
|
|
||||||
EndLineNumber = scriptExtent.EndLineNumber,
|
|
||||||
EndColumnNumber = scriptExtent.EndColumnNumber,
|
|
||||||
EndOffset = scriptExtent.EndOffset
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.EditorServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Manages a "workspace" of script files that are open for a particular
|
|
||||||
/// editing session. Also helps to navigate references between ScriptFiles.
|
|
||||||
/// </summary>
|
|
||||||
public class Workspace
|
|
||||||
{
|
|
||||||
#region Private Fields
|
|
||||||
|
|
||||||
private Version SqlToolsVersion;
|
|
||||||
private Dictionary<string, ScriptFile> workspaceFiles = new Dictionary<string, ScriptFile>();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the root path of the workspace.
|
|
||||||
/// </summary>
|
|
||||||
public string WorkspacePath { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the Workspace class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="SqlToolsVersion">The version of SqlTools for which scripts will be parsed.</param>
|
|
||||||
public Workspace(Version SqlToolsVersion)
|
|
||||||
{
|
|
||||||
this.SqlToolsVersion = SqlToolsVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Public Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an open file in the workspace. If the file isn't open but
|
|
||||||
/// exists on the filesystem, load and return it.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">The file path at which the script resides.</param>
|
|
||||||
/// <exception cref="FileNotFoundException">
|
|
||||||
/// <paramref name="filePath"/> is not found.
|
|
||||||
/// </exception>
|
|
||||||
/// <exception cref="ArgumentException">
|
|
||||||
/// <paramref name="filePath"/> contains a null or empty string.
|
|
||||||
/// </exception>
|
|
||||||
public ScriptFile GetFile(string filePath)
|
|
||||||
{
|
|
||||||
Validate.IsNotNullOrEmptyString("filePath", filePath);
|
|
||||||
|
|
||||||
// Resolve the full file path
|
|
||||||
string resolvedFilePath = this.ResolveFilePath(filePath);
|
|
||||||
string keyName = resolvedFilePath.ToLower();
|
|
||||||
|
|
||||||
// Make sure the file isn't already loaded into the workspace
|
|
||||||
ScriptFile scriptFile = null;
|
|
||||||
if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile))
|
|
||||||
{
|
|
||||||
// This method allows FileNotFoundException to bubble up
|
|
||||||
// if the file isn't found.
|
|
||||||
using (FileStream fileStream = new FileStream(resolvedFilePath, FileMode.Open, FileAccess.Read))
|
|
||||||
using (StreamReader streamReader = new StreamReader(fileStream, Encoding.UTF8))
|
|
||||||
{
|
|
||||||
scriptFile =
|
|
||||||
new ScriptFile(
|
|
||||||
resolvedFilePath,
|
|
||||||
filePath,
|
|
||||||
streamReader,
|
|
||||||
this.SqlToolsVersion);
|
|
||||||
|
|
||||||
this.workspaceFiles.Add(keyName, scriptFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Write(LogLevel.Verbose, "Opened file on disk: " + resolvedFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return scriptFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ResolveFilePath(string filePath)
|
|
||||||
{
|
|
||||||
if (!IsPathInMemory(filePath))
|
|
||||||
{
|
|
||||||
if (filePath.StartsWith(@"file://"))
|
|
||||||
{
|
|
||||||
// Client sent the path in URI format, extract the local path and trim
|
|
||||||
// any extraneous slashes
|
|
||||||
Uri fileUri = new Uri(filePath);
|
|
||||||
filePath = fileUri.LocalPath.TrimStart('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some clients send paths with UNIX-style slashes, replace those if necessary
|
|
||||||
filePath = filePath.Replace('/', '\\');
|
|
||||||
|
|
||||||
// Clients could specify paths with escaped space, [ and ] characters which .NET APIs
|
|
||||||
// will not handle. These paths will get appropriately escaped just before being passed
|
|
||||||
// into the SqlTools engine.
|
|
||||||
filePath = UnescapePath(filePath);
|
|
||||||
|
|
||||||
// Get the absolute file path
|
|
||||||
filePath = Path.GetFullPath(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Write(LogLevel.Verbose, "Resolved path: " + filePath);
|
|
||||||
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool IsPathInMemory(string filePath)
|
|
||||||
{
|
|
||||||
// When viewing SqlTools files in the Git diff viewer, VS Code
|
|
||||||
// sends the contents of the file at HEAD with a URI that starts
|
|
||||||
// with 'inmemory'. Untitled files which have been marked of
|
|
||||||
// type SqlTools have a path starting with 'untitled'.
|
|
||||||
return
|
|
||||||
filePath.StartsWith("inmemory") ||
|
|
||||||
filePath.StartsWith("untitled");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unescapes any escaped [, ] or space characters. Typically use this before calling a
|
|
||||||
/// .NET API that doesn't understand PowerShell escaped chars.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path to unescape.</param>
|
|
||||||
/// <returns>The path with the ` character before [, ] and spaces removed.</returns>
|
|
||||||
public static string UnescapePath(string path)
|
|
||||||
{
|
|
||||||
if (!path.Contains("`"))
|
|
||||||
{
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Regex.Replace(path, @"`(?=[ \[\]])", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a new ScriptFile instance which is identified by the given file
|
|
||||||
/// path and initially contains the given buffer contents.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath"></param>
|
|
||||||
/// <param name="initialBuffer"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public ScriptFile GetFileBuffer(string filePath, string initialBuffer)
|
|
||||||
{
|
|
||||||
Validate.IsNotNullOrEmptyString("filePath", filePath);
|
|
||||||
|
|
||||||
// Resolve the full file path
|
|
||||||
string resolvedFilePath = this.ResolveFilePath(filePath);
|
|
||||||
string keyName = resolvedFilePath.ToLower();
|
|
||||||
|
|
||||||
// Make sure the file isn't already loaded into the workspace
|
|
||||||
ScriptFile scriptFile = null;
|
|
||||||
if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile))
|
|
||||||
{
|
|
||||||
scriptFile =
|
|
||||||
new ScriptFile(
|
|
||||||
resolvedFilePath,
|
|
||||||
filePath,
|
|
||||||
initialBuffer,
|
|
||||||
this.SqlToolsVersion);
|
|
||||||
|
|
||||||
this.workspaceFiles.Add(keyName, scriptFile);
|
|
||||||
|
|
||||||
Logger.Write(LogLevel.Verbose, "Opened file as in-memory buffer: " + resolvedFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return scriptFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an array of all opened ScriptFiles in the workspace.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An array of all opened ScriptFiles in the workspace.</returns>
|
|
||||||
public ScriptFile[] GetOpenedFiles()
|
|
||||||
{
|
|
||||||
return workspaceFiles.Values.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Closes a currently open script file with the given file path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFile">The file path at which the script resides.</param>
|
|
||||||
public void CloseFile(ScriptFile scriptFile)
|
|
||||||
{
|
|
||||||
Validate.IsNotNull("scriptFile", scriptFile);
|
|
||||||
|
|
||||||
this.workspaceFiles.Remove(scriptFile.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetBaseFilePath(string filePath)
|
|
||||||
{
|
|
||||||
if (IsPathInMemory(filePath))
|
|
||||||
{
|
|
||||||
// If the file is in memory, use the workspace path
|
|
||||||
return this.WorkspacePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Path.IsPathRooted(filePath))
|
|
||||||
{
|
|
||||||
// TODO: Assert instead?
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
string.Format(
|
|
||||||
"Must provide a full path for originalScriptPath: {0}",
|
|
||||||
filePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the directory of the file path
|
|
||||||
return Path.GetDirectoryName(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ResolveRelativeScriptPath(string baseFilePath, string relativePath)
|
|
||||||
{
|
|
||||||
if (Path.IsPathRooted(relativePath))
|
|
||||||
{
|
|
||||||
return relativePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the directory of the original script file, combine it
|
|
||||||
// with the given path and then resolve the absolute file path.
|
|
||||||
string combinedPath =
|
|
||||||
Path.GetFullPath(
|
|
||||||
Path.Combine(
|
|
||||||
baseFilePath,
|
|
||||||
relativePath));
|
|
||||||
|
|
||||||
return combinedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1.0.0-*",
|
|
||||||
"buildOptions": {
|
|
||||||
"debugType": "portable",
|
|
||||||
"emitEntryPoint": true
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"Newtonsoft.Json": "9.0.1",
|
|
||||||
},
|
|
||||||
"frameworks": {
|
|
||||||
"netcoreapp1.0": {
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.NETCore.App": {
|
|
||||||
"type": "platform",
|
|
||||||
"version": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"imports": "dnxcore50"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user