1// Copyright 2019 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
5package cache
6
7import (
8	"context"
9	"crypto/sha1"
10	"fmt"
11	"go/token"
12	"strconv"
13	"sync/atomic"
14
15	"golang.org/x/tools/internal/lsp/debug"
16	"golang.org/x/tools/internal/lsp/source"
17	"golang.org/x/tools/internal/memoize"
18	"golang.org/x/tools/internal/span"
19)
20
21func New() source.Cache {
22	index := atomic.AddInt64(&cacheIndex, 1)
23	c := &cache{
24		fs:   &nativeFileSystem{},
25		id:   strconv.FormatInt(index, 10),
26		fset: token.NewFileSet(),
27	}
28	debug.AddCache(debugCache{c})
29	return c
30}
31
32type cache struct {
33	fs   source.FileSystem
34	id   string
35	fset *token.FileSet
36
37	store memoize.Store
38}
39
40type fileKey struct {
41	identity source.FileIdentity
42}
43
44type fileHandle struct {
45	cache      *cache
46	underlying source.FileHandle
47	handle     *memoize.Handle
48}
49
50type fileData struct {
51	memoize.NoCopy
52	bytes []byte
53	hash  string
54	err   error
55}
56
57func (c *cache) GetFile(uri span.URI, kind source.FileKind) source.FileHandle {
58	underlying := c.fs.GetFile(uri, kind)
59	key := fileKey{
60		identity: underlying.Identity(),
61	}
62	h := c.store.Bind(key, func(ctx context.Context) interface{} {
63		data := &fileData{}
64		data.bytes, data.hash, data.err = underlying.Read(ctx)
65		return data
66	})
67	return &fileHandle{
68		cache:      c,
69		underlying: underlying,
70		handle:     h,
71	}
72}
73
74func (c *cache) NewSession(ctx context.Context) source.Session {
75	index := atomic.AddInt64(&sessionIndex, 1)
76	s := &session{
77		cache:         c,
78		id:            strconv.FormatInt(index, 10),
79		options:       source.DefaultOptions,
80		overlays:      make(map[span.URI]*overlay),
81		filesWatchMap: NewWatchMap(),
82	}
83	debug.AddSession(debugSession{s})
84	return s
85}
86
87func (c *cache) FileSet() *token.FileSet {
88	return c.fset
89}
90
91func (h *fileHandle) FileSystem() source.FileSystem {
92	return h.cache
93}
94
95func (h *fileHandle) Identity() source.FileIdentity {
96	return h.underlying.Identity()
97}
98
99func (h *fileHandle) Read(ctx context.Context) ([]byte, string, error) {
100	v := h.handle.Get(ctx)
101	if v == nil {
102		return nil, "", ctx.Err()
103	}
104	data := v.(*fileData)
105	return data.bytes, data.hash, data.err
106}
107
108func hashContents(contents []byte) string {
109	// TODO: consider whether sha1 is the best choice here
110	// This hash is used for internal identity detection only
111	return fmt.Sprintf("%x", sha1.Sum(contents))
112}
113
114var cacheIndex, sessionIndex, viewIndex int64
115
116type debugCache struct{ *cache }
117
118func (c *cache) ID() string                  { return c.id }
119func (c debugCache) FileSet() *token.FileSet { return c.fset }
120