@@ -4,7 +4,7 @@ import Emitter from '@/utils/emitter'
44import { insertSpaces , tabSize } from '@/utils/spx/highlighter'
55import type { I18n } from '@/utils/i18n'
66import { packageSpx } from '@/utils/spx'
7- import type { Runtime } from '@/models/runtime'
7+ import { RuntimeOutputKind , type Runtime } from '@/models/runtime'
88import type { Project } from '@/models/project'
99import { Copilot } from './copilot'
1010import { DocumentBase } from './document-base'
@@ -59,7 +59,9 @@ import {
5959 type WorkspaceDiagnostics ,
6060 type TextDocumentDiagnostics ,
6161 fromLSPDiagnostic ,
62- isTextDocumentStageCode
62+ isTextDocumentStageCode ,
63+ DiagnosticSeverity ,
64+ textDocumentIdEq
6365} from './common'
6466import { TextDocument , createTextDocument } from './text-document'
6567import { type Monaco } from './monaco'
@@ -127,17 +129,54 @@ class ResourceReferencesProvider implements IResourceReferencesProvider {
127129
128130class DiagnosticsProvider
129131 extends Emitter < {
130- didChangeDiagnostics : [ ]
132+ didChangeDiagnostics : void
131133 } >
132134 implements IDiagnosticsProvider
133135{
134136 constructor (
135137 private runtime : Runtime ,
136- private lspClient : SpxLSPClient
138+ private lspClient : SpxLSPClient ,
139+ private project : Project
137140 ) {
138141 super ( )
142+
143+ this . addDisposer (
144+ this . runtime . on ( 'didChangeOutput' , ( ) => {
145+ this . emit ( 'didChangeDiagnostics' )
146+ } )
147+ )
139148 }
140- private adaptDiagnosticRange ( { start, end } : Range , textDocument : ITextDocument ) {
149+
150+ private adaptRuntimeDiagnosticRange ( { start, end } : Range , textDocument : ITextDocument ) {
151+ // Expand the range to whole line because the range from runtime is not accurate.
152+ // TODO: it's a workaround, should be fixed in the runtime (ispx) side
153+ if ( start . line !== end . line ) return { start, end }
154+ const line = textDocument . getLineContent ( start . line )
155+ const leadingSpaces = line . match ( / ^ \s * / ) ?. [ 0 ] ?? ''
156+ const lineStartPos : Position = { line : start . line , column : leadingSpaces . length + 1 }
157+ const lineEndPos : Position = { line : start . line , column : line . length + 1 }
158+ return { start : lineStartPos , end : lineEndPos }
159+ }
160+
161+ private getRuntimeDiagnostics ( ctx : DiagnosticsContext ) {
162+ const { outputs, filesHash } = this . runtime
163+ if ( filesHash !== this . project . filesHash ) return [ ]
164+ const diagnostics : Diagnostic [ ] = [ ]
165+ for ( const output of outputs ) {
166+ if ( output . kind !== RuntimeOutputKind . Error ) continue
167+ if ( output . source == null ) continue
168+ if ( ! textDocumentIdEq ( output . source . textDocument , ctx . textDocument . id ) ) continue
169+ const range = this . adaptRuntimeDiagnosticRange ( output . source . range , ctx . textDocument )
170+ diagnostics . push ( {
171+ message : output . message ,
172+ range,
173+ severity : DiagnosticSeverity . Error
174+ } )
175+ }
176+ return diagnostics
177+ }
178+
179+ private adaptLSDiagnosticRange ( { start, end } : Range , textDocument : ITextDocument ) {
141180 // make sure the range is not empty, so that the diagnostic info can be displayed as inline decorations
142181 // TODO: it's a workaround, should be fixed in the server side
143182 if ( positionEq ( start , end ) ) {
@@ -165,8 +204,8 @@ class DiagnosticsProvider
165204 }
166205 return { start, end }
167206 }
168- async provideDiagnostics ( ctx : DiagnosticsContext ) : Promise < Diagnostic [ ] > {
169- // TODO: get diagnostics from runtime. https://github.com/goplus/builder/issues/1256
207+
208+ private async getLSDiagnostics ( ctx : DiagnosticsContext ) {
170209 const diagnostics : Diagnostic [ ] = [ ]
171210 const report = await this . lspClient . textDocumentDiagnostic ( {
172211 textDocument : ctx . textDocument . id
@@ -175,11 +214,15 @@ class DiagnosticsProvider
175214 throw new Error ( `Report kind ${ report . kind } not supported` )
176215 for ( const item of report . items ) {
177216 const diagnostic = fromLSPDiagnostic ( item )
178- const range = this . adaptDiagnosticRange ( diagnostic . range , ctx . textDocument )
217+ const range = this . adaptLSDiagnosticRange ( diagnostic . range , ctx . textDocument )
179218 diagnostics . push ( { ...diagnostic , range } )
180219 }
181220 return diagnostics
182221 }
222+
223+ async provideDiagnostics ( ctx : DiagnosticsContext ) : Promise < Diagnostic [ ] > {
224+ return [ ...this . getRuntimeDiagnostics ( ctx ) , ...( await this . getLSDiagnostics ( ctx ) ) ]
225+ }
183226}
184227
185228class HoverProvider implements IHoverProvider {
@@ -466,7 +509,7 @@ export class CodeEditor extends Disposable {
466509 this . completionProvider = new CompletionProvider ( this . lspClient , this . documentBase )
467510 this . contextMenuProvider = new ContextMenuProvider ( this . lspClient , this . documentBase )
468511 this . resourceReferencesProvider = new ResourceReferencesProvider ( this . lspClient )
469- this . diagnosticsProvider = new DiagnosticsProvider ( this . runtime , this . lspClient )
512+ this . diagnosticsProvider = new DiagnosticsProvider ( this . runtime , this . lspClient , this . project )
470513 this . hoverProvider = new HoverProvider ( this . lspClient , this . documentBase )
471514 }
472515
0 commit comments