Skip to content

Commit

Permalink
Merge pull request #74 from ionide/suave
Browse files Browse the repository at this point in the history
[WIP] Add Suave hosting for FSAC
  • Loading branch information
rneatherway committed Oct 14, 2015
2 parents 543cfa7 + 5aa3cfe commit fde76f3
Show file tree
Hide file tree
Showing 33 changed files with 1,098 additions and 490 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ TestResults.xml
.fake/

fsautocomplete.zip
fsautocomplete.suave.zip
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
namespace FsAutoComplete
namespace FsAutoComplete

open System
open Newtonsoft.Json
open Newtonsoft.Json.Converters

open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.SourceCodeServices

Expand Down Expand Up @@ -168,78 +167,30 @@ module CommandResponse =
Kind: string
}

type private FSharpErrorSeverityConverter() =
inherit JsonConverter()

override x.CanConvert(t:System.Type) = t = typeof<FSharpErrorSeverity>

override x.WriteJson(writer, value, serializer) =
match value :?> FSharpErrorSeverity with
| FSharpErrorSeverity.Error -> serializer.Serialize(writer, "Error")
| FSharpErrorSeverity.Warning -> serializer.Serialize(writer, "Warning")

override x.ReadJson(_reader, _t, _, _serializer) =
raise (System.NotSupportedException())

override x.CanRead = false
override x.CanWrite = true

type private RangeConverter() =
inherit JsonConverter()

override x.CanConvert(t:System.Type) = t = typeof<Range.range>

override x.WriteJson(writer, value, _serializer) =
let range = value :?> Range.range
writer.WriteStartObject()
writer.WritePropertyName("StartColumn")
writer.WriteValue(range.StartColumn + 1)
writer.WritePropertyName("StartLine")
writer.WriteValue(range.StartLine)
writer.WritePropertyName("EndColumn")
writer.WriteValue(range.EndColumn + 1)
writer.WritePropertyName("EndLine")
writer.WriteValue(range.EndLine)
writer.WriteEndObject()

override x.ReadJson(_reader, _t, _, _serializer) =
raise (System.NotSupportedException())

override x.CanRead = false
override x.CanWrite = true

let private jsonConverters =
[|
new FSharpErrorSeverityConverter() :> JsonConverter;
new RangeConverter() :> JsonConverter
|]

let private writeJson(o: obj) = Console.WriteLine (JsonConvert.SerializeObject(o, jsonConverters))

let info(s: string) = writeJson { Kind = "info"; Data = s }
let error(s: string) = writeJson { Kind = "error"; Data = s }
let info (serialize : obj -> string) (s: string) = serialize { Kind = "info"; Data = s }
let error (serialize : obj -> string) (s: string) = serialize { Kind = "error"; Data = s }

let helpText(name: string, tip: FSharpToolTipText) =
let helpText (serialize : obj -> string) (name: string, tip: FSharpToolTipText) =
let data = TipFormatter.formatTip tip |> List.map(List.map(fun (n,m) -> {Signature = n; Comment = m} ))
writeJson { Kind = "helptext"; Data = { Name = name; Overloads = data } }
serialize { Kind = "helptext"; Data = { Name = name; Overloads = data } }

let project(projectFileName, projectFiles, outFileOpt, references, frameworkOpt) =
let project (serialize : obj -> string) (projectFileName, projectFiles, outFileOpt, references, frameworkOpt) =
let projectData =
{ Project = projectFileName
Files = projectFiles
Output = match outFileOpt with Some x -> x | None -> "null"
References = List.sortBy IO.Path.GetFileName references
Framework = match frameworkOpt with Some x -> x | None -> "null" }
writeJson { Kind = "project"; Data = projectData }
serialize { Kind = "project"; Data = projectData }

let completion(decls: FSharpDeclarationListItem[]) =
writeJson
{ Kind = "completion"
Data = [ for d in decls do
let (glyph, glyphChar) = CompletionUtils.getIcon d.Glyph
yield { Name = d.Name; Glyph = glyph; GlyphChar = glyphChar } ] }
let completion (serialize : obj -> string) (decls: FSharpDeclarationListItem[]) =
serialize { Kind = "completion"
Data = [ for d in decls do
let (glyph, glyphChar) = CompletionUtils.getIcon d.Glyph
yield { Name = d.Name; Glyph = glyph; GlyphChar = glyphChar } ] }

let symbolUse(symbol: FSharpSymbolUse, uses: FSharpSymbolUse[]) =
let symbolUse (serialize : obj -> string) (symbol: FSharpSymbolUse, uses: FSharpSymbolUse[]) =
let su =
{ Name = symbol.Symbol.DisplayName
Uses =
Expand All @@ -255,56 +206,54 @@ module CommandResponse =
IsFromDispatchSlotImplementation = su.IsFromDispatchSlotImplementation
IsFromPattern = su.IsFromPattern
IsFromType = su.IsFromType } ] }
writeJson { Kind = "symboluse"; Data = su }

let methods(meth: FSharpMethodGroup, commas: int) =
writeJson
{ Kind = "method"
Data = { Name = meth.MethodName
CurrentParameter = commas
Overloads =
[ for o in meth.Methods do
let tip = TipFormatter.formatTip o.Description |> List.map(List.map(fun (n,m) -> {Signature = n; Comment = m} ))
yield {
Tip = tip
TypeText = o.TypeText
Parameters =
[ for p in o.Parameters do
yield {
Name = p.ParameterName
CanonicalTypeTextForSorting = p.CanonicalTypeTextForSorting
Display = p.Display
Description = p.Description
}
]
IsStaticArguments = o.IsStaticArguments
}
] }
}

let errors(errors: Microsoft.FSharp.Compiler.FSharpErrorInfo[]) =
writeJson { Kind = "errors"
Data = Seq.map FSharpErrorInfo.OfFSharpError errors }

let colorizations(colorizations: (Range.range * FSharpTokenColorKind)[]) =
serialize { Kind = "symboluse"; Data = su }

let methods (serialize : obj -> string) (meth: FSharpMethodGroup, commas: int) =
serialize { Kind = "method"
Data = { Name = meth.MethodName
CurrentParameter = commas
Overloads =
[ for o in meth.Methods do
let tip = TipFormatter.formatTip o.Description |> List.map(List.map(fun (n,m) -> {Signature = n; Comment = m} ))
yield {
Tip = tip
TypeText = o.TypeText
Parameters =
[ for p in o.Parameters do
yield {
Name = p.ParameterName
CanonicalTypeTextForSorting = p.CanonicalTypeTextForSorting
Display = p.Display
Description = p.Description
}
]
IsStaticArguments = o.IsStaticArguments
}
] }
}

let errors (serialize : obj -> string) (errors: Microsoft.FSharp.Compiler.FSharpErrorInfo[]) =
serialize { Kind = "errors"; Data = Seq.map FSharpErrorInfo.OfFSharpError errors }

let colorizations (serialize : obj -> string) (colorizations: (Range.range * FSharpTokenColorKind)[]) =
let data = [ for r, k in colorizations do
yield { Range = r; Kind = Enum.GetName(typeof<FSharpTokenColorKind>, k) } ]
writeJson { Kind = "colorizations"; Data = data }
serialize { Kind = "colorizations"; Data = data }

let findDeclaration(range: Range.range) =
let findDeclaration (serialize : obj -> string) (range: Range.range) =
let data = { Line = range.StartLine; Column = range.StartColumn + 1; File = range.FileName }
writeJson { Kind = "finddecl"; Data = data }
serialize { Kind = "finddecl"; Data = data }

let declarations(decls) =
writeJson { Kind = "declarations"; Data = decls }
let declarations (serialize : obj -> string) (decls) =
serialize { Kind = "declarations"; Data = decls }

let toolTip(tip) =
let toolTip (serialize : obj -> string) (tip) =
let data = TipFormatter.formatTip tip |> List.map(List.map(fun (n,m) -> {Signature = n; Comment = m} ))
writeJson { Kind = "tooltip"; Data = data }
serialize { Kind = "tooltip"; Data = data }

let compilerLocation fsc fsi msbuild =
let compilerLocation (serialize : obj -> string) fsc fsi msbuild =
let data = { Fsi = fsi; Fsc = fsc; MSBuild = msbuild }
writeJson { Kind = "compilerlocation"; Data = data }
serialize { Kind = "compilerlocation"; Data = data }

let message(kind: string, data: 'a) =
writeJson { Kind = kind; Data = data }
let message (serialize : obj -> string) (kind: string, data: 'a) =
serialize { Kind = kind; Data = data }
File renamed without changes.
File renamed without changes.
File renamed without changes.
142 changes: 142 additions & 0 deletions FsAutoComplete.Core/FsAutoComplete.Core.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
namespace FsAutoComplete

open System
open System.IO
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.SourceCodeServices

module Response = CommandResponse

module Commands =
let parse (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) file lines = async {
let colorizations = state.ColorizationOutput
let parse' fileName text options =
async {
let! _parseResults, checkResults = checker.ParseAndCheckFileInProject(fileName, 0, text, options)
return match checkResults with
| FSharpCheckFileAnswer.Aborted -> [Response.info serialize "Parse aborted"]
| FSharpCheckFileAnswer.Succeeded results ->
if colorizations then
[ Response.errors serialize (results.Errors)
Response.colorizations serialize (results.GetExtraColorizationsAlternate()) ]
else [ Response.errors serialize (results.Errors) ]
}
let file = Path.GetFullPath file
let text = String.concat "\n" lines

if Utils.isAScript file then
let checkOptions = checker.GetProjectOptionsFromScript(file, text)
let state' = state.WithFileTextAndCheckerOptions(file, lines, checkOptions)
let! res = (parse' file text checkOptions)
return res , state'
else
let state', checkOptions = state.WithFileTextGetCheckerOptions(file, lines)
let! res = (parse' file text checkOptions)
return res, state'
}



let project (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) file time = async {
let file = Path.GetFullPath file

// The FileSystemWatcher often triggers multiple times for
// each event, as editors often modify files in several steps.
// This 'debounces' the events, by only reloading a max of once
// per second.
return match state.ProjectLoadTimes.TryFind file with
| Some oldtime when time - oldtime < TimeSpan.FromSeconds(1.0) -> [],state
| _ ->

match checker.TryGetProjectOptions(file) with
| Result.Failure s -> [Response.error serialize s],state
| Result.Success(po, projectFiles, outFileOpt, references, frameworkOpt) ->
let res = Response.project serialize (file, projectFiles, outFileOpt, references, frameworkOpt)
let checkOptions =
projectFiles
|> List.fold (fun s f -> Map.add f po s) state.FileCheckOptions
let loadTimes = Map.add file time state.ProjectLoadTimes
let state' = { state with FileCheckOptions = checkOptions; ProjectLoadTimes = loadTimes }
[res], state'
}

let declarations (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) file = async {
let file = Path.GetFullPath file
return match state.TryGetFileCheckerOptionsWithSource(file) with
| Failure s -> [Response.error serialize (s)], state
| Success (checkOptions, source) ->
let decls = checker.GetDeclarations(file, source, checkOptions)
[Response.declarations serialize (decls)], state
}

let helptext (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) sym = async {
return match Map.tryFind sym state.HelpText with
| None -> [Response.error serialize (sprintf "No help text available for symbol '%s'" sym)], state
| Some tip -> [Response.helpText serialize (sym, tip)], state
}

let compilerLocation (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) = async {
return [Response.compilerLocation serialize Environment.fsc Environment.fsi Environment.msbuild], state
}

let colorization (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) enabled = async {
return [], { state with ColorizationOutput = enabled }
}

let error (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) msg = async {
return [Response.error serialize msg], state
}

let completion (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) (tyRes : ParseAndCheckResults ) line col lineStr timeout filter = async {
return match tyRes.TryGetCompletions line col lineStr timeout filter with
| Some (decls, residue) ->
let declName (d: FSharpDeclarationListItem) = d.Name

// Send the first helptext without being requested.
// This allows it to be displayed immediately in the editor.
let firstMatchOpt =
Array.sortBy declName decls
|> Array.tryFind (fun d -> (declName d).StartsWith residue)
let res = match firstMatchOpt with
| None -> [Response.completion serialize (decls)]
| Some d ->
[ Response.helpText serialize (d.Name, d.DescriptionText)
Response.completion serialize (decls) ]



let helptext =
Seq.fold (fun m d -> Map.add (declName d) d.DescriptionText m) Map.empty decls
res,{ state with HelpText = helptext }

| None ->
[Response.error serialize "Timed out while fetching completions"], state
}

let toolTip (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) (tyRes : ParseAndCheckResults ) line col lineStr = async {
// A failure is only info here, as this command is expected to be
// used 'on idle', and frequent errors are expected.
return match tyRes.TryGetToolTip line col lineStr with
| Result.Failure s -> [Response.info serialize (s)], state
| Result.Success tip -> [Response.toolTip serialize tip], state
}

let symbolUse (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) (tyRes : ParseAndCheckResults ) line col lineStr = async {
// A failure is only info here, as this command is expected to be
// used 'on idle', and frequent errors are expected.
return match tyRes.TryGetSymbolUse line col lineStr with
| Result.Failure s -> [Response.info serialize (s)], state
| Result.Success (sym,usages) -> [Response.symbolUse serialize (sym,usages)], state
}

let findDeclarations (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) (tyRes : ParseAndCheckResults ) line col lineStr = async {
return match tyRes.TryFindDeclaration line col lineStr with
| Result.Failure s -> [Response.error serialize (s)], state
| Result.Success range -> [Response.findDeclaration serialize range], state
}

let methods (serialize : obj -> string) (state : State) (checker : FSharpCompilerServiceChecker) (tyRes : ParseAndCheckResults ) line col lines = async {
return match tyRes.TryGetMethodOverrides lines line col with
| Result.Failure s -> [Response.error serialize (s)], state
| Result.Success (meth, commas) -> [Response.methods serialize (meth, commas)], state
}
Loading

0 comments on commit fde76f3

Please sign in to comment.