mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
Actually stage the deletes. Update .gitignore
This commit is contained in:
273
.gitignore
vendored
273
.gitignore
vendored
@@ -1,3 +1,270 @@
|
||||
bin
|
||||
obj
|
||||
project.lock.json
|
||||
syntax: glob
|
||||
|
||||
### VisualStudio ###
|
||||
|
||||
# Project.json lock file
|
||||
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