@@ -7,16 +7,17 @@ import com.intellij.openapi.editor.Document
7
7
import com .intellij .openapi .module .Module
8
8
import com .intellij .openapi .project .Project
9
9
import com .intellij .openapi .util .io .FileUtil
10
+ import com .intellij .openapi .util .registry .Registry
10
11
import com .intellij .openapi .vfs .VirtualFile
11
- import org .jetbrains .jps .incremental .scala .Client
12
12
import org .jetbrains .jps .incremental .scala .remote .{CommandIds , SourceScope }
13
+ import org .jetbrains .jps .incremental .scala .{Client , DelegateClient }
13
14
import org .jetbrains .plugins .scala .compiler .data .{CompilerData , CompilerJarsFactory , DocumentCompilationArguments , DocumentCompilationData , IncrementalityType }
14
15
import org .jetbrains .plugins .scala .compiler .{RemoteServerConnectorBase , RemoteServerRunner }
15
16
import org .jetbrains .plugins .scala .editor .DocumentExt
16
- import org .jetbrains .plugins .scala .project .{ModuleExt , ScalaLanguageLevel }
17
+ import org .jetbrains .plugins .scala .project .{ModuleExt , ScalaLanguageLevel , VirtualFileExt }
17
18
18
19
import java .io .File
19
- import java .nio .file .Path
20
+ import java .nio .file .{ Files , Path }
20
21
21
22
@ Service (Array (Service .Level .PROJECT ))
22
23
private final class DocumentCompiler (project : Project ) {
@@ -42,21 +43,64 @@ private final class DocumentCompiler(project: Project) {
42
43
virtualFile : VirtualFile ,
43
44
client : Client
44
45
): Unit = {
45
- compileDocumentContent(
46
- sourcePath = virtualFile.toNioPath,
47
- sourceContent = document.textWithConvertedSeparators(virtualFile),
48
- module = module,
49
- sourceScope = sourceScope,
50
- client = client
51
- )
46
+ val useInMemoryFile = Registry .is(" scala.compiler.highlighting.document.use.in.memory.file" )
47
+ val originalSourceFile = virtualFile.toFile
48
+ val sourceContent = document.textWithConvertedSeparators(virtualFile)
49
+
50
+ if (useInMemoryFile) {
51
+ compileInMemoryFile(
52
+ sourcePath = originalSourceFile.toPath,
53
+ sourceContent = sourceContent,
54
+ module = module,
55
+ sourceScope = sourceScope,
56
+ client = client
57
+ )
58
+ } else {
59
+ compilePhysicalFile(
60
+ originalSourceFile = originalSourceFile,
61
+ content = sourceContent,
62
+ module = module,
63
+ sourceScope = sourceScope,
64
+ client = client
65
+ )
66
+ }
52
67
}
53
68
54
- private def compileDocumentContent (sourcePath : Path ,
55
- sourceContent : String ,
56
- module : Module ,
57
- sourceScope : SourceScope ,
58
- client : Client ): Unit = {
59
- val connector = new RemoteServerConnector (sourcePath, sourceContent, module, sourceScope)
69
+ private def compilePhysicalFile (originalSourceFile : File ,
70
+ content : String ,
71
+ module : Module ,
72
+ sourceScope : SourceScope ,
73
+ client : Client ): Unit = {
74
+ val tempSourceFile = workingDirectory.resolve(" tempSourceFile" ).toFile
75
+ Files .writeString(tempSourceFile.toPath, content)
76
+ val connector =
77
+ try new PhysicalFileConnector (tempSourceFile, module, sourceScope)
78
+ catch {
79
+ case t : Throwable =>
80
+ // Remove the temporary source file if creating the connector failed.
81
+ FileUtil .delete(tempSourceFile)
82
+ throw t
83
+ }
84
+
85
+ try connector.compile(originalSourceFile, client)
86
+ finally {
87
+ if (connector.requiresCleanup) {
88
+ val files = workingDirectory.toFile.listFiles()
89
+ if (files ne null ) {
90
+ files.foreach(FileUtil .delete)
91
+ }
92
+ } else {
93
+ FileUtil .delete(tempSourceFile)
94
+ }
95
+ }
96
+ }
97
+
98
+ private def compileInMemoryFile (sourcePath : Path ,
99
+ sourceContent : String ,
100
+ module : Module ,
101
+ sourceScope : SourceScope ,
102
+ client : Client ): Unit = {
103
+ val connector = new InMemoryFileConnector (sourcePath, sourceContent, module, sourceScope)
60
104
try connector.compile(client)
61
105
finally {
62
106
if (connector.requiresCleanup) {
@@ -68,8 +112,64 @@ private final class DocumentCompiler(project: Project) {
68
112
}
69
113
}
70
114
71
- private class RemoteServerConnector (sourcePath : Path , sourceContent : String , module : Module , sourceScope : SourceScope )
72
- extends RemoteServerConnectorBase (module, None , workingDirectory.toFile) {
115
+ private final class PhysicalFileConnector (tempSourceFile : File , module : Module , sourceScope : SourceScope )
116
+ extends AbstractRemoteServerConnector (Some (Seq (tempSourceFile)), module, sourceScope) {
117
+
118
+ def compile (originalSourceFile : File , client : Client ): Unit = {
119
+ val fixedClient = new DelegateClient (client) {
120
+ override def message (msg : Client .ClientMsg ): Unit = {
121
+ /**
122
+ * NOTE: some compiler errors can be with empty `source`<br>
123
+ * Example: `bad option '-g:vars' was ignored`<br>
124
+ * We do not want to loose such message.
125
+ * We rely that they will be reported in the beginning of the file<br>
126
+ * see [[org.jetbrains.plugins.scala.compiler.highlighting.ExternalHighlightersService.toHighlightInfo ]]
127
+ * (we assume that `from` and `to` are also empty for such files)
128
+ */
129
+ val fixedSource = Some (originalSourceFile) // msg.source.map(_ => originalSourceFile)
130
+ val fixedMsg = msg.copy(source = fixedSource)
131
+ client.message(fixedMsg)
132
+ }
133
+
134
+ override def compilationEnd (sources : Set [File ]): Unit = {
135
+ val fixedSources = Set (originalSourceFile)
136
+ client.compilationEnd(fixedSources)
137
+ }
138
+ }
139
+ new RemoteServerRunner ()
140
+ .buildProcess(CommandIds .Compile , arguments.asStrings, fixedClient)
141
+ .runSync()
142
+ }
143
+ }
144
+
145
+ private final class InMemoryFileConnector (sourcePath : Path , sourceContent : String , module : Module , sourceScope : SourceScope )
146
+ extends AbstractRemoteServerConnector (None , module, sourceScope) {
147
+
148
+ def compile (client : Client ): Unit = {
149
+ val arguments = DocumentCompilationArguments (
150
+ sbtData = sbtData,
151
+ compilerData = CompilerData (
152
+ compilerJars = CompilerJarsFactory .fromFiles(compilerClasspath, module.customScalaCompilerBridgeJar).toOption,
153
+ javaHome = Some (findJdk),
154
+ incrementalType = IncrementalityType .IDEA
155
+ ),
156
+ compilationData = DocumentCompilationData (
157
+ sourcePath = sourcePath,
158
+ sourceContent = sourceContent,
159
+ output = workingDirectory,
160
+ classpath = runtimeClasspath.map(_.toPath),
161
+ scalacOptions = scalaParameters
162
+ )
163
+ )
164
+
165
+ new RemoteServerRunner ()
166
+ .buildProcess(CommandIds .CompileDocument , DocumentCompilationArguments .serialize(arguments), client)
167
+ .runSync()
168
+ }
169
+ }
170
+
171
+ private abstract class AbstractRemoteServerConnector (filesToCompile : Option [Seq [File ]], module : Module , sourceScope : SourceScope )
172
+ extends RemoteServerConnectorBase (module, filesToCompile, workingDirectory.toFile) {
73
173
74
174
var requiresCleanup : Boolean = false
75
175
@@ -112,32 +212,10 @@ private final class DocumentCompiler(project: Project) {
112
212
.map(Path .of(_).toFile)
113
213
(fromSuper ++ outputDir).distinct
114
214
}
115
-
116
- def compile (client : Client ): Unit = {
117
- val arguments = DocumentCompilationArguments (
118
- sbtData = sbtData,
119
- compilerData = CompilerData (
120
- compilerJars = CompilerJarsFactory .fromFiles(compilerClasspath, module.customScalaCompilerBridgeJar).toOption,
121
- javaHome = Some (findJdk),
122
- incrementalType = IncrementalityType .IDEA
123
- ),
124
- compilationData = DocumentCompilationData (
125
- sourcePath = sourcePath,
126
- sourceContent = sourceContent,
127
- output = workingDirectory,
128
- classpath = runtimeClasspath.map(_.toPath),
129
- scalacOptions = scalaParameters
130
- )
131
- )
132
-
133
- new RemoteServerRunner ()
134
- .buildProcess(CommandIds .CompileDocument , DocumentCompilationArguments .serialize(arguments), client)
135
- .runSync()
136
- }
137
215
}
138
216
}
139
217
140
218
private object DocumentCompiler {
141
219
def get (project : Project ): DocumentCompiler =
142
220
project.getService(classOf [DocumentCompiler ])
143
- }
221
+ }
0 commit comments