Actually stage the deletes. Update .gitignore

This commit is contained in:
Karl Burtram
2016-07-17 11:44:03 -07:00
parent 68999917b2
commit ae54004e99
70 changed files with 270 additions and 6295 deletions

273
.gitignore vendored
View File

@@ -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/

View File

@@ -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
}
]
}

View File

@@ -1,14 +0,0 @@
{
"version": "0.1.0",
"command": "dotnet",
"isShellCommand": true,
"args": [],
"tasks": [
{
"taskName": "build",
"args": [],
"isBuildCommand": true,
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -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
{
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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");
}
}

View File

@@ -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; }
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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; }
}
}

View File

@@ -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");
}
}

View File

@@ -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;}
}
}

View File

@@ -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");
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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;}
}
}

View File

@@ -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];
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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.
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
};
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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
};
}
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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)));
}
}
}

View File

@@ -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
};
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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")]

View File

@@ -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
};
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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

View File

@@ -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; }
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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; }
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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"
}
}
}