Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions application/di/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ var (
scanNotifier scanner2.ScanNotifier
codeActionService *codeaction.CodeActionsService
fileWatcher *watcher.FileWatcher
remediationNotifier remediation.FileChangeNotifier
initMutex = &sync.Mutex{}
notifier domainNotify.Notifier
codeInstrumentor codeClientObservability.Instrumentor
Expand Down Expand Up @@ -108,13 +109,14 @@ type Dependencies struct {
// process-lifecycle dependencies used during startup, not per-request.
// Access them via di.Installer() / di.Initializer() until those global
// accessors are retired.
Scanner scanner2.Scanner
HoverService hover.Service
ScanNotifier scanner2.ScanNotifier
ScanPersister persistence.ScanSnapshotPersister
FileWatcher *watcher.FileWatcher
ErrorReporter er.ErrorReporter
CodeActionService *codeaction.CodeActionsService
Scanner scanner2.Scanner
HoverService hover.Service
ScanNotifier scanner2.ScanNotifier
ScanPersister persistence.ScanSnapshotPersister
FileWatcher *watcher.FileWatcher
ErrorReporter er.ErrorReporter
CodeActionService *codeaction.CodeActionsService
RemediationNotifier remediation.FileChangeNotifier
}

func currentDependencies() Dependencies {
Expand All @@ -133,13 +135,14 @@ func currentDependencies() Dependencies {
InlineValueProvider: inlineValueProvider,
TreeEmitter: treeEmitterInstance,
// Handler-accessed dependencies:
Scanner: scanner,
HoverService: hoverService,
ScanNotifier: scanNotifier,
ScanPersister: scanPersister,
FileWatcher: fileWatcher,
ErrorReporter: errorReporter,
CodeActionService: codeActionService,
Scanner: scanner,
HoverService: hoverService,
ScanNotifier: scanNotifier,
ScanPersister: scanPersister,
FileWatcher: fileWatcher,
ErrorReporter: errorReporter,
CodeActionService: codeActionService,
RemediationNotifier: remediationNotifier,
}
}

Expand Down Expand Up @@ -230,12 +233,13 @@ func initApplication(conf configuration.Configuration, engine workflow.Engine, l
fileWatcher = watcher.NewFileWatcher()

var remediationProvider remediation.RemediationProvider
remediationNotifier = nil
if conf.GetBool(remediationAgentEnabledKey) {
lp := engine.GetLogger().With().Str("provider", "remy").Logger()
remediationProvider = remediation.NewRemyProvider(remediation.RemyOptions{
CliPath: config.GetCliPath(conf),
Logger: &lp,
}, nil)
p := remediation.NewRemyProvider(engine, nil)
remediationProvider = p
if n, ok := p.(remediation.FileChangeNotifier); ok {
remediationNotifier = n
}
}

codeActionService = codeaction.NewService(engine, w, fileWatcher, notifier, featureFlagService, configResolver, remediationProvider)
Expand Down
19 changes: 15 additions & 4 deletions application/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,14 @@ func initHandlers(srv *jrpc2.Server, handlers handler.Map, conf configuration.Co
}
handlers["initialize"] = enrich(initializeHandler(conf, engine, srv))
handlers["initialized"] = enrich(initializedHandler(conf, engine, srv))
handlers["textDocument/didChange"] = enrich(textDocumentDidChangeHandler(conf))
var onFileChange func(types.FilePath)
if deps.RemediationNotifier != nil {
onFileChange = deps.RemediationNotifier.InvalidateFile
}
handlers["textDocument/didChange"] = enrich(textDocumentDidChangeHandler(conf, onFileChange))
handlers["textDocument/didClose"] = enrich(noOpHandler())
handlers[textDocumentDidOpenOperation] = enrich(textDocumentDidOpenHandler(conf))
handlers[textDocumentDidSaveOperation] = enrich(textDocumentDidSaveHandler(conf))
handlers[textDocumentDidSaveOperation] = enrich(textDocumentDidSaveHandler(conf, onFileChange))
handlers["textDocument/hover"] = enrich(textDocumentHover())
handlers["textDocument/codeAction"] = enrich(textDocumentCodeActionHandler(logger, deps.CodeActionService))
handlers["textDocument/codeLens"] = enrich(codeLensHandler())
Expand Down Expand Up @@ -501,7 +505,7 @@ func mustConfigResolverFromContext(ctx context.Context) types.ConfigResolverInte
return cr
}

func textDocumentDidChangeHandler(conf configuration.Configuration) jrpc2.Handler {
func textDocumentDidChangeHandler(conf configuration.Configuration, onFileChange func(types.FilePath)) jrpc2.Handler {
return handler.New(func(ctx context.Context, params sglsp.DidChangeTextDocumentParams) (any, error) {
logger := ctx2.LoggerFromContext(ctx).With().Str("method", "TextDocumentDidChangeHandler").Logger()
pathFromUri := uri.PathFromUri(params.TextDocument.URI)
Expand All @@ -519,6 +523,9 @@ func textDocumentDidChangeHandler(conf configuration.Configuration) jrpc2.Handle
}

mustFileWatcherFromContext(ctx).SetFileAsChanged(params.TextDocument.URI)
if onFileChange != nil {
onFileChange(pathFromUri)
}

return nil, nil
})
Expand Down Expand Up @@ -1137,7 +1144,7 @@ func textDocumentDidOpenHandler(conf configuration.Configuration) jrpc2.Handler
})
}

func textDocumentDidSaveHandler(conf configuration.Configuration) jrpc2.Handler {
func textDocumentDidSaveHandler(conf configuration.Configuration, onFileChange func(types.FilePath)) jrpc2.Handler {
return handler.New(func(ctx context.Context, params sglsp.DidSaveTextDocumentParams) (any, error) {
bgCtx := context.Background()
logger := ctx2.LoggerFromContext(ctx).With().Str("method", "TextDocumentDidSaveHandler").Logger()
Expand All @@ -1157,6 +1164,10 @@ func textDocumentDidSaveHandler(conf configuration.Configuration) jrpc2.Handler
return nil, nil
}

if onFileChange != nil {
onFileChange(filePath)
}

if folder.IsAutoScanEnabled() && uri.IsDotSnykFile(params.TextDocument.URI) {
go folder.ScanFolder(bgCtx)
return nil, nil
Expand Down
25 changes: 25 additions & 0 deletions domain/snyk/remediation/export_for_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* © 2026 Snyk Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package remediation

import "github.com/snyk/snyk-ls/internal/types"

// ExportedWorkspaceEditFromContent exposes workspaceEditFromContent for
// black-box tests in the remediation_test package.
func ExportedWorkspaceEditFromContent(absPath string, originalContent []byte, diff string) (*types.WorkspaceEdit, error) {
return workspaceEditFromContent(absPath, originalContent, diff)
}
7 changes: 7 additions & 0 deletions domain/snyk/remediation/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,10 @@ type RemediationRequest struct {
type RemediationProvider interface {
Remediate(ctx context.Context, req RemediationRequest) (*types.WorkspaceEdit, error)
}

// FileChangeNotifier is implemented by providers that cache per-file results.
// Call InvalidateFile whenever a file is modified so that stale cached diffs
// are evicted before the user resolves another code action against that file.
type FileChangeNotifier interface {
InvalidateFile(path types.FilePath)
}
Loading
Loading