Skip to content

Commit 42cf4cc

Browse files
committed
Merge pull request #8 from rojepp/multiple_file_check
POC: Implement multiple unsaved file checking
2 parents c8947cb + 65676c9 commit 42cf4cc

File tree

10 files changed

+236
-19
lines changed

10 files changed

+236
-19
lines changed

FSharp.AutoComplete/FSharp.AutoComplete.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
<ItemGroup>
6060
<Compile Include="AssemblyInfo.fs" />
6161
<Compile Include="Debug.fs" />
62+
<Compile Include="FileSystem.fs" />
6263
<Compile Include="Options.fs" />
6364
<Compile Include="TipFormatter.fs" />
6465
<Compile Include="Program.fs" />

FSharp.AutoComplete/FileSystem.fs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
namespace FSharp.InteractiveAutocomplete
2+
3+
open Microsoft.FSharp.Compiler.AbstractIL.Internal.Library
4+
open Microsoft.FSharp.Compiler
5+
open Microsoft.FSharp.Compiler.SourceCodeServices
6+
open System
7+
8+
type VolatileFile =
9+
{
10+
Touched: DateTime
11+
Lines: string []
12+
}
13+
14+
open System.IO
15+
16+
type FileSystem (actualFs: IFileSystem, getFiles: unit -> Map<string, VolatileFile>) =
17+
let getFile (filename: string) =
18+
let files = getFiles ()
19+
Map.tryFind filename files
20+
21+
let getContent (filename: string) =
22+
match getFile filename with
23+
| Some d ->
24+
let bytes = System.Text.Encoding.UTF8.GetBytes (String.Join ("\n", d.Lines))
25+
Some bytes
26+
| _ -> None
27+
28+
let getOrElse f o =
29+
match o with
30+
| Some v -> v
31+
| _ -> f()
32+
33+
interface IFileSystem with
34+
member x.FileStreamReadShim fileName =
35+
getContent fileName
36+
|> Option.map (fun bytes -> new MemoryStream (bytes) :> Stream)
37+
|> getOrElse (fun () -> actualFs.FileStreamReadShim fileName)
38+
39+
member x.ReadAllBytesShim fileName =
40+
getContent fileName
41+
|> getOrElse (fun () -> actualFs.ReadAllBytesShim fileName)
42+
43+
member x.GetLastWriteTimeShim fileName =
44+
match getFile fileName with
45+
| Some f -> f.Touched
46+
| _ -> actualFs.GetLastWriteTimeShim fileName
47+
48+
member x.GetTempPathShim() = actualFs.GetTempPathShim()
49+
member x.FileStreamCreateShim fileName = actualFs.FileStreamCreateShim fileName
50+
member x.FileStreamWriteExistingShim fileName = actualFs.FileStreamWriteExistingShim fileName
51+
member x.GetFullPathShim fileName = actualFs.GetFullPathShim fileName
52+
member x.IsInvalidPathShim fileName = actualFs.IsInvalidPathShim fileName
53+
member x.IsPathRootedShim fileName = actualFs.IsPathRootedShim fileName
54+
member x.SafeExists fileName = actualFs.SafeExists fileName
55+
member x.FileDelete fileName = actualFs.FileDelete fileName
56+
member x.AssemblyLoadFrom fileName = actualFs.AssemblyLoadFrom fileName
57+
member x.AssemblyLoad(assemblyName) = actualFs.AssemblyLoad assemblyName

FSharp.AutoComplete/Program.fs

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ type Location =
4848
Column: int
4949
}
5050

51-
type CompletionResponse =
51+
type CompletionResponse =
5252
{
5353
Name: string
5454
Glyph: string
@@ -68,12 +68,12 @@ type FSharpErrorSeverityConverter() =
6868
inherit JsonConverter()
6969

7070
override x.CanConvert(t:System.Type) = t = typeof<FSharpErrorSeverity>
71-
71+
7272
override x.WriteJson(writer, value, serializer) =
7373
match value :?> FSharpErrorSeverity with
7474
| FSharpErrorSeverity.Error -> serializer.Serialize(writer, "Error")
7575
| FSharpErrorSeverity.Warning -> serializer.Serialize(writer, "Warning")
76-
76+
7777
override x.ReadJson(_reader, _t, _, _serializer) =
7878
raise (System.NotSupportedException())
7979

@@ -135,7 +135,7 @@ module internal CommandInput =
135135
- prints the best guess for the location of fsc and fsi
136136
(or fsharpc and fsharpi on unix)
137137
"
138-
138+
139139
let outputText = @"
140140
Output format
141141
=============
@@ -323,7 +323,7 @@ module internal CompletionUtils =
323323
/// Represents current state
324324
type internal State =
325325
{
326-
Files : Map<string,string[]> //filename -> lines
326+
Files : Map<string,VolatileFile> //filename -> lines * touch date
327327
Projects : Map<string, FSharpProjectFileInfo>
328328
OutputMode : OutputMode
329329
HelpText : Map<String, FSharpToolTipText>
@@ -343,7 +343,7 @@ module internal Main =
343343
}
344344
loop ()
345345
)
346-
346+
347347
member x.WriteLine(s) = agent.Post (Choice1Of2 s)
348348

349349
member x.Quit() = agent.PostAndReply(fun ch -> Choice2Of2 ch)
@@ -367,8 +367,13 @@ module internal Main =
367367
// Main agent that handles IntelliSense requests
368368
let agent = new FSharp.CompilerBinding.LanguageService(fun _ -> ())
369369

370-
let rec main (state:State) : int =
370+
let mutable currentFiles = Map.empty
371+
let originalFs = Microsoft.FSharp.Compiler.AbstractIL.Internal.Library.Shim.FileSystem
372+
let fs = new FileSystem(originalFs, fun () -> currentFiles)
373+
Microsoft.FSharp.Compiler.AbstractIL.Internal.Library.Shim.FileSystem <- fs
371374

375+
let rec main (state:State) : int =
376+
currentFiles <- state.Files
372377
let printMsg = printMsg state
373378

374379
let parsed file =
@@ -379,14 +384,14 @@ module internal Main =
379384
/// Is the specified position consistent with internal state of file?
380385
// Note that both emacs and FSC use 1-based line indexing
381386
let posok file line col =
382-
let lines = state.Files.[file]
387+
let lines = state.Files.[file].Lines
383388
let ok = line <= lines.Length && line >= 1 &&
384389
col <= lines.[line - 1].Length && col >= 0
385390
if not ok then printMsg "ERROR" "Position is out of range"
386391
ok
387392

388393
let getoptions file state =
389-
let text = String.concat "\n" state.Files.[file]
394+
let text = String.concat "\n" state.Files.[file].Lines
390395
let project = Map.tryFind file state.Projects
391396
let projFile, args =
392397
match project with
@@ -399,14 +404,16 @@ module internal Main =
399404
// (Map.fold (fun ks k _ -> k::ks) [] state.Files)
400405
// state.OutputMode
401406
match parseCommand(Console.ReadLine()) with
402-
407+
403408
| OutputMode m -> main { state with OutputMode = m }
404409

405410
| Parse(file,kind) ->
406411
// Trigger parse request for a particular file
407412
let lines = readInput [] |> Array.ofList
408413
let file = Path.GetFullPath file
409-
let state' = { state with Files = Map.add file lines state.Files }
414+
let state' = { state with Files = state.Files |> Map.add file
415+
{ Lines = lines
416+
Touched = DateTime.Now } }
410417
let text, projFile, args = getoptions file state'
411418

412419
let task =
@@ -474,7 +481,7 @@ module internal Main =
474481
if parsed file then
475482
let text, projFile, args = getoptions file state
476483
let parseResult = agent.ParseFileInProject(projFile, file, text, args) |> Async.RunSynchronously
477-
let decls = parseResult.GetNavigationItems().Declarations
484+
let decls = parseResult.GetNavigationItems().Declarations
478485
match state.OutputMode with
479486
| Text ->
480487
let declstrings =
@@ -495,7 +502,7 @@ module internal Main =
495502
match Map.tryFind sym state.HelpText with
496503
| None -> ()
497504
| Some d ->
498-
505+
499506
let tip = TipFormatter.formatTip d
500507
let helptext = Map.add sym tip Map.empty
501508
prAsJson { Kind = "helptext"; Data = helptext }
@@ -506,7 +513,7 @@ module internal Main =
506513
let file = Path.GetFullPath file
507514
if parsed file && posok file line col then
508515
let text, projFile, args = getoptions file state
509-
let lineStr = state.Files.[file].[line - 1]
516+
let lineStr = state.Files.[file].Lines.[line - 1]
510517
// TODO: Deny recent typecheck results under some circumstances (after bracketed expr..)
511518
let timeout = match timeout with Some x -> x | _ -> 20000
512519
let tyResOpt = agent.GetTypedParseResultWithTimeout(projFile, file, text, [||], args, AllowStaleResults.MatchingFileName, timeout)
@@ -537,15 +544,15 @@ module internal Main =
537544
prAsJson { Kind = "helptext"; Data = helptext }
538545

539546
prAsJson { Kind = "completion"
540-
Data = [ for d in decls.Items do
547+
Data = [ for d in decls.Items do
541548
let (glyph, glyphChar) = CompletionUtils.getIcon d.Glyph
542549
yield { Name = d.Name; Glyph = glyph; GlyphChar = glyphChar } ] }
543550

544551
let helptext =
545552
Seq.fold (fun m (d: FSharpDeclarationListItem) -> Map.add d.Name d.DescriptionText m) Map.empty decls.Items
546553

547554
main { state with HelpText = helptext }
548-
| None ->
555+
| None ->
549556
printMsg "ERROR" "Could not get type information"
550557
main state
551558

@@ -570,20 +577,20 @@ module internal Main =
570577
| Json -> prAsJson { Kind = "tooltip"; Data = TipFormatter.formatTip tip }
571578

572579
main state
573-
580+
574581
| FindDeclaration ->
575582
let declarations = tyRes.GetDeclarationLocation(line,col,lineStr)
576583
|> Async.RunSynchronously
577584
match declarations with
578585
| FSharpFindDeclResult.DeclNotFound _ -> printMsg "ERROR" "Could not find declaration"
579586
| FSharpFindDeclResult.DeclFound range ->
580-
587+
581588
match state.OutputMode with
582589
| Text -> printAgent.WriteLine(sprintf "DATA: finddecl\n%s:%d:%d\n<<EOF>>" range.FileName range.StartLine range.StartColumn)
583590
| Json ->
584591
let data = { Line = range.StartLine; Column = range.StartColumn; File = range.FileName }
585592
prAsJson { Kind = "finddecl"; Data = data }
586-
593+
587594
main state
588595

589596
else
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module FileTwo
2+
3+
let addTwo x y = x + y
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
let testval = FileTwo.addTwo 1 2
2+
3+
[<EntryPoint>]
4+
let main args =
5+
printfn "Hello %d" testval
6+
0
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
5+
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
6+
<ProductVersion>8.0.30703</ProductVersion>
7+
<SchemaVersion>2.0</SchemaVersion>
8+
<ProjectGuid>{116CC2F9-F987-4B3D-915A-34CAC04A73DA}</ProjectGuid>
9+
<OutputType>Exe</OutputType>
10+
<RootNamespace>Test1</RootNamespace>
11+
<AssemblyName>Test1</AssemblyName>
12+
<Name>Test1</Name>
13+
<UsePartialTypes>False</UsePartialTypes>
14+
<BuildOrder>
15+
<BuildOrder>
16+
<String>Program.fs</String>
17+
</BuildOrder>
18+
</BuildOrder>
19+
<TargetFSharpCoreVersion>4.3.0.0</TargetFSharpCoreVersion>
20+
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
21+
</PropertyGroup>
22+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
23+
<DebugSymbols>True</DebugSymbols>
24+
<Optimize>False</Optimize>
25+
<Tailcalls>False</Tailcalls>
26+
<OutputPath>bin\Debug\</OutputPath>
27+
<DefineConstants>DEBUG;TRACE</DefineConstants>
28+
<WarningLevel>3</WarningLevel>
29+
<PlatformTarget>x86</PlatformTarget>
30+
<DocumentationFile>bin\Debug\Test1.XML</DocumentationFile>
31+
</PropertyGroup>
32+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
33+
<DebugType>pdbonly</DebugType>
34+
<Optimize>True</Optimize>
35+
<Tailcalls>True</Tailcalls>
36+
<OutputPath>bin\Release\</OutputPath>
37+
<DefineConstants>TRACE</DefineConstants>
38+
<WarningLevel>3</WarningLevel>
39+
<PlatformTarget>x86</PlatformTarget>
40+
<DocumentationFile>bin\Release\Test1.XML</DocumentationFile>
41+
<DebugSymbols>False</DebugSymbols>
42+
</PropertyGroup>
43+
<ItemGroup>
44+
<Reference Include="mscorlib" />
45+
<Reference Include="System" />
46+
<Reference Include="System.Core" />
47+
<Reference Include="FSharp.Core">
48+
<Private>True</Private>
49+
</Reference>
50+
</ItemGroup>
51+
<ItemGroup>
52+
<Compile Include="FileTwo.fs" />
53+
<Compile Include="Program.fs" />
54+
</ItemGroup>
55+
<Choose>
56+
<When Condition="'$(VisualStudioVersion)' == '11.0'">
57+
<PropertyGroup>
58+
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
59+
</PropertyGroup>
60+
</When>
61+
<Otherwise>
62+
<PropertyGroup>
63+
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
64+
</PropertyGroup>
65+
</Otherwise>
66+
</Choose>
67+
<Import Project="$(FSharpTargetsPath)" Condition="Exists('$(FSharpTargetsPath)')" />
68+
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
69+
Other similar extension points exist, see Microsoft.Common.targets.
70+
<Target Name="BeforeBuild">
71+
</Target>
72+
<Target Name="AfterBuild">
73+
</Target>
74+
-->
75+
</Project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 11.00
3+
# Visual Studio 2010
4+
Project("{f2a71f9b-5d33-465a-a702-920d77279786}") = "multunsaved", "multunsaved.fsproj", "{116CC2F9-F987-4B3D-915A-34CAC04A73DA}"
5+
EndProject
6+
Global
7+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8+
Debug|x86 = Debug|x86
9+
Release|x86 = Release|x86
10+
EndGlobalSection
11+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
12+
{116CC2F9-F987-4B3D-915A-34CAC04A73DA}.Debug|x86.ActiveCfg = Debug|x86
13+
{116CC2F9-F987-4B3D-915A-34CAC04A73DA}.Debug|x86.Build.0 = Debug|x86
14+
{116CC2F9-F987-4B3D-915A-34CAC04A73DA}.Release|x86.ActiveCfg = Release|x86
15+
{116CC2F9-F987-4B3D-915A-34CAC04A73DA}.Release|x86.Build.0 = Release|x86
16+
EndGlobalSection
17+
EndGlobal
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#load "../TestHelpers.fsx"
2+
open TestHelpers
3+
open System.IO
4+
open System
5+
6+
(*
7+
* This test is a simple sanity check of a basic run of the program.
8+
* A few completions, files and script.
9+
*)
10+
11+
Environment.CurrentDirectory <- __SOURCE_DIRECTORY__
12+
File.Delete "output.txt"
13+
14+
let p = new FSharpAutoCompleteWrapper()
15+
16+
p.project "multunsaved.fsproj"
17+
p.parse "FileTwo.fs"
18+
p.parse "Program.fs"
19+
p.parseContent "FileTwo.fs" """
20+
module FileTwo
21+
22+
let addTwo2 x y = x + y
23+
"""
24+
p.parse "Program.fs"
25+
p.send "quit\n"
26+
let output = p.finalOutput ()
27+
File.WriteAllText("output.txt", output)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
DATA: project
2+
<absolute path removed>/test/integration/MultipleUnsavedFiles/FileTwo.fs
3+
<absolute path removed>/test/integration/MultipleUnsavedFiles/Program.fs
4+
<<EOF>>
5+
INFO: Synchronous parsing started
6+
<<EOF>>
7+
DATA: errors
8+
<<EOF>>
9+
INFO: Synchronous parsing started
10+
<<EOF>>
11+
DATA: errors
12+
<<EOF>>
13+
INFO: Synchronous parsing started
14+
<<EOF>>
15+
DATA: errors
16+
<<EOF>>
17+
INFO: Synchronous parsing started
18+
<<EOF>>
19+
DATA: errors
20+
[1:22-1:28] ERROR The value, constructor, namespace or type 'addTwo' is not defined
21+
<<EOF>>

0 commit comments

Comments
 (0)