.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.0 / internal / lsp / cache / imports.go
1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package cache
6
7 import (
8         "context"
9         "fmt"
10         "reflect"
11         "strings"
12         "sync"
13         "time"
14
15         "golang.org/x/tools/internal/event"
16         "golang.org/x/tools/internal/event/keys"
17         "golang.org/x/tools/internal/gocommand"
18         "golang.org/x/tools/internal/imports"
19         "golang.org/x/tools/internal/lsp/source"
20 )
21
22 type importsState struct {
23         ctx context.Context
24
25         mu                   sync.Mutex
26         processEnv           *imports.ProcessEnv
27         cleanupProcessEnv    func()
28         cacheRefreshDuration time.Duration
29         cacheRefreshTimer    *time.Timer
30         cachedModFileHash    string
31         cachedBuildFlags     []string
32 }
33
34 func (s *importsState) runProcessEnvFunc(ctx context.Context, snapshot *snapshot, fn func(*imports.Options) error) error {
35         s.mu.Lock()
36         defer s.mu.Unlock()
37
38         // Find the hash of the active mod file, if any. Using the unsaved content
39         // is slightly wasteful, since we'll drop caches a little too often, but
40         // the mod file shouldn't be changing while people are autocompleting.
41         var modFileHash string
42         if snapshot.workspaceMode()&usesWorkspaceModule == 0 {
43                 for m := range snapshot.workspace.getActiveModFiles() { // range to access the only element
44                         modFH, err := snapshot.GetFile(ctx, m)
45                         if err != nil {
46                                 return err
47                         }
48                         modFileHash = modFH.FileIdentity().Hash
49                 }
50         } else {
51                 modFile, err := snapshot.workspace.modFile(ctx, snapshot)
52                 if err != nil {
53                         return err
54                 }
55                 modBytes, err := modFile.Format()
56                 if err != nil {
57                         return err
58                 }
59                 modFileHash = hashContents(modBytes)
60         }
61
62         // view.goEnv is immutable -- changes make a new view. Options can change.
63         // We can't compare build flags directly because we may add -modfile.
64         snapshot.view.optionsMu.Lock()
65         localPrefix := snapshot.view.options.Local
66         currentBuildFlags := snapshot.view.options.BuildFlags
67         changed := !reflect.DeepEqual(currentBuildFlags, s.cachedBuildFlags) ||
68                 snapshot.view.options.VerboseOutput != (s.processEnv.Logf != nil) ||
69                 modFileHash != s.cachedModFileHash
70         snapshot.view.optionsMu.Unlock()
71
72         // If anything relevant to imports has changed, clear caches and
73         // update the processEnv. Clearing caches blocks on any background
74         // scans.
75         if changed {
76                 // As a special case, skip cleanup the first time -- we haven't fully
77                 // initialized the environment yet and calling GetResolver will do
78                 // unnecessary work and potentially mess up the go.mod file.
79                 if s.cleanupProcessEnv != nil {
80                         if resolver, err := s.processEnv.GetResolver(); err == nil {
81                                 if modResolver, ok := resolver.(*imports.ModuleResolver); ok {
82                                         modResolver.ClearForNewMod()
83                                 }
84                         }
85                         s.cleanupProcessEnv()
86                 }
87                 s.cachedModFileHash = modFileHash
88                 s.cachedBuildFlags = currentBuildFlags
89                 var err error
90                 s.cleanupProcessEnv, err = s.populateProcessEnv(ctx, snapshot)
91                 if err != nil {
92                         return err
93                 }
94         }
95
96         // Run the user function.
97         opts := &imports.Options{
98                 // Defaults.
99                 AllErrors:   true,
100                 Comments:    true,
101                 Fragment:    true,
102                 FormatOnly:  false,
103                 TabIndent:   true,
104                 TabWidth:    8,
105                 Env:         s.processEnv,
106                 LocalPrefix: localPrefix,
107         }
108
109         if err := fn(opts); err != nil {
110                 return err
111         }
112
113         if s.cacheRefreshTimer == nil {
114                 // Don't refresh more than twice per minute.
115                 delay := 30 * time.Second
116                 // Don't spend more than a couple percent of the time refreshing.
117                 if adaptive := 50 * s.cacheRefreshDuration; adaptive > delay {
118                         delay = adaptive
119                 }
120                 s.cacheRefreshTimer = time.AfterFunc(delay, s.refreshProcessEnv)
121         }
122
123         return nil
124 }
125
126 // populateProcessEnv sets the dynamically configurable fields for the view's
127 // process environment. Assumes that the caller is holding the s.view.importsMu.
128 func (s *importsState) populateProcessEnv(ctx context.Context, snapshot *snapshot) (cleanup func(), err error) {
129         pe := s.processEnv
130
131         if snapshot.view.Options().VerboseOutput {
132                 pe.Logf = func(format string, args ...interface{}) {
133                         event.Log(ctx, fmt.Sprintf(format, args...))
134                 }
135         } else {
136                 pe.Logf = nil
137         }
138
139         // Take an extra reference to the snapshot so that its workspace directory
140         // (if any) isn't destroyed while we're using it.
141         release := snapshot.generation.Acquire(ctx)
142         _, inv, cleanupInvocation, err := snapshot.goCommandInvocation(ctx, source.LoadWorkspace, &gocommand.Invocation{
143                 WorkingDir: snapshot.view.rootURI.Filename(),
144         })
145         if err != nil {
146                 return nil, err
147         }
148         pe.WorkingDir = inv.WorkingDir
149         pe.BuildFlags = inv.BuildFlags
150         pe.WorkingDir = inv.WorkingDir
151         pe.ModFile = inv.ModFile
152         pe.ModFlag = inv.ModFlag
153         pe.Env = map[string]string{}
154         for _, kv := range inv.Env {
155                 split := strings.SplitN(kv, "=", 2)
156                 if len(split) != 2 {
157                         continue
158                 }
159                 pe.Env[split[0]] = split[1]
160         }
161
162         return func() {
163                 cleanupInvocation()
164                 release()
165         }, nil
166 }
167
168 func (s *importsState) refreshProcessEnv() {
169         start := time.Now()
170
171         s.mu.Lock()
172         env := s.processEnv
173         if resolver, err := s.processEnv.GetResolver(); err == nil {
174                 resolver.ClearForNewScan()
175         }
176         s.mu.Unlock()
177
178         event.Log(s.ctx, "background imports cache refresh starting")
179         if err := imports.PrimeCache(context.Background(), env); err == nil {
180                 event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)))
181         } else {
182                 event.Log(s.ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), keys.Err.Of(err))
183         }
184         s.mu.Lock()
185         s.cacheRefreshDuration = time.Since(start)
186         s.cacheRefreshTimer = nil
187         s.mu.Unlock()
188 }
189
190 func (s *importsState) destroy() {
191         s.mu.Lock()
192         if s.cleanupProcessEnv != nil {
193                 s.cleanupProcessEnv()
194         }
195         s.mu.Unlock()
196 }