handleUpdateSnapshot creates a new snapshot, optionally opening or closing projects and files. With no args, it adopts the latest LSP state. Opens and closes are ref-counted per session: the session holds at most one ref per project/file, so repeated opens are idempotent and a close only releases a
(ctx context.Context, params *UpdateSnapshotParams)
| 825 | // project/file, so repeated opens are idempotent and a close only releases a ref |
| 826 | // the session is actually holding. |
| 827 | func (s *Session) handleUpdateSnapshot(ctx context.Context, params *UpdateSnapshotParams) (*UpdateSnapshotResponse, error) { |
| 828 | // Fully serialize updates: snapshot creation, ref tracking, and the |
| 829 | // latestSnapshot/diff bookkeeping must be atomic with respect to other updates, |
| 830 | // otherwise concurrent updates could compute diffs against a non-adjacent |
| 831 | // snapshot or leave latestSnapshot pointing at a stale snapshot. |
| 832 | s.updateMu.Lock() |
| 833 | defer s.updateMu.Unlock() |
| 834 | |
| 835 | fileChanges := s.toFileChangeSummary(params.FileChanges) |
| 836 | |
| 837 | apiRequest := &project.APISnapshotRequest{} |
| 838 | |
| 839 | // Open projects: only take a new ref for projects we aren't already holding open. |
| 840 | var openedProjects []tspath.Path |
| 841 | for _, p := range params.OpenProjects { |
| 842 | configFileName := p.ToAbsoluteFileName(s.projectSession.GetCurrentDirectory()) |
| 843 | configPath := s.toPath(configFileName) |
| 844 | if s.openProjects.Has(configPath) { |
| 845 | continue |
| 846 | } |
| 847 | if apiRequest.OpenProjects == nil { |
| 848 | apiRequest.OpenProjects = collections.NewSetWithSizeHint[string](len(params.OpenProjects)) |
| 849 | } |
| 850 | apiRequest.OpenProjects.Add(configFileName) |
| 851 | openedProjects = append(openedProjects, configPath) |
| 852 | } |
| 853 | |
| 854 | // Close projects: only release a ref we currently hold. |
| 855 | var closedProjects []tspath.Path |
| 856 | for _, p := range params.CloseProjects { |
| 857 | configPath := s.toPath(p.ToAbsoluteFileName(s.projectSession.GetCurrentDirectory())) |
| 858 | if !s.openProjects.Has(configPath) { |
| 859 | continue |
| 860 | } |
| 861 | if apiRequest.CloseProjects == nil { |
| 862 | apiRequest.CloseProjects = collections.NewSetWithSizeHint[tspath.Path](len(params.CloseProjects)) |
| 863 | } |
| 864 | apiRequest.CloseProjects.Add(configPath) |
| 865 | closedProjects = append(closedProjects, configPath) |
| 866 | } |
| 867 | |
| 868 | // Open files: only open files we aren't already holding open, so each file is |
| 869 | // held by at most one API ref from this session. |
| 870 | var openedFiles []tspath.Path |
| 871 | for _, f := range params.OpenFiles { |
| 872 | uri := f.ToURI(s.projectSession.GetCurrentDirectory()) |
| 873 | path := s.toPath(uri.FileName()) |
| 874 | if s.openFiles.Has(path) { |
| 875 | continue |
| 876 | } |
| 877 | if apiRequest.OpenFiles == nil { |
| 878 | apiRequest.OpenFiles = collections.NewSetWithSizeHint[lsproto.DocumentUri](len(params.OpenFiles)) |
| 879 | } |
| 880 | apiRequest.OpenFiles.Add(uri) |
| 881 | openedFiles = append(openedFiles, path) |
| 882 | } |
| 883 | |
| 884 | // Close files: only release a ref we currently hold. |