Skip to content
Open
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
2 changes: 1 addition & 1 deletion cmd/fastbelt/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func runGenerateCLI(opts generateOptions) error {
if err != nil {
return err
}
if err := builder.Build(context.Background(), []*core.Document{document}, nil); err != nil {
if err := builder.Build(context.Background(), []*core.Document{document}); err != nil {
return err
}

Expand Down
31 changes: 26 additions & 5 deletions document.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package fastbelt
import (
"strings"
"sync"
"sync/atomic"

"typefox.dev/fastbelt/textdoc"
"typefox.dev/lsp"
Expand All @@ -17,12 +18,15 @@ import (
// For example, the Root node may be nil if the document has not been parsed yet.
//
// Access to the fields of Document should be synchronized using a [typefox.dev/fastbelt/workspace] Lock.
// Readers admitted based on the document's build state (workspace Lock.ReadAt) may only access
// the fields produced by the states they requested; later fields may still be under construction.
// The document struct should never be copied after creation.
type Document struct {
// URI identifies the document in the workspace.
URI URI
// State tracks which build phases already ran for this document.
State DocumentState
// state tracks which build phases already ran for this document.
// Access it through [Document.State] and [Document.SetState].
state atomic.Uint32
// Root is the AST root produced by parsing.
// It is nil until parsing succeeds.
Root AstNode
Expand Down Expand Up @@ -78,7 +82,6 @@ func NewDocument(textDoc textdoc.Handle) *Document {
uri := ParseURI(string(textDoc.URI()))
return &Document{
URI: uri,
State: 0,
TextDoc: textDoc,
Root: nil,
LocalSymbols: nil,
Expand Down Expand Up @@ -108,6 +111,24 @@ func NewDocumentFromString(uri, languageId, content string) (*Document, error) {
return doc, nil
}

// State returns the build phases that have completed for this document.
//
// It is safe to call concurrently with a running build: the atomic load pairs
// with [Document.SetState], so a caller that observes a state bit is
// guaranteed to see all document data written by that build phase.
func (d *Document) State() DocumentState {
return DocumentState(d.state.Load())
}

// SetState replaces the document's build state.
//
// Callers must hold the workspace write lock and must write all document data
// belonging to a phase before setting its bit; the atomic store publishes that
// data to concurrent [Document.State] readers.
func (d *Document) SetState(state DocumentState) {
d.state.Store(uint32(state))
}

// DocumentState is a bitmask capturing the already completed build phases of a document.
type DocumentState uint32

Expand Down Expand Up @@ -152,9 +173,9 @@ func (s DocumentState) String() string {
return strings.Join(flags, " | ")
}

// Has reports whether flag is set in s.
// Has reports whether flag is set in s. If flag is a combination of multiple bits, Has reports whether all of them are set.
func (s DocumentState) Has(flag DocumentState) bool {
return s&flag != 0
return s&flag == flag
}

// With returns s with flag set.
Expand Down
6 changes: 3 additions & 3 deletions examples/statemachine/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ func BenchmarkWorkspaceCycle(b *testing.B) {
}

start := time.Now()
lock.Write(context.Background(), func(ctx context.Context, downgrade func()) {
if err := builder.Build(ctx, docs, downgrade); err != nil {
lock.Write(context.Background(), func(ctx context.Context) {
if err := builder.Build(ctx, docs); err != nil {
b.Errorf("build failed: %v", err)
}
})
Expand Down Expand Up @@ -181,7 +181,7 @@ func BenchmarkLocalLinking(b *testing.B) {
docs := []*fastbelt.Document{doc}
builder := service.MustGet[workspace.Builder](srv)
// Prebuild the file - we will reset the references later
if err := builder.Build(b.Context(), docs, nil); err != nil {
if err := builder.Build(b.Context(), docs); err != nil {
b.Errorf("build failed: %v", err)
}
b.SetBytes(int64(len(content)))
Expand Down
2 changes: 1 addition & 1 deletion examples/statemachine/builder_reset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestResetKeepsLocalSymbols(t *testing.T) {
t.Error("Reset kept ImportedSymbols although DocStateImportedSymbols was dropped")
}

if err := builder.Build(f.Ctx(), []*core.Document{doc.Document}, nil); err != nil {
if err := builder.Build(f.Ctx(), []*core.Document{doc.Document}); err != nil {
t.Fatalf("rebuild after reset failed: %v", err)
}
for _, ref := range doc.Document.References {
Expand Down
3 changes: 3 additions & 0 deletions server/completion_contributor.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ func NewDefaultCompletionContributor() CompletionContributor {
return &DefaultCompletionContributor{}
}

// Ensure DefaultCompletionContributor implements CompletionContributor.
var _ CompletionContributor = (*DefaultCompletionContributor)(nil)

// CompletionForToken emits a default item only for keyword tokens that contains a letter or digit.
// This prevents trivial punctuation tokens from appearing in the default completion list.
func (*DefaultCompletionContributor) CompletionForToken(_ context.Context, tt *core.TokenType, _ int, _ ContributorContext, accept CompletionAcceptor) {
Expand Down
11 changes: 11 additions & 0 deletions server/completion_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ func NewDefaultCompletionProvider(sc *service.Container) CompletionProvider {
return &DefaultCompletionProvider{sc: sc}
}

// The default required state for completion is DocStateLinked, which has to be
// satisfied across the whole workspace, because cross-references might require
// complex scoping and linking to be fully resolved.
func (s *DefaultCompletionProvider) RequiredState() (core.DocumentState, bool) {
return core.DocStateLinked, true
}

// Ensure that the default completion provider correctly implements the interfaces
var _ DocumentStateRequirements = (*DefaultCompletionProvider)(nil)
var _ CompletionProvider = (*DefaultCompletionProvider)(nil)

// HandleCompletionRequest fulfils textDocument/completion. The flow is:
//
// 1. resolve doc + cursor offset;
Expand Down
3 changes: 3 additions & 0 deletions server/completion_snippet.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ type DefaultSnippetRegistry struct {
snippets []SnippetTemplate
}

// Ensure DefaultSnippetRegistry implements SnippetRegistry.
var _ SnippetRegistry = (*DefaultSnippetRegistry)(nil)

// NewDefaultSnippetRegistry returns an empty registry.
func NewDefaultSnippetRegistry() SnippetRegistry {
return &DefaultSnippetRegistry{}
Expand Down
3 changes: 3 additions & 0 deletions server/completion_triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ type CompletionTriggers interface {
// DefaultCompletionTriggers returns nil - no auto-open characters.
type DefaultCompletionTriggers struct{}

// Ensure DefaultCompletionTriggers implements CompletionTriggers.
var _ CompletionTriggers = (*DefaultCompletionTriggers)(nil)

// NewDefaultCompletionTriggers returns the no-op trigger set.
func NewDefaultCompletionTriggers() CompletionTriggers {
return &DefaultCompletionTriggers{}
Expand Down
9 changes: 9 additions & 0 deletions server/definition_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@ type DefaultDefinitionProvider struct {
sc *service.Container
}

// Ensure the DefaultDefinitionProvider implements the expected interfaces.
var _ DefinitionProvider = (*DefaultDefinitionProvider)(nil)
var _ DocumentStateRequirements = (*DefaultDefinitionProvider)(nil)

func NewDefaultDefinitionProvider(sc *service.Container) DefinitionProvider {
return &DefaultDefinitionProvider{sc: sc}
}

// The default definition provider requires the document in question to be linked.
func (s *DefaultDefinitionProvider) RequiredState() (core.DocumentState, bool) {
return core.DocStateLinked, false
}

func (s *DefaultDefinitionProvider) HandleDefinitionRequest(ctx context.Context, params *lsp.DefinitionParams) ([]lsp.DefinitionLink, error) {
documentManager := service.MustGet[workspace.DocumentManager](s.sc)
uri := core.ParseURI(string(params.TextDocument.URI))
Expand Down
3 changes: 3 additions & 0 deletions server/diagnostics_publisher.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type DiagnosticsPublisher struct {
sc *service.Container
}

// Ensure DiagnosticsPublisher implements InitializeParticipant.
var _ InitializeParticipant = (*DiagnosticsPublisher)(nil)

// NewDiagnosticsPublisher creates a new instance of [DiagnosticsPublisher].
func NewDiagnosticsPublisher(sc *service.Container) *DiagnosticsPublisher {
return &DiagnosticsPublisher{sc: sc}
Expand Down
9 changes: 9 additions & 0 deletions server/document_highlight_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ type DocumentHighlightProvider interface {
HandleDocumentHighlightRequest(ctx context.Context, params *lsp.DocumentHighlightParams) ([]lsp.DocumentHighlight, error)
}

// Ensure DefaultDocumentHighlightProvider implements the expected interfaces.
var _ DocumentHighlightProvider = (*DefaultDocumentHighlightProvider)(nil)
var _ DocumentStateRequirements = (*DefaultDocumentHighlightProvider)(nil)

type DefaultDocumentHighlightProvider struct {
sc *service.Container
}
Expand All @@ -25,6 +29,11 @@ func NewDefaultDocumentHighlightProvider(sc *service.Container) DocumentHighligh
return &DefaultDocumentHighlightProvider{sc: sc}
}

// The default document highlight provider requires the document in question to be linked.
func (s *DefaultDocumentHighlightProvider) RequiredState() (core.DocumentState, bool) {
return core.DocStateLinked, false
}

func (s *DefaultDocumentHighlightProvider) HandleDocumentHighlightRequest(ctx context.Context, params *lsp.DocumentHighlightParams) ([]lsp.DocumentHighlight, error) {
documentManager := service.MustGet[workspace.DocumentManager](s.sc)
uri := core.ParseURI(string(params.TextDocument.URI))
Expand Down
8 changes: 8 additions & 0 deletions server/document_symbol_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ type DefaultDocumentSymbolProvider struct {
filter DocumentSymbolFilter
}

var _ DocumentSymbolProvider = (*DefaultDocumentSymbolProvider)(nil)
var _ DocumentStateRequirements = (*DefaultDocumentSymbolProvider)(nil)

// RequiredState indicates that the default document symbol provider requires the document to be parsed.
func (p *DefaultDocumentSymbolProvider) RequiredState() (core.DocumentState, bool) {
return core.DocStateParsed, false
}

// NewDefaultDocumentSymbolProvider creates a provider using services from the container.
func NewDefaultDocumentSymbolProvider(sc *service.Container) DocumentSymbolProvider {
return &DefaultDocumentSymbolProvider{
Expand Down
9 changes: 9 additions & 0 deletions server/folding_range_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ type DefaultFoldingRangeProvider struct {
filter FoldingRangeFilter
}

// Ensure DefaultFoldingRangeProvider implements the expected interfaces.
var _ FoldingRangeProvider = (*DefaultFoldingRangeProvider)(nil)
var _ DocumentStateRequirements = (*DefaultFoldingRangeProvider)(nil)

// RequiredState indicates that the default folding range provider requires the document to be parsed.
func (p *DefaultFoldingRangeProvider) RequiredState() (core.DocumentState, bool) {
return core.DocStateParsed, false
}

func NewDefaultFoldingRangeProvider(sc *service.Container) FoldingRangeProvider {
return &DefaultFoldingRangeProvider{
sc: sc,
Expand Down
9 changes: 9 additions & 0 deletions server/hover_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ type DefaultHoverProvider struct {
sc *service.Container
}

// Ensure DefaultHoverProvider implements the expected interfaces.
var _ HoverProvider = (*DefaultHoverProvider)(nil)
var _ DocumentStateRequirements = (*DefaultHoverProvider)(nil)

// RequiredState indicates that the default hover provider requires the document to be linked.
func (s *DefaultHoverProvider) RequiredState() (core.DocumentState, bool) {
return core.DocStateLinked, false
}

func NewDefaultHoverProvider(sc *service.Container) HoverProvider {
return &DefaultHoverProvider{sc: sc}
}
Expand Down
3 changes: 3 additions & 0 deletions server/references_finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type DefaultReferencesFinder struct {
sc *service.Container
}

// Ensure DefaultReferencesFinder implements the expected interfaces.
var _ ReferencesFinder = (*DefaultReferencesFinder)(nil)

func NewDefaultReferencesFinder(sc *service.Container) ReferencesFinder {
return &DefaultReferencesFinder{sc: sc}
}
Expand Down
10 changes: 10 additions & 0 deletions server/references_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ type DefaultReferencesProvider struct {
sc *service.Container
}

// Ensure DefaultReferencesProvider implements the expected interfaces.
var _ ReferencesProvider = (*DefaultReferencesProvider)(nil)
var _ DocumentStateRequirements = (*DefaultReferencesProvider)(nil)

// RequiredState indicates that the default references provider requires the workspace
// to have reference information collected.
func (rf *DefaultReferencesProvider) RequiredState() (core.DocumentState, bool) {
return core.DocStateReferences, true
}

func NewDefaultReferencesProvider(sc *service.Container) ReferencesProvider {
return &DefaultReferencesProvider{sc: sc}
}
Expand Down
10 changes: 10 additions & 0 deletions server/rename_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ type DefaultRenameProvider struct {
sc *service.Container
}

// Ensure DefaultRenameProvider implements the expected interfaces.
var _ RenameProvider = (*DefaultRenameProvider)(nil)
var _ DocumentStateRequirements = (*DefaultRenameProvider)(nil)

// RequiredState indicates that the default rename provider requires the workspace
// to have reference information collected.
func (rp *DefaultRenameProvider) RequiredState() (core.DocumentState, bool) {
return core.DocStateReferences, true
}

func NewDefaultRenameProvider(sc *service.Container) RenameProvider {
return &DefaultRenameProvider{sc: sc}
}
Expand Down
Loading
Loading