tree-sitter test
This commit is contained in:
parent
aad4917fc0
commit
4d79b8d6ba
|
|
@ -86,11 +86,14 @@ set(SOURCES
|
|||
src/provider/initialized/initialized_provider.cpp
|
||||
src/provider/text_document/did_open_provider.cpp
|
||||
src/provider/text_document/did_change_provider.cpp
|
||||
src/provider/text_document/did_close_provider.cpp
|
||||
src/provider/text_document/completion_provider.cpp
|
||||
src/provider/shutdown/shutdown_provider.cpp
|
||||
src/provider/exit/exit_provider.cpp
|
||||
src/provider/cancel_request/cancel_request_provider.cpp
|
||||
src/provider/trace/set_trace_provider.cpp)
|
||||
src/provider/trace/set_trace_provider.cpp
|
||||
src/services/document.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE src)
|
||||
|
|
|
|||
|
|
@ -1,762 +0,0 @@
|
|||
Completion Request (:leftwards_arrow_with_hook:)
|
||||
The Completion request is sent from the client to the server to compute completion items at a given cursor position. Completion items are presented in the IntelliSense user interface. If computing full completion items is expensive, servers can additionally provide a handler for the completion item resolve request (‘completionItem/resolve’). This request is sent when a completion item is selected in the user interface. A typical use case is for example: the textDocument/completion request doesn’t fill in the documentation property for returned completion items since it is expensive to compute. When the item is selected in the user interface then a ‘completionItem/resolve’ request is sent with the selected completion item as a parameter. The returned completion item should have the documentation property filled in. By default the request can only delay the computation of the detail and documentation properties. Since 3.16.0 the client can signal that it can resolve more properties lazily. This is done using the completionItem#resolveSupport client capability which lists all properties that can be filled in during a ‘completionItem/resolve’ request. All other properties (usually sortText, filterText, insertText and textEdit) must be provided in the textDocument/completion response and must not be changed during resolve.
|
||||
|
||||
The language server protocol uses the following model around completions:
|
||||
|
||||
to achieve consistency across languages and to honor different clients usually the client is responsible for filtering and sorting. This has also the advantage that client can experiment with different filter and sorting models. However servers can enforce different behavior by setting a filterText / sortText
|
||||
for speed clients should be able to filter an already received completion list if the user continues typing. Servers can opt out of this using a CompletionList and mark it as isIncomplete.
|
||||
A completion item provides additional means to influence filtering and sorting. They are expressed by either creating a CompletionItem with a insertText or with a textEdit. The two modes differ as follows:
|
||||
|
||||
Completion item provides an insertText / label without a text edit: in the model the client should filter against what the user has already typed using the word boundary rules of the language (e.g. resolving the word under the cursor position). The reason for this mode is that it makes it extremely easy for a server to implement a basic completion list and get it filtered on the client.
|
||||
|
||||
Completion Item with text edits: in this mode the server tells the client that it actually knows what it is doing. If you create a completion item with a text edit at the current cursor position no word guessing takes place and no automatic filtering (like with an insertText) should happen. This mode can be combined with a sort text and filter text to customize two things. If the text edit is a replace edit then the range denotes the word used for filtering. If the replace changes the text it most likely makes sense to specify a filter text to be used.
|
||||
|
||||
Client Capability:
|
||||
|
||||
property name (optional): textDocument.completion
|
||||
property type: CompletionClientCapabilities defined as follows:
|
||||
export interface CompletionClientCapabilities {
|
||||
/**
|
||||
* Whether completion supports dynamic registration.
|
||||
*/
|
||||
dynamicRegistration?: boolean;
|
||||
|
||||
/**
|
||||
* The client supports the following `CompletionItem` specific
|
||||
* capabilities.
|
||||
*/
|
||||
completionItem?: {
|
||||
/**
|
||||
* Client supports snippets as insert text.
|
||||
*
|
||||
* A snippet can define tab stops and placeholders with `$1`, `$2`
|
||||
* and `${3:foo}`. `$0` defines the final tab stop, it defaults to
|
||||
* the end of the snippet. Placeholders with equal identifiers are
|
||||
* linked, that is typing in one will update others too.
|
||||
*/
|
||||
snippetSupport?: boolean;
|
||||
|
||||
/**
|
||||
* Client supports commit characters on a completion item.
|
||||
*/
|
||||
commitCharactersSupport?: boolean;
|
||||
|
||||
/**
|
||||
* Client supports the follow content formats for the documentation
|
||||
* property. The order describes the preferred format of the client.
|
||||
*/
|
||||
documentationFormat?: MarkupKind[];
|
||||
|
||||
/**
|
||||
* Client supports the deprecated property on a completion item.
|
||||
*/
|
||||
deprecatedSupport?: boolean;
|
||||
|
||||
/**
|
||||
* Client supports the preselect property on a completion item.
|
||||
*/
|
||||
preselectSupport?: boolean;
|
||||
|
||||
/**
|
||||
* Client supports the tag property on a completion item. Clients
|
||||
* supporting tags have to handle unknown tags gracefully. Clients
|
||||
* especially need to preserve unknown tags when sending a completion
|
||||
* item back to the server in a resolve call.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*/
|
||||
tagSupport?: {
|
||||
/**
|
||||
* The tags supported by the client.
|
||||
*/
|
||||
valueSet: CompletionItemTag[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Client supports insert replace edit to control different behavior if
|
||||
* a completion item is inserted in the text or should replace text.
|
||||
*
|
||||
* @since 3.16.0
|
||||
*/
|
||||
insertReplaceSupport?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates which properties a client can resolve lazily on a
|
||||
* completion item. Before version 3.16.0 only the predefined properties
|
||||
* `documentation` and `detail` could be resolved lazily.
|
||||
*
|
||||
* @since 3.16.0
|
||||
*/
|
||||
resolveSupport?: {
|
||||
/**
|
||||
* The properties that a client can resolve lazily.
|
||||
*/
|
||||
properties: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The client supports the `insertTextMode` property on
|
||||
* a completion item to override the whitespace handling mode
|
||||
* as defined by the client (see `insertTextMode`).
|
||||
*
|
||||
* @since 3.16.0
|
||||
*/
|
||||
insertTextModeSupport?: {
|
||||
valueSet: InsertTextMode[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The client has support for completion item label
|
||||
* details (see also `CompletionItemLabelDetails`).
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
labelDetailsSupport?: boolean;
|
||||
};
|
||||
|
||||
completionItemKind?: {
|
||||
/**
|
||||
* The completion item kind values the client supports. When this
|
||||
* property exists the client also guarantees that it will
|
||||
* handle values outside its set gracefully and falls back
|
||||
* to a default value when unknown.
|
||||
*
|
||||
* If this property is not present the client only supports
|
||||
* the completion items kinds from `Text` to `Reference` as defined in
|
||||
* the initial version of the protocol.
|
||||
*/
|
||||
valueSet?: CompletionItemKind[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The client supports to send additional context information for a
|
||||
* `textDocument/completion` request.
|
||||
*/
|
||||
contextSupport?: boolean;
|
||||
|
||||
/**
|
||||
* The client's default when the completion item doesn't provide a
|
||||
* `insertTextMode` property.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
insertTextMode?: InsertTextMode;
|
||||
|
||||
/**
|
||||
* The client supports the following `CompletionList` specific
|
||||
* capabilities.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
completionList?: {
|
||||
/**
|
||||
* The client supports the following itemDefaults on
|
||||
* a completion list.
|
||||
*
|
||||
* The value lists the supported property names of the
|
||||
* `CompletionList.itemDefaults` object. If omitted
|
||||
* no properties are supported.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
itemDefaults?: string[];
|
||||
}
|
||||
}
|
||||
Server Capability:
|
||||
|
||||
property name (optional): completionProvider
|
||||
property type: CompletionOptions defined as follows:
|
||||
/**
|
||||
* Completion options.
|
||||
*/
|
||||
export interface CompletionOptions extends WorkDoneProgressOptions {
|
||||
/**
|
||||
* The additional characters, beyond the defaults provided by the client (typically
|
||||
* [a-zA-Z]), that should automatically trigger a completion request. For example
|
||||
* `.` in JavaScript represents the beginning of an object property or method and is
|
||||
* thus a good candidate for triggering a completion request.
|
||||
*
|
||||
* Most tools trigger a completion request automatically without explicitly
|
||||
* requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they
|
||||
* do so when the user starts to type an identifier. For example if the user
|
||||
* types `c` in a JavaScript file code complete will automatically pop up
|
||||
* present `console` besides others as a completion item. Characters that
|
||||
* make up identifiers don't need to be listed here.
|
||||
*/
|
||||
triggerCharacters?: string[];
|
||||
|
||||
/**
|
||||
* The list of all possible characters that commit a completion. This field
|
||||
* can be used if clients don't support individual commit characters per
|
||||
* completion item. See client capability
|
||||
* `completion.completionItem.commitCharactersSupport`.
|
||||
*
|
||||
* If a server provides both `allCommitCharacters` and commit characters on
|
||||
* an individual completion item the ones on the completion item win.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
allCommitCharacters?: string[];
|
||||
|
||||
/**
|
||||
* The server provides support to resolve additional
|
||||
* information for a completion item.
|
||||
*/
|
||||
resolveProvider?: boolean;
|
||||
|
||||
/**
|
||||
* The server supports the following `CompletionItem` specific
|
||||
* capabilities.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
completionItem?: {
|
||||
/**
|
||||
* The server has support for completion item label
|
||||
* details (see also `CompletionItemLabelDetails`) when receiving
|
||||
* a completion item in a resolve call.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
labelDetailsSupport?: boolean;
|
||||
}
|
||||
}
|
||||
Registration Options: CompletionRegistrationOptions options defined as follows:
|
||||
|
||||
export interface CompletionRegistrationOptions
|
||||
extends TextDocumentRegistrationOptions, CompletionOptions {
|
||||
}
|
||||
Request:
|
||||
|
||||
method: textDocument/completion
|
||||
params: CompletionParams defined as follows:
|
||||
export interface CompletionParams extends TextDocumentPositionParams,
|
||||
WorkDoneProgressParams, PartialResultParams {
|
||||
/**
|
||||
* The completion context. This is only available if the client specifies
|
||||
* to send this using the client capability
|
||||
* `completion.contextSupport === true`
|
||||
*/
|
||||
context?: CompletionContext;
|
||||
}
|
||||
/**
|
||||
* How a completion was triggered
|
||||
*/
|
||||
export namespace CompletionTriggerKind {
|
||||
/**
|
||||
* Completion was triggered by typing an identifier (24x7 code
|
||||
* complete), manual invocation (e.g Ctrl+Space) or via API.
|
||||
*/
|
||||
export const Invoked: 1 = 1;
|
||||
|
||||
/**
|
||||
* Completion was triggered by a trigger character specified by
|
||||
* the `triggerCharacters` properties of the
|
||||
* `CompletionRegistrationOptions`.
|
||||
*/
|
||||
export const TriggerCharacter: 2 = 2;
|
||||
|
||||
/**
|
||||
* Completion was re-triggered as the current completion list is incomplete.
|
||||
*/
|
||||
export const TriggerForIncompleteCompletions: 3 = 3;
|
||||
}
|
||||
export type CompletionTriggerKind = 1 | 2 | 3;
|
||||
/**
|
||||
* Contains additional information about the context in which a completion
|
||||
* request is triggered.
|
||||
*/
|
||||
export interface CompletionContext {
|
||||
/**
|
||||
* How the completion was triggered.
|
||||
*/
|
||||
triggerKind: CompletionTriggerKind;
|
||||
|
||||
/**
|
||||
* The trigger character (a single character) that has trigger code
|
||||
* complete. Is undefined if
|
||||
* `triggerKind !== CompletionTriggerKind.TriggerCharacter`
|
||||
*/
|
||||
triggerCharacter?: string;
|
||||
}
|
||||
Response:
|
||||
|
||||
result: CompletionItem[] | CompletionList | null. If a CompletionItem[] is provided it is interpreted to be complete. So it is the same as { isIncomplete: false, items }
|
||||
/**
|
||||
* Represents a collection of [completion items](#CompletionItem) to be
|
||||
* presented in the editor.
|
||||
*/
|
||||
export interface CompletionList {
|
||||
/**
|
||||
* This list is not complete. Further typing should result in recomputing
|
||||
* this list.
|
||||
*
|
||||
* Recomputed lists have all their items replaced (not appended) in the
|
||||
* incomplete completion sessions.
|
||||
*/
|
||||
isIncomplete: boolean;
|
||||
|
||||
/**
|
||||
* In many cases the items of an actual completion result share the same
|
||||
* value for properties like `commitCharacters` or the range of a text
|
||||
* edit. A completion list can therefore define item defaults which will
|
||||
* be used if a completion item itself doesn't specify the value.
|
||||
*
|
||||
* If a completion list specifies a default value and a completion item
|
||||
* also specifies a corresponding value the one from the item is used.
|
||||
*
|
||||
* Servers are only allowed to return default values if the client
|
||||
* signals support for this via the `completionList.itemDefaults`
|
||||
* capability.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
itemDefaults?: {
|
||||
/**
|
||||
* A default commit character set.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
commitCharacters?: string[];
|
||||
|
||||
/**
|
||||
* A default edit range
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
editRange?: Range | {
|
||||
insert: Range;
|
||||
replace: Range;
|
||||
};
|
||||
|
||||
/**
|
||||
* A default insert text format
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
insertTextFormat?: InsertTextFormat;
|
||||
|
||||
/**
|
||||
* A default insert text mode
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
insertTextMode?: InsertTextMode;
|
||||
|
||||
/**
|
||||
* A default data value.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
data?: LSPAny;
|
||||
}
|
||||
|
||||
/**
|
||||
* The completion items.
|
||||
*/
|
||||
items: CompletionItem[];
|
||||
}
|
||||
/**
|
||||
* Defines whether the insert text in a completion item should be interpreted as
|
||||
* plain text or a snippet.
|
||||
*/
|
||||
export namespace InsertTextFormat {
|
||||
/**
|
||||
* The primary text to be inserted is treated as a plain string.
|
||||
*/
|
||||
export const PlainText = 1;
|
||||
|
||||
/**
|
||||
* The primary text to be inserted is treated as a snippet.
|
||||
*
|
||||
* A snippet can define tab stops and placeholders with `$1`, `$2`
|
||||
* and `${3:foo}`. `$0` defines the final tab stop, it defaults to
|
||||
* the end of the snippet. Placeholders with equal identifiers are linked,
|
||||
* that is typing in one will update others too.
|
||||
*/
|
||||
export const Snippet = 2;
|
||||
}
|
||||
|
||||
export type InsertTextFormat = 1 | 2;
|
||||
/**
|
||||
* Completion item tags are extra annotations that tweak the rendering of a
|
||||
* completion item.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*/
|
||||
export namespace CompletionItemTag {
|
||||
/**
|
||||
* Render a completion as obsolete, usually using a strike-out.
|
||||
*/
|
||||
export const Deprecated = 1;
|
||||
}
|
||||
|
||||
export type CompletionItemTag = 1;
|
||||
/**
|
||||
* A special text edit to provide an insert and a replace operation.
|
||||
*
|
||||
* @since 3.16.0
|
||||
*/
|
||||
export interface InsertReplaceEdit {
|
||||
/**
|
||||
* The string to be inserted.
|
||||
*/
|
||||
newText: string;
|
||||
|
||||
/**
|
||||
* The range if the insert is requested
|
||||
*/
|
||||
insert: Range;
|
||||
|
||||
/**
|
||||
* The range if the replace is requested.
|
||||
*/
|
||||
replace: Range;
|
||||
}
|
||||
/**
|
||||
* How whitespace and indentation is handled during completion
|
||||
* item insertion.
|
||||
*
|
||||
* @since 3.16.0
|
||||
*/
|
||||
export namespace InsertTextMode {
|
||||
/**
|
||||
* The insertion or replace strings is taken as it is. If the
|
||||
* value is multi line the lines below the cursor will be
|
||||
* inserted using the indentation defined in the string value.
|
||||
* The client will not apply any kind of adjustments to the
|
||||
* string.
|
||||
*/
|
||||
export const asIs: 1 = 1;
|
||||
|
||||
/**
|
||||
* The editor adjusts leading whitespace of new lines so that
|
||||
* they match the indentation up to the cursor of the line for
|
||||
* which the item is accepted.
|
||||
*
|
||||
* Consider a line like this: <2tabs><cursor><3tabs>foo. Accepting a
|
||||
* multi line completion item is indented using 2 tabs and all
|
||||
* following lines inserted will be indented using 2 tabs as well.
|
||||
*/
|
||||
export const adjustIndentation: 2 = 2;
|
||||
}
|
||||
|
||||
export type InsertTextMode = 1 | 2;
|
||||
/**
|
||||
* Additional details for a completion item label.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
export interface CompletionItemLabelDetails {
|
||||
|
||||
/**
|
||||
* An optional string which is rendered less prominently directly after
|
||||
* {@link CompletionItem.label label}, without any spacing. Should be
|
||||
* used for function signatures or type annotations.
|
||||
*/
|
||||
detail?: string;
|
||||
|
||||
/**
|
||||
* An optional string which is rendered less prominently after
|
||||
* {@link CompletionItemLabelDetails.detail}. Should be used for fully qualified
|
||||
* names or file path.
|
||||
*/
|
||||
description?: string;
|
||||
}
|
||||
export interface CompletionItem {
|
||||
|
||||
/**
|
||||
* The label of this completion item.
|
||||
*
|
||||
* The label property is also by default the text that
|
||||
* is inserted when selecting this completion.
|
||||
*
|
||||
* If label details are provided the label itself should
|
||||
* be an unqualified name of the completion item.
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* Additional details for the label
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
labelDetails?: CompletionItemLabelDetails;
|
||||
|
||||
|
||||
/**
|
||||
* The kind of this completion item. Based of the kind
|
||||
* an icon is chosen by the editor. The standardized set
|
||||
* of available values is defined in `CompletionItemKind`.
|
||||
*/
|
||||
kind?: CompletionItemKind;
|
||||
|
||||
/**
|
||||
* Tags for this completion item.
|
||||
*
|
||||
* @since 3.15.0
|
||||
*/
|
||||
tags?: CompletionItemTag[];
|
||||
|
||||
/**
|
||||
* A human-readable string with additional information
|
||||
* about this item, like type or symbol information.
|
||||
*/
|
||||
detail?: string;
|
||||
|
||||
/**
|
||||
* A human-readable string that represents a doc-comment.
|
||||
*/
|
||||
documentation?: string | MarkupContent;
|
||||
|
||||
/**
|
||||
* Indicates if this item is deprecated.
|
||||
*
|
||||
* @deprecated Use `tags` instead if supported.
|
||||
*/
|
||||
deprecated?: boolean;
|
||||
|
||||
/**
|
||||
* Select this item when showing.
|
||||
*
|
||||
* *Note* that only one completion item can be selected and that the
|
||||
* tool / client decides which item that is. The rule is that the *first*
|
||||
* item of those that match best is selected.
|
||||
*/
|
||||
preselect?: boolean;
|
||||
|
||||
/**
|
||||
* A string that should be used when comparing this item
|
||||
* with other items. When omitted the label is used
|
||||
* as the sort text for this item.
|
||||
*/
|
||||
sortText?: string;
|
||||
|
||||
/**
|
||||
* A string that should be used when filtering a set of
|
||||
* completion items. When omitted the label is used as the
|
||||
* filter text for this item.
|
||||
*/
|
||||
filterText?: string;
|
||||
|
||||
/**
|
||||
* A string that should be inserted into a document when selecting
|
||||
* this completion. When omitted the label is used as the insert text
|
||||
* for this item.
|
||||
*
|
||||
* The `insertText` is subject to interpretation by the client side.
|
||||
* Some tools might not take the string literally. For example
|
||||
* VS Code when code complete is requested in this example
|
||||
* `con<cursor position>` and a completion item with an `insertText` of
|
||||
* `console` is provided it will only insert `sole`. Therefore it is
|
||||
* recommended to use `textEdit` instead since it avoids additional client
|
||||
* side interpretation.
|
||||
*/
|
||||
insertText?: string;
|
||||
|
||||
/**
|
||||
* The format of the insert text. The format applies to both the
|
||||
* `insertText` property and the `newText` property of a provided
|
||||
* `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`.
|
||||
*
|
||||
* Please note that the insertTextFormat doesn't apply to
|
||||
* `additionalTextEdits`.
|
||||
*/
|
||||
insertTextFormat?: InsertTextFormat;
|
||||
|
||||
/**
|
||||
* How whitespace and indentation is handled during completion
|
||||
* item insertion. If not provided the client's default value depends on
|
||||
* the `textDocument.completion.insertTextMode` client capability.
|
||||
*
|
||||
* @since 3.16.0
|
||||
* @since 3.17.0 - support for `textDocument.completion.insertTextMode`
|
||||
*/
|
||||
insertTextMode?: InsertTextMode;
|
||||
|
||||
/**
|
||||
* An edit which is applied to a document when selecting this completion.
|
||||
* When an edit is provided the value of `insertText` is ignored.
|
||||
*
|
||||
* *Note:* The range of the edit must be a single line range and it must
|
||||
* contain the position at which completion has been requested.
|
||||
*
|
||||
* Most editors support two different operations when accepting a completion
|
||||
* item. One is to insert a completion text and the other is to replace an
|
||||
* existing text with a completion text. Since this can usually not be
|
||||
* predetermined by a server it can report both ranges. Clients need to
|
||||
* signal support for `InsertReplaceEdit`s via the
|
||||
* `textDocument.completion.completionItem.insertReplaceSupport` client
|
||||
* capability property.
|
||||
*
|
||||
* *Note 1:* The text edit's range as well as both ranges from an insert
|
||||
* replace edit must be a [single line] and they must contain the position
|
||||
* at which completion has been requested.
|
||||
* *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range
|
||||
* must be a prefix of the edit's replace range, that means it must be
|
||||
* contained and starting at the same position.
|
||||
*
|
||||
* @since 3.16.0 additional type `InsertReplaceEdit`
|
||||
*/
|
||||
textEdit?: TextEdit | InsertReplaceEdit;
|
||||
|
||||
/**
|
||||
* The edit text used if the completion item is part of a CompletionList and
|
||||
* CompletionList defines an item default for the text edit range.
|
||||
*
|
||||
* Clients will only honor this property if they opt into completion list
|
||||
* item defaults using the capability `completionList.itemDefaults`.
|
||||
*
|
||||
* If not provided and a list's default range is provided the label
|
||||
* property is used as a text.
|
||||
*
|
||||
* @since 3.17.0
|
||||
*/
|
||||
textEditText?: string;
|
||||
|
||||
/**
|
||||
* An optional array of additional text edits that are applied when
|
||||
* selecting this completion. Edits must not overlap (including the same
|
||||
* insert position) with the main edit nor with themselves.
|
||||
*
|
||||
* Additional text edits should be used to change text unrelated to the
|
||||
* current cursor position (for example adding an import statement at the
|
||||
* top of the file if the completion item will insert an unqualified type).
|
||||
*/
|
||||
additionalTextEdits?: TextEdit[];
|
||||
|
||||
/**
|
||||
* An optional set of characters that when pressed while this completion is
|
||||
* active will accept it first and then type that character. *Note* that all
|
||||
* commit characters should have `length=1` and that superfluous characters
|
||||
* will be ignored.
|
||||
*/
|
||||
commitCharacters?: string[];
|
||||
|
||||
/**
|
||||
* An optional command that is executed *after* inserting this completion.
|
||||
* *Note* that additional modifications to the current document should be
|
||||
* described with the additionalTextEdits-property.
|
||||
*/
|
||||
command?: Command;
|
||||
|
||||
/**
|
||||
* A data entry field that is preserved on a completion item between
|
||||
* a completion and a completion resolve request.
|
||||
*/
|
||||
data?: LSPAny;
|
||||
}
|
||||
/**
|
||||
* The kind of a completion entry.
|
||||
*/
|
||||
export namespace CompletionItemKind {
|
||||
export const Text = 1;
|
||||
export const Method = 2;
|
||||
export const Function = 3;
|
||||
export const Constructor = 4;
|
||||
export const Field = 5;
|
||||
export const Variable = 6;
|
||||
export const Class = 7;
|
||||
export const Interface = 8;
|
||||
export const Module = 9;
|
||||
export const Property = 10;
|
||||
export const Unit = 11;
|
||||
export const Value = 12;
|
||||
export const Enum = 13;
|
||||
export const Keyword = 14;
|
||||
export const Snippet = 15;
|
||||
export const Color = 16;
|
||||
export const File = 17;
|
||||
export const Reference = 18;
|
||||
export const Folder = 19;
|
||||
export const EnumMember = 20;
|
||||
export const Constant = 21;
|
||||
export const Struct = 22;
|
||||
export const Event = 23;
|
||||
export const Operator = 24;
|
||||
export const TypeParameter = 25;
|
||||
}
|
||||
|
||||
export type CompletionItemKind = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
|
||||
partial result: CompletionItem[] or CompletionList followed by CompletionItem[]. If the first provided result item is of type CompletionList subsequent partial results of CompletionItem[] add to the items property of the CompletionList.
|
||||
error: code and message set in case an exception happens during the completion request.
|
||||
Completion items support snippets (see InsertTextFormat.Snippet). The snippet format is as follows:
|
||||
|
||||
Snippet Syntax
|
||||
The body of a snippet can use special constructs to control cursors and the text being inserted. The following are supported features and their syntaxes:
|
||||
|
||||
Tab stops
|
||||
With tab stops, you can make the editor cursor move inside a snippet. Use $1, $2 to specify cursor locations. The number is the order in which tab stops will be visited, whereas $0 denotes the final cursor position. Multiple tab stops are linked and updated in sync.
|
||||
|
||||
Placeholders
|
||||
Placeholders are tab stops with values, like ${1:foo}. The placeholder text will be inserted and selected such that it can be easily changed. Placeholders can be nested, like ${1:another ${2:placeholder}}.
|
||||
|
||||
Choice
|
||||
Placeholders can have choices as values. The syntax is a comma separated enumeration of values, enclosed with the pipe-character, for example ${1|one,two,three|}. When the snippet is inserted and the placeholder selected, choices will prompt the user to pick one of the values.
|
||||
|
||||
Variables
|
||||
With $name or ${name:default} you can insert the value of a variable. When a variable isn’t set, its default or the empty string is inserted. When a variable is unknown (that is, its name isn’t defined) the name of the variable is inserted and it is transformed into a placeholder.
|
||||
|
||||
The following variables can be used:
|
||||
|
||||
TM_SELECTED_TEXT The currently selected text or the empty string
|
||||
TM_CURRENT_LINE The contents of the current line
|
||||
TM_CURRENT_WORD The contents of the word under cursor or the empty string
|
||||
TM_LINE_INDEX The zero-index based line number
|
||||
TM_LINE_NUMBER The one-index based line number
|
||||
TM_FILENAME The filename of the current document
|
||||
TM_FILENAME_BASE The filename of the current document without its extensions
|
||||
TM_DIRECTORY The directory of the current document
|
||||
TM_FILEPATH The full file path of the current document
|
||||
Variable Transforms
|
||||
Transformations allow you to modify the value of a variable before it is inserted. The definition of a transformation consists of three parts:
|
||||
|
||||
A regular expression that is matched against the value of a variable, or the empty string when the variable cannot be resolved.
|
||||
A “format string” that allows to reference matching groups from the regular expression. The format string allows for conditional inserts and simple modifications.
|
||||
Options that are passed to the regular expression.
|
||||
The following example inserts the name of the current file without its ending, so from foo.txt it makes foo.
|
||||
|
||||
${TM_FILENAME/(.*)\..+$/$1/}
|
||||
| | | |
|
||||
| | | |-> no options
|
||||
| | |
|
||||
| | |-> references the contents of the first
|
||||
| | capture group
|
||||
| |
|
||||
| |-> regex to capture everything before
|
||||
| the final `.suffix`
|
||||
|
|
||||
|-> resolves to the filename
|
||||
Grammar
|
||||
Below is the EBNF (extended Backus-Naur form) for snippets. With \ (backslash), you can escape $, } and \. Within choice elements, the backslash also escapes comma and pipe characters.
|
||||
|
||||
any ::= tabstop | placeholder | choice | variable | text
|
||||
tabstop ::= '$' int | '${' int '}'
|
||||
placeholder ::= '${' int ':' any '}'
|
||||
choice ::= '${' int '|' text (',' text)* '|}'
|
||||
variable ::= '$' var | '${' var }'
|
||||
| '${' var ':' any '}'
|
||||
| '${' var '/' regex '/' (format | text)+ '/' options '}'
|
||||
format ::= '$' int | '${' int '}'
|
||||
| '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
|
||||
| '${' int ':+' if '}'
|
||||
| '${' int ':?' if ':' else '}'
|
||||
| '${' int ':-' else '}' | '${' int ':' else '}'
|
||||
regex ::= Regular Expression value (ctor-string)
|
||||
options ::= Regular Expression option (ctor-options)
|
||||
var ::= [_a-zA-Z] [_a-zA-Z0-9]*
|
||||
int ::= [0-9]+
|
||||
text ::= .*
|
||||
if ::= text
|
||||
else ::= text
|
||||
Completion Item Resolve Request (:leftwards_arrow_with_hook:)
|
||||
The request is sent from the client to the server to resolve additional information for a given completion item.
|
||||
|
||||
Request:
|
||||
|
||||
method: completionItem/resolve
|
||||
params: CompletionItem
|
||||
Response:
|
||||
|
||||
result: CompletionItem
|
||||
error: code and message set in case an exception happens during the completion resolve request.
|
||||
|
|
@ -19,9 +19,9 @@ namespace lsp::core
|
|||
spdlog::debug("RequestScheduler set in dispatcher");
|
||||
}
|
||||
|
||||
void RequestDispatcher::SetDocumentService(services::DocumentService* document_manager)
|
||||
void RequestDispatcher::SetSeviceContainer(services::ServiceContainer* service_container)
|
||||
{
|
||||
document_service_ = document_manager;
|
||||
service_container_ = service_container;
|
||||
spdlog::debug("DocumentService is set in dispatcher");
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ namespace lsp::core
|
|||
|
||||
std::string RequestDispatcher::Dispatch(const protocol::RequestMessage& request)
|
||||
{
|
||||
providers::ExecutionContext context(scheduler_, context_lifecycle_callback_, document_service_);
|
||||
providers::ExecutionContext context(context_lifecycle_callback_, *scheduler_, *service_container_);
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock(providers_mutex_);
|
||||
auto it = providers_.find(request.method);
|
||||
|
|
@ -76,7 +76,7 @@ namespace lsp::core
|
|||
|
||||
void RequestDispatcher::Dispatch(const protocol::NotificationMessage& notification)
|
||||
{
|
||||
providers::ExecutionContext context(scheduler_, context_lifecycle_callback_, document_service_);
|
||||
providers::ExecutionContext context(context_lifecycle_callback_, *scheduler_, *service_container_);
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock(notification_providers_mutex_);
|
||||
// 先尝试精确匹配
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <shared_mutex>
|
||||
#include "../protocol/protocol.hpp"
|
||||
#include "../provider/base/provider_interface.hpp"
|
||||
#include "../services/service_container.hpp"
|
||||
|
||||
namespace lsp::core
|
||||
{
|
||||
|
|
@ -15,7 +16,7 @@ namespace lsp::core
|
|||
~RequestDispatcher() = default;
|
||||
|
||||
void SetRequestScheduler(scheduler::RequestScheduler* scheduler);
|
||||
void SetDocumentService(services::DocumentService* document_manager);
|
||||
void SetSeviceContainer(services::ServiceContainer* service_container);
|
||||
|
||||
void RegisterRequestProvider(std::shared_ptr<providers::IRequestProvider> provider);
|
||||
void RegisterNotificationProvider(std::shared_ptr<providers::INotificationProvider> provider);
|
||||
|
|
@ -52,6 +53,6 @@ namespace lsp::core
|
|||
|
||||
// 服务引用
|
||||
scheduler::RequestScheduler* scheduler_ = nullptr;
|
||||
services::DocumentService* document_service_ = nullptr;
|
||||
services::ServiceContainer* service_container_ = nullptr;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "../protocol/transform/facade.hpp"
|
||||
#include "../scheduler/request_scheduler.hpp"
|
||||
#include "../services/document.hpp"
|
||||
#include "../services/document.hpp"
|
||||
#include "./server.hpp"
|
||||
|
||||
namespace lsp::core
|
||||
|
|
@ -300,8 +301,8 @@ namespace lsp::core
|
|||
{
|
||||
spdlog::debug("Initializing extension services...");
|
||||
|
||||
document_service_ = std::make_unique<services::DocumentService>();
|
||||
dispatcher_.SetDocumentService(document_service_.get());
|
||||
// service_container_.RegisterService(std::shared_ptr<services::DocumentService>());
|
||||
dispatcher_.SetSeviceContainer(&service_container_);
|
||||
|
||||
spdlog::debug("Extension services initialized");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,12 +50,9 @@ namespace lsp::core
|
|||
void SendStateError(const protocol::RequestMessage& request);
|
||||
|
||||
private:
|
||||
// 核心组件-必需的,生命周期和LspServer一致
|
||||
RequestDispatcher dispatcher_;
|
||||
scheduler::RequestScheduler scheduler_;
|
||||
|
||||
// 可选/扩展组件 -- 所以用智能指针
|
||||
std::unique_ptr<services::DocumentService> document_service_;
|
||||
services::ServiceContainer service_container_;
|
||||
|
||||
std::atomic<bool> is_initialized_ = false;
|
||||
std::atomic<bool> is_shutting_down_ = false;
|
||||
|
|
|
|||
|
|
@ -180,11 +180,6 @@ namespace lsp::protocol
|
|||
ServerInfo serverInfo;
|
||||
};
|
||||
|
||||
enum class InitializeErrorCodes
|
||||
{
|
||||
kUnknownProtocolVersion = 1
|
||||
};
|
||||
|
||||
struct InitializeError
|
||||
{
|
||||
boolean retry;
|
||||
|
|
@ -201,8 +196,6 @@ namespace lsp::protocol
|
|||
std::optional<integer> processId;
|
||||
std::optional<ClientInfo> clientInfo;
|
||||
std::optional<string> locale;
|
||||
std::optional<string> rootPath;
|
||||
std::optional<DocumentUri> rootUri;
|
||||
std::optional<LSPAny> initializationOptions;
|
||||
std::optional<ClientCapabilities> capabilities;
|
||||
TraceValue trace;
|
||||
|
|
|
|||
|
|
@ -86,15 +86,7 @@ namespace lsp::protocol
|
|||
|
||||
struct Hover
|
||||
{
|
||||
struct MarkedStringObject
|
||||
{
|
||||
std::string language;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
using MarkedString = std::variant<std::string, MarkedStringObject>;
|
||||
|
||||
std::variant<MarkedString, std::vector<MarkedString>, MarkupContent> contents;
|
||||
MarkupContent contents;
|
||||
std::optional<Range> range;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ namespace lsp::protocol
|
|||
struct TextDocumentContentChangeEvent
|
||||
{
|
||||
Range range;
|
||||
uinteger rangeLength;
|
||||
string text;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -47,22 +47,11 @@ namespace lsp::protocol
|
|||
std::optional<string> detail;
|
||||
SymbolKind kind;
|
||||
std::optional<std::vector<SymbolTag>> tags;
|
||||
std::optional<boolean> deprecated;
|
||||
Range range;
|
||||
Range selectionRange;
|
||||
std::optional<std::vector<DocumentSymbol>> children;
|
||||
};
|
||||
|
||||
struct SymbolInformation
|
||||
{
|
||||
string name;
|
||||
SymbolKind kind;
|
||||
std::optional<std::vector<SymbolTag>> tags;
|
||||
std::optional<boolean> deprecated;
|
||||
Location location;
|
||||
std::optional<string> containerName;
|
||||
};
|
||||
|
||||
struct DocumentSymbolClientCapabilities
|
||||
{
|
||||
struct SymbolKinds
|
||||
|
|
|
|||
|
|
@ -103,8 +103,6 @@ namespace glz
|
|||
&T::processId,
|
||||
&T::clientInfo,
|
||||
&T::locale,
|
||||
&T::rootPath,
|
||||
&T::rootUri,
|
||||
&T::initializationOptions,
|
||||
&T::capabilities,
|
||||
&T::trace,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include "../../protocol/protocol.hpp"
|
||||
#include "../../scheduler/request_scheduler.hpp"
|
||||
#include "../../services/document.hpp"
|
||||
#include "../../services/service_container.hpp"
|
||||
|
||||
namespace lsp::providers
|
||||
{
|
||||
|
|
@ -21,12 +22,16 @@ namespace lsp::providers
|
|||
class ExecutionContext
|
||||
{
|
||||
public:
|
||||
ExecutionContext(scheduler::RequestScheduler* scheduler, LifecycleCallback lifecycle_callback, services::DocumentService* document_manager = nullptr) :
|
||||
scheduler_(scheduler), lifecycle_callback_(lifecycle_callback), document_service_(document_manager) {}
|
||||
ExecutionContext(LifecycleCallback lifecycle_callback, scheduler::RequestScheduler& scheduler, services::ServiceContainer& container) :
|
||||
lifecycle_callback_(lifecycle_callback), scheduler_(scheduler), service_container_(container) {}
|
||||
|
||||
scheduler::RequestScheduler* GetScheduler() const { return scheduler_; }
|
||||
scheduler::RequestScheduler& GetScheduler() const { return scheduler_; }
|
||||
|
||||
services::DocumentService* GetDocumentService() const { return document_service_; }
|
||||
template<typename T>
|
||||
T& GetService() const
|
||||
{
|
||||
return service_container_.Get<T>();
|
||||
}
|
||||
|
||||
void TriggerLifecycleEvent(ServerLifecycleEvent event) const
|
||||
{
|
||||
|
|
@ -35,9 +40,9 @@ namespace lsp::providers
|
|||
}
|
||||
|
||||
private:
|
||||
scheduler::RequestScheduler* scheduler_;
|
||||
LifecycleCallback lifecycle_callback_;
|
||||
services::DocumentService* document_service_;
|
||||
scheduler::RequestScheduler& scheduler_;
|
||||
services::ServiceContainer& service_container_;
|
||||
};
|
||||
|
||||
// LSP请求提供者接口基类
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "../initialized/initialized_provider.hpp"
|
||||
#include "../text_document/did_open_provider.hpp"
|
||||
#include "../text_document/did_change_provider.hpp"
|
||||
#include "../text_document/did_close_provider.hpp"
|
||||
#include "../text_document/completion_provider.hpp"
|
||||
#include "../trace/set_trace_provider.hpp"
|
||||
#include "../shutdown/shutdown_provider.hpp"
|
||||
|
|
@ -21,6 +22,7 @@ namespace lsp::providers
|
|||
RegisterProvider<initialized::InitializedProvider>(dispatcher);
|
||||
RegisterProvider<text_document::DidOpenProvider>(dispatcher);
|
||||
RegisterProvider<text_document::DidChangeProvider>(dispatcher);
|
||||
RegisterProvider<text_document::DidCloseProvider>(dispatcher);
|
||||
RegisterProvider<text_document::CompletionProvider>(dispatcher);
|
||||
RegisterProvider<set_trace::SetTraceProvider>(dispatcher);
|
||||
RegisterProvider<shutdown::ShutdownProvider>(dispatcher);
|
||||
|
|
|
|||
|
|
@ -20,12 +20,11 @@ namespace lsp::providers::cancel_request
|
|||
std::string id_to_cancel = transform::debug::GetIdString(params.id);
|
||||
spdlog::info("Processing cancel request for ID: {}", id_to_cancel);
|
||||
|
||||
if (auto* scheduler = context.GetScheduler())
|
||||
{
|
||||
bool cancelled = scheduler->Cancel(id_to_cancel);
|
||||
auto& scheduler = context.GetScheduler();
|
||||
|
||||
bool cancelled = scheduler.Cancel(id_to_cancel);
|
||||
spdlog::info("Cancel request {} result: {}", id_to_cancel, cancelled ? "success" : "not found");
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
spdlog::error("Error handling cancel request: {}", e.what());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
#include <spdlog/spdlog.h>
|
||||
#include "./did_change_provider.hpp"
|
||||
#include "../../protocol/protocol.hpp"
|
||||
#include "../../protocol/transform/facade.hpp"
|
||||
#include "../../services/document.hpp"
|
||||
|
||||
namespace lsp::providers::text_document
|
||||
{
|
||||
|
|
@ -16,8 +19,12 @@ namespace lsp::providers::text_document
|
|||
|
||||
void DidChangeProvider::HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context)
|
||||
{
|
||||
static_cast<void>(context);
|
||||
spdlog::debug("DidChangeProvider: Providing response for method {}", notification.method);
|
||||
|
||||
protocol::DidChangeTextDocumentParams did_change_text_document_params = transform::As<protocol::DidChangeTextDocumentParams>(notification.params.value());
|
||||
|
||||
services::DocumentService& document_service = context.GetService<services::DocumentService>();
|
||||
document_service.UpdateDocument(did_change_text_document_params);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
#include <spdlog/spdlog.h>
|
||||
#include "./did_close_provider.hpp"
|
||||
#include "../../protocol/protocol.hpp"
|
||||
#include "../../protocol/transform/facade.hpp"
|
||||
#include "../../services/document.hpp"
|
||||
|
||||
namespace lsp::providers::text_document
|
||||
{
|
||||
|
||||
std::string DidCloseProvider::GetMethod() const
|
||||
{
|
||||
return "textDocument/didClose";
|
||||
}
|
||||
|
||||
std::string DidCloseProvider::GetProviderName() const
|
||||
{
|
||||
return "DidCloseProvider";
|
||||
}
|
||||
|
||||
void DidCloseProvider::HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context)
|
||||
{
|
||||
spdlog::debug("DidCloseProvider: Providing response for method {}", notification.method);
|
||||
|
||||
protocol::DidCloseTextDocumentParams did_close_text_document_params = transform::As<protocol::DidCloseTextDocumentParams>(notification.params.value());
|
||||
|
||||
services::DocumentService& document_service = context.GetService<services::DocumentService>();
|
||||
document_service.CloseDocument(did_close_text_document_params);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
#include "../base/provider_interface.hpp"
|
||||
|
||||
namespace lsp::providers::text_document
|
||||
{
|
||||
class DidCloseProvider : public INotificationProvider
|
||||
{
|
||||
public:
|
||||
DidCloseProvider() = default;
|
||||
std::string GetMethod() const override;
|
||||
std::string GetProviderName() const override;
|
||||
void HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context) override;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
#include <spdlog/spdlog.h>
|
||||
#include "./did_open_provider.hpp"
|
||||
#include "../../services/document.hpp"
|
||||
#include "../../protocol/transform/facade.hpp"
|
||||
|
||||
namespace lsp::providers::text_document
|
||||
{
|
||||
|
|
@ -15,8 +17,23 @@ namespace lsp::providers::text_document
|
|||
|
||||
void DidOpenProvider::HandleNotification(const protocol::NotificationMessage& notification, ExecutionContext& context)
|
||||
{
|
||||
static_cast<void>(context);
|
||||
spdlog::debug("DidOpenProvider: Providing response for method {}", notification.method);
|
||||
|
||||
protocol::DidOpenTextDocumentParams did_open_text_document_params = transform::As<protocol::DidOpenTextDocumentParams>(notification.params.value());
|
||||
|
||||
services::DocumentService& document_service = context.GetService<services::DocumentService>();
|
||||
document_service.OpenDocument(did_open_text_document_params);
|
||||
|
||||
/*
|
||||
if (auto* symbolService = context.TryGetService<SymbolService>()) {
|
||||
symbolService->parseDocument(uri, content);
|
||||
}
|
||||
|
||||
// 3. 触发诊断
|
||||
if (auto* diagnosticService = context.TryGetService<DiagnosticService>()) {
|
||||
diagnosticService->diagnose(uri);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,821 +1,112 @@
|
|||
#include <algorithm>
|
||||
#include "./document.hpp"
|
||||
|
||||
namespace lsp::services
|
||||
{
|
||||
// ===== Document 实现 =====
|
||||
|
||||
Document::Document(const protocol::TextDocumentItem& item) :
|
||||
item_(item), last_modified_time_(std::chrono::system_clock::now())
|
||||
void DocumentService::OpenDocument(const protocol::DidOpenTextDocumentParams& params)
|
||||
{
|
||||
UpdateInternalState();
|
||||
spdlog::trace("Created document: {} (version {}, {} bytes)", item_.uri, item_.version, item_.text.length());
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
documents_[params.textDocument.uri] = params.textDocument;
|
||||
}
|
||||
|
||||
void Document::SetContent(int32_t newVersion, const std::string& newText)
|
||||
void DocumentService::UpdateDocument(const protocol::DidChangeTextDocumentParams& params)
|
||||
{
|
||||
item_.version = newVersion;
|
||||
item_.text = newText;
|
||||
is_dirty_ = true;
|
||||
last_modified_time_ = std::chrono::system_clock::now();
|
||||
UpdateInternalState();
|
||||
|
||||
spdlog::trace("Document {} updated to version {} ({} bytes)", item_.uri, item_.version, item_.text.length());
|
||||
}
|
||||
|
||||
void Document::ApplyContentChange(protocol::integer version, const std::vector<protocol::TextDocumentContentChangeEvent>& changes)
|
||||
{
|
||||
// 应用所有变更
|
||||
for (const auto& change : changes)
|
||||
{
|
||||
ApplyContentChange(change);
|
||||
}
|
||||
|
||||
item_.version = version;
|
||||
is_dirty_ = true;
|
||||
last_modified_time_ = std::chrono::system_clock::now();
|
||||
UpdateInternalState();
|
||||
|
||||
spdlog::trace("Document {} updated to version {} with {} changes", item_.uri, item_.version, changes.size());
|
||||
}
|
||||
|
||||
void Document::ApplyContentChange(const protocol::TextDocumentContentChangeEvent& change)
|
||||
{
|
||||
// 增量更新
|
||||
size_t startOffset = PositionToOffset(change.range.start);
|
||||
size_t endOffset = PositionToOffset(change.range.end);
|
||||
|
||||
// 替换指定范围
|
||||
item_.text = item_.text.substr(0, startOffset) + change.text + item_.text.substr(endOffset);
|
||||
}
|
||||
|
||||
size_t Document::PositionToOffset(const protocol::Position& position) const
|
||||
{
|
||||
if (position.line >= lines_.size())
|
||||
{
|
||||
return item_.text.length();
|
||||
}
|
||||
|
||||
size_t offset = line_offsets_[position.line];
|
||||
|
||||
// 根据编码计算字符偏移
|
||||
if (encoding_ == protocol::PositionEncodingKindLiterals::UTF8)
|
||||
{
|
||||
// 直接使用字节偏移
|
||||
offset += std::min(static_cast<size_t>(position.character), lines_[position.line].length());
|
||||
}
|
||||
else
|
||||
{
|
||||
// UTF-16 或 UTF-32
|
||||
offset += CharacterToByteOffset(lines_[position.line], position.character);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
protocol::Position Document::OffsetToPosition(size_t offset) const
|
||||
{
|
||||
protocol::Position pos;
|
||||
pos.line = 0;
|
||||
pos.character = 0;
|
||||
|
||||
// 二分查找行号
|
||||
auto it = std::upper_bound(line_offsets_.begin(), line_offsets_.end(), offset);
|
||||
if (it != line_offsets_.begin())
|
||||
{
|
||||
--it;
|
||||
pos.line = static_cast<int32_t>(std::distance(line_offsets_.begin(), it));
|
||||
size_t lineOffset = *it;
|
||||
size_t byteOffset = offset - lineOffset;
|
||||
|
||||
// 根据编码计算字符位置
|
||||
if (encoding_ == protocol::PositionEncodingKindLiterals::UTF8)
|
||||
{
|
||||
pos.character = static_cast<int32_t>(byteOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
pos.character = ByteOffsetToCharacter(lines_[pos.line], byteOffset);
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
std::string Document::GetTextInRange(const protocol::Range& range) const
|
||||
{
|
||||
size_t start = PositionToOffset(range.start);
|
||||
size_t end = PositionToOffset(range.end);
|
||||
|
||||
if (start >= item_.text.length())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
end = std::min(end, item_.text.length());
|
||||
return item_.text.substr(start, end - start);
|
||||
}
|
||||
|
||||
std::optional<char> Document::GetCharAt(const protocol::Position& position) const
|
||||
{
|
||||
if (position.line >= lines_.size())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::string& line = lines_[position.line];
|
||||
size_t byteOffset = CharacterToByteOffset(line, position.character);
|
||||
|
||||
if (byteOffset >= line.length())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return line[byteOffset];
|
||||
}
|
||||
|
||||
std::string Document::GetLine(size_t lineNumber) const
|
||||
{
|
||||
if (lineNumber < lines_.size())
|
||||
{
|
||||
return lines_[lineNumber];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string Document::GetLineAt(const protocol::Position& position) const
|
||||
{
|
||||
return GetLine(position.line);
|
||||
}
|
||||
|
||||
std::string Document::GetWordAt(const protocol::Position& position) const
|
||||
{
|
||||
if (position.line >= lines_.size())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::string& line = lines_[position.line];
|
||||
size_t bytePos = CharacterToByteOffset(line, position.character);
|
||||
|
||||
// 找到单词边界
|
||||
size_t start = bytePos;
|
||||
while (start > 0 && IsWordChar(line[start - 1]))
|
||||
{
|
||||
--start;
|
||||
}
|
||||
|
||||
size_t end = bytePos;
|
||||
while (end < line.length() && IsWordChar(line[end]))
|
||||
{
|
||||
++end;
|
||||
}
|
||||
|
||||
return line.substr(start, end - start);
|
||||
}
|
||||
|
||||
protocol::Range Document::GetWordRangeAt(const protocol::Position& position) const
|
||||
{
|
||||
if (position.line >= lines_.size())
|
||||
{
|
||||
return protocol::Range{ position, position };
|
||||
}
|
||||
|
||||
const std::string& line = lines_[position.line];
|
||||
size_t bytePos = CharacterToByteOffset(line, position.character);
|
||||
|
||||
// 找到单词边界
|
||||
size_t start = bytePos;
|
||||
while (start > 0 && IsWordChar(line[start - 1]))
|
||||
{
|
||||
--start;
|
||||
}
|
||||
|
||||
size_t end = bytePos;
|
||||
while (end < line.length() && IsWordChar(line[end]))
|
||||
{
|
||||
++end;
|
||||
}
|
||||
|
||||
protocol::Range range;
|
||||
range.start.line = position.line;
|
||||
range.start.character = ByteOffsetToCharacter(line, start);
|
||||
range.end.line = position.line;
|
||||
range.end.character = ByteOffsetToCharacter(line, end);
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
void Document::UpdateInternalState()
|
||||
{
|
||||
UpdateLines();
|
||||
UpdateLineOffsets();
|
||||
}
|
||||
|
||||
void Document::UpdateLines()
|
||||
{
|
||||
lines_.clear();
|
||||
|
||||
size_t start = 0;
|
||||
for (size_t i = 0; i < item_.text.length(); ++i)
|
||||
{
|
||||
if (item_.text[i] == '\n')
|
||||
{
|
||||
lines_.push_back(item_.text.substr(start, i - start));
|
||||
start = i + 1;
|
||||
}
|
||||
else if (item_.text[i] == '\r')
|
||||
{
|
||||
if (i + 1 < item_.text.length() && item_.text[i + 1] == '\n')
|
||||
{
|
||||
lines_.push_back(item_.text.substr(start, i - start));
|
||||
start = i + 2;
|
||||
++i; // Skip \n
|
||||
}
|
||||
else
|
||||
{
|
||||
lines_.push_back(item_.text.substr(start, i - start));
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加最后一行
|
||||
if (start <= item_.text.length())
|
||||
{
|
||||
lines_.push_back(item_.text.substr(start));
|
||||
}
|
||||
}
|
||||
|
||||
void Document::UpdateLineOffsets()
|
||||
{
|
||||
line_offsets_.clear();
|
||||
line_offsets_.reserve(lines_.size() + 1);
|
||||
|
||||
size_t offset = 0;
|
||||
line_offsets_.push_back(0);
|
||||
|
||||
for (size_t i = 0; i < item_.text.length(); ++i)
|
||||
{
|
||||
if (item_.text[i] == '\n')
|
||||
{
|
||||
line_offsets_.push_back(i + 1);
|
||||
}
|
||||
else if (item_.text[i] == '\r')
|
||||
{
|
||||
if (i + 1 < item_.text.length() && item_.text[i + 1] == '\n')
|
||||
{
|
||||
line_offsets_.push_back(i + 2);
|
||||
++i;
|
||||
}
|
||||
else
|
||||
{
|
||||
line_offsets_.push_back(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Document::IsWordChar(char c) const
|
||||
{
|
||||
return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '$';
|
||||
}
|
||||
|
||||
size_t Document::CharacterToByteOffset(const std::string& line, int32_t character) const
|
||||
{
|
||||
if (encoding_ == protocol::PositionEncodingKindLiterals::UTF8)
|
||||
{
|
||||
return std::min(static_cast<size_t>(character), line.length());
|
||||
}
|
||||
|
||||
// UTF-16 编码:需要正确计算
|
||||
size_t byteOffset = 0;
|
||||
int32_t charCount = 0;
|
||||
|
||||
while (byteOffset < line.length() && charCount < character)
|
||||
{
|
||||
unsigned char c = line[byteOffset];
|
||||
|
||||
if (encoding_ == protocol::PositionEncodingKindLiterals::UTF16)
|
||||
{
|
||||
// UTF-16: 计算代码单元
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
// ASCII
|
||||
byteOffset += 1;
|
||||
charCount += 1;
|
||||
}
|
||||
else if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
// 2字节UTF-8 -> 1个UTF-16单元
|
||||
byteOffset += 2;
|
||||
charCount += 1;
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
// 3字节UTF-8 -> 1个UTF-16单元
|
||||
byteOffset += 3;
|
||||
charCount += 1;
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0)
|
||||
{
|
||||
// 4字节UTF-8 -> 2个UTF-16单元(代理对)
|
||||
byteOffset += 4;
|
||||
charCount += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
byteOffset += 1; // 错误情况
|
||||
}
|
||||
}
|
||||
else // UTF32
|
||||
{
|
||||
// UTF-32: 每个Unicode代码点算一个
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
byteOffset += 1;
|
||||
}
|
||||
else if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
byteOffset += 2;
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
byteOffset += 3;
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0)
|
||||
{
|
||||
byteOffset += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
byteOffset += 1;
|
||||
}
|
||||
charCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return byteOffset;
|
||||
}
|
||||
|
||||
int32_t Document::ByteOffsetToCharacter(const std::string& line, size_t byteOffset) const
|
||||
{
|
||||
if (encoding_ == protocol::PositionEncodingKindLiterals::UTF8)
|
||||
{
|
||||
return static_cast<int32_t>(byteOffset);
|
||||
}
|
||||
|
||||
int32_t charCount = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < byteOffset && pos < line.length())
|
||||
{
|
||||
unsigned char c = line[pos];
|
||||
|
||||
if (encoding_ == protocol::PositionEncodingKindLiterals::UTF16)
|
||||
{
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
pos += 1;
|
||||
charCount += 1;
|
||||
}
|
||||
else if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
pos += 2;
|
||||
charCount += 1;
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
pos += 3;
|
||||
charCount += 1;
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0)
|
||||
{
|
||||
pos += 4;
|
||||
charCount += 2; // 代理对
|
||||
}
|
||||
else
|
||||
{
|
||||
pos += 1;
|
||||
charCount += 1;
|
||||
}
|
||||
}
|
||||
else // UTF32
|
||||
{
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
pos += 1;
|
||||
}
|
||||
else if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
pos += 2;
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
pos += 3;
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0)
|
||||
{
|
||||
pos += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos += 1;
|
||||
}
|
||||
charCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return charCount;
|
||||
}
|
||||
|
||||
// ===== DocumentManager 实现 =====
|
||||
|
||||
void DocumentManager::DidOpenTextDocument(const protocol::DidOpenTextDocumentParams& params)
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
|
||||
// 检查文档大小
|
||||
if (config_.max_document_size > 0 &&
|
||||
params.textDocument.text.length() > config_.max_document_size)
|
||||
{
|
||||
spdlog::error("Document {} exceeds maximum size ({} > {})",
|
||||
params.textDocument.uri,
|
||||
params.textDocument.text.length(),
|
||||
config_.max_document_size);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建新文档
|
||||
auto doc = std::make_shared<Document>(params.textDocument);
|
||||
doc->SetEncoding(config_.default_encoding);
|
||||
|
||||
documents_[params.textDocument.uri] = doc;
|
||||
|
||||
spdlog::info("Opened document: {} (version {}, {} bytes, language: {})",
|
||||
params.textDocument.uri,
|
||||
params.textDocument.version,
|
||||
params.textDocument.text.length(),
|
||||
params.textDocument.languageId);
|
||||
}
|
||||
|
||||
void DocumentManager::DidChangeTextDocument(const protocol::DidChangeTextDocumentParams& params)
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = documents_.find(params.textDocument.uri);
|
||||
if (it == documents_.end())
|
||||
{
|
||||
spdlog::error("Attempt to change non-existent document: {}",
|
||||
params.textDocument.uri);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& doc = it->second;
|
||||
|
||||
// 版本检查
|
||||
if (params.textDocument.version)
|
||||
{
|
||||
protocol::integer expectedVersion = params.textDocument.version;
|
||||
if (expectedVersion <= doc->GetVersion())
|
||||
{
|
||||
spdlog::warn("Ignoring stale change for {}: version {} <= current {}",
|
||||
params.textDocument.uri,
|
||||
expectedVersion,
|
||||
doc->GetVersion());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 应用变更
|
||||
if (params.contentChanges.empty())
|
||||
{
|
||||
spdlog::warn("Empty content changes for document: {}", params.textDocument.uri);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是全文还是增量
|
||||
if (params.contentChanges.size() == 1)
|
||||
{
|
||||
// 全文更新
|
||||
// doc->SetContent(params.textDocument.version(doc->GetVersion() + 1), params.contentChanges[0].text);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 增量更新
|
||||
// doc->ApplyContentChanges( params.textDocument.version(doc->GetVersion() + 1), params.contentChanges);
|
||||
}
|
||||
|
||||
spdlog::debug("Changed document: {} to version {} ({} changes)", params.textDocument.uri, doc->GetVersion(), params.contentChanges.size());
|
||||
}
|
||||
|
||||
void DocumentManager::DidCloseTextDocument(const protocol::DidCloseTextDocumentParams& params)
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
|
||||
auto it = documents_.find(params.textDocument.uri);
|
||||
if (it == documents_.end())
|
||||
{
|
||||
spdlog::warn("Attempt to close non-existent document: {}",
|
||||
params.textDocument.uri);
|
||||
return;
|
||||
}
|
||||
|
||||
documents_.erase(it);
|
||||
spdlog::info("Closed document: {}", params.textDocument.uri);
|
||||
}
|
||||
|
||||
void DocumentManager::DidSaveTextDocument(const protocol::DidSaveTextDocumentParams& params)
|
||||
{
|
||||
std::shared_lock lock(mutex_);
|
||||
|
||||
auto it = documents_.find(params.textDocument.uri);
|
||||
if (it == documents_.end())
|
||||
{
|
||||
spdlog::warn("Attempt to save non-existent document: {}",
|
||||
params.textDocument.uri);
|
||||
return;
|
||||
}
|
||||
|
||||
it->second->SetDirty(false);
|
||||
|
||||
// 如果保存通知包含文本,可以验证同步状态
|
||||
if (params.text.has_value())
|
||||
{
|
||||
if (params.text.value() != it->second->GetText())
|
||||
{
|
||||
spdlog::error("Document content mismatch on save for: {}", params.textDocument.uri);
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::info("Saved document: {}", params.textDocument.uri);
|
||||
}
|
||||
|
||||
std::shared_ptr<Document> DocumentManager::GetDocument(const std::string& uri) const
|
||||
{
|
||||
std::shared_lock lock(mutex_);
|
||||
|
||||
auto it = documents_.find(uri);
|
||||
if (it != documents_.end())
|
||||
{
|
||||
return it->second;
|
||||
for (const auto& change : params.contentChanges)
|
||||
{
|
||||
if (IsFullDocumentUpdate(change, it->second.text))
|
||||
it->second.text = change.text;
|
||||
else
|
||||
ApplyIncrementalChange(it->second.text, change);
|
||||
// 更新文本
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string> DocumentManager::GetAllUris() const
|
||||
{
|
||||
std::shared_lock lock(mutex_);
|
||||
|
||||
std::vector<std::string> uris;
|
||||
uris.reserve(documents_.size());
|
||||
|
||||
for (const auto& [uri, doc] : documents_)
|
||||
{
|
||||
uris.push_back(uri);
|
||||
}
|
||||
|
||||
return uris;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Document>> DocumentManager::GetAllDocuments() const
|
||||
{
|
||||
std::shared_lock lock(mutex_);
|
||||
|
||||
std::vector<std::shared_ptr<Document>> docs;
|
||||
docs.reserve(documents_.size());
|
||||
|
||||
for (const auto& [uri, doc] : documents_)
|
||||
{
|
||||
docs.push_back(doc);
|
||||
}
|
||||
|
||||
return docs;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Document>> DocumentManager::GetDocumentsByLanguage(
|
||||
const std::string& languageId) const
|
||||
{
|
||||
std::shared_lock lock(mutex_);
|
||||
|
||||
std::vector<std::shared_ptr<Document>> docs;
|
||||
|
||||
for (const auto& [uri, doc] : documents_)
|
||||
{
|
||||
if (doc->GetLanguageId() == languageId)
|
||||
{
|
||||
docs.push_back(doc);
|
||||
if (params.textDocument.version.has_value())
|
||||
it->second.version = params.textDocument.version.value();
|
||||
}
|
||||
}
|
||||
|
||||
return docs;
|
||||
void DocumentService::CloseDocument(const protocol::DidCloseTextDocumentParams& params)
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
documents_.erase(params.textDocument.uri);
|
||||
}
|
||||
|
||||
bool DocumentManager::HasDocument(const std::string& uri) const
|
||||
void DocumentService::ApplyIncrementalChange(protocol::string& content, const protocol::TextDocumentContentChangeEvent& change)
|
||||
{
|
||||
std::shared_lock lock(mutex_);
|
||||
return documents_.find(uri) != documents_.end();
|
||||
protocol::uinteger start_offset = PositionToOffset(content, change.range.start);
|
||||
protocol::uinteger end_offset = PositionToOffset(content, change.range.end);
|
||||
|
||||
if (start_offset > content.length() || end_offset > content.length() || start_offset > end_offset)
|
||||
spdlog::error("Invalid range for text edit: start={}, end={}, length={}", start_offset, end_offset, content.length());
|
||||
else
|
||||
content.replace(start_offset, end_offset - start_offset, change.text);
|
||||
}
|
||||
|
||||
size_t DocumentManager::GetDocumentCount() const
|
||||
bool DocumentService::IsFullDocumentUpdate(const protocol::TextDocumentContentChangeEvent& change, const protocol::string& current_content)
|
||||
{
|
||||
std::shared_lock lock(mutex_);
|
||||
return documents_.size();
|
||||
}
|
||||
|
||||
std::vector<std::string> DocumentManager::GetDirtyDocuments() const
|
||||
{
|
||||
std::shared_lock lock(mutex_);
|
||||
|
||||
std::vector<std::string> dirtyUris;
|
||||
|
||||
for (const auto& [uri, doc] : documents_)
|
||||
{
|
||||
if (doc->IsDirty())
|
||||
{
|
||||
dirtyUris.push_back(uri);
|
||||
}
|
||||
}
|
||||
|
||||
return dirtyUris;
|
||||
}
|
||||
|
||||
std::string DocumentManager::ResolveUri(const std::string& uri) const
|
||||
{
|
||||
// 如果已经是绝对URI,直接返回
|
||||
if (utils::IsFileUri(uri))
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
|
||||
// 尝试相对于工作区文件夹解析
|
||||
for (const auto& folder : workspace_folders_)
|
||||
{
|
||||
std::string folderPath = utils::UriToPath(folder.uri);
|
||||
std::string resolvedPath = folderPath + "/" + uri;
|
||||
|
||||
// 检查文件是否存在(这里简化处理)
|
||||
return utils::PathToUri(resolvedPath);
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
// ===== 工具函数实现 =====
|
||||
|
||||
namespace utils
|
||||
{
|
||||
std::string NormalizeUri(const std::string& uri)
|
||||
{
|
||||
std::string normalized = uri;
|
||||
|
||||
// 确保使用正斜杠
|
||||
std::replace(normalized.begin(), normalized.end(), '\\', '/');
|
||||
|
||||
// 移除重复的斜杠
|
||||
auto newEnd = std::unique(normalized.begin(), normalized.end(), [](char a, char b) { return a == '/' && b == '/'; });
|
||||
normalized.erase(newEnd, normalized.end());
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
std::string UriToPath(const std::string& uri)
|
||||
{
|
||||
if (uri.substr(0, 7) == "file://")
|
||||
{
|
||||
std::string path = uri.substr(7);
|
||||
|
||||
// Windows路径处理
|
||||
#ifdef _WIN32
|
||||
if (path.length() >= 3 && path[0] == '/' &&
|
||||
std::isalpha(path[1]) && path[2] == ':')
|
||||
{
|
||||
path = path.substr(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
std::string PathToUri(const std::string& path)
|
||||
{
|
||||
std::string uri = "file://";
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows路径
|
||||
if (path.length() >= 2 && std::isalpha(path[0]) && path[1] == ':')
|
||||
{
|
||||
uri += "/";
|
||||
}
|
||||
#endif
|
||||
|
||||
uri += path;
|
||||
return NormalizeUri(uri);
|
||||
}
|
||||
|
||||
bool IsFileUri(const std::string& uri)
|
||||
{
|
||||
return uri.substr(0, 7) == "file://";
|
||||
}
|
||||
|
||||
protocol::TextEdit CreateReplace(const protocol::Range& range, const std::string& newText)
|
||||
{
|
||||
protocol::TextEdit edit;
|
||||
edit.range = range;
|
||||
edit.newText = newText;
|
||||
return edit;
|
||||
}
|
||||
|
||||
protocol::TextEdit CreateInsert(const protocol::Position& position, const std::string& text)
|
||||
{
|
||||
return CreateReplace(protocol::Range{ position, position }, text);
|
||||
}
|
||||
|
||||
protocol::TextEdit CreateDelete(const protocol::Range& range)
|
||||
{
|
||||
return CreateReplace(range, "");
|
||||
}
|
||||
|
||||
bool IsPositionInRange(const protocol::Position& position, const protocol::Range& range)
|
||||
{
|
||||
if (position.line < range.start.line || position.line > range.end.line)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (position.line == range.start.line && position.character < range.start.character)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (position.line == range.end.line && position.character >= range.end.character)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (change.range.start.line == 0 && change.range.start.character == 0)
|
||||
return true;
|
||||
|
||||
if (change.range.start.line == 0 && change.range.start.character == 0 && change.range.end.line == 0 && change.range.end.character == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsRangeOverlapping(const protocol::Range& a, const protocol::Range& b)
|
||||
protocol::uinteger DocumentService::PositionToOffset(const protocol::string& content, const protocol::Position& position)
|
||||
{
|
||||
return !(a.end.line < b.start.line ||
|
||||
(a.end.line == b.start.line && a.end.character <= b.start.character) ||
|
||||
b.end.line < a.start.line ||
|
||||
(b.end.line == a.start.line && b.end.character <= a.start.character));
|
||||
}
|
||||
protocol::uinteger offset = 0;
|
||||
protocol::uinteger current_line = 0;
|
||||
|
||||
protocol::Range ExtendRange(const protocol::Range& range, int32_t lines)
|
||||
// 找到目标行
|
||||
while (offset < content.length() && current_line < position.line)
|
||||
{
|
||||
protocol::Range extended = range;
|
||||
extended.start.line = std::max(static_cast<std::int32_t>(0), static_cast<std::int32_t>(extended.start.line - lines));
|
||||
extended.end.line += lines;
|
||||
return extended;
|
||||
if (content[offset] == '\n')
|
||||
current_line++;
|
||||
offset++;
|
||||
}
|
||||
|
||||
std::string ApplyTextEdits(const std::string& text,
|
||||
const std::vector<protocol::TextEdit>& edits)
|
||||
if (offset >= content.length())
|
||||
return content.length();
|
||||
|
||||
protocol::uinteger current_char = 0;
|
||||
|
||||
while (offset < content.length() && current_char < position.character)
|
||||
{
|
||||
if (edits.empty())
|
||||
if (content[offset] == '\n')
|
||||
break;
|
||||
// UTF-8字符边界检测
|
||||
unsigned char ch = static_cast<unsigned char>(content[offset]);
|
||||
if ((ch & 0x80) == 0)
|
||||
{
|
||||
return text;
|
||||
// ASCII (1 byte)
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
// 排序编辑(从后向前,避免偏移问题)
|
||||
std::vector<protocol::TextEdit> sortedEdits = edits;
|
||||
std::sort(sortedEdits.begin(), sortedEdits.end(), [](const protocol::TextEdit& a, const protocol::TextEdit& b) {
|
||||
if (a.range.start.line != b.range.start.line)
|
||||
else if ((ch & 0xE0) == 0xC0)
|
||||
{
|
||||
return a.range.start.line > b.range.start.line;
|
||||
// 2-byte UTF-8
|
||||
offset += 2;
|
||||
}
|
||||
return a.range.start.character > b.range.start.character;
|
||||
});
|
||||
|
||||
// 创建临时文档来应用编辑
|
||||
protocol::TextDocumentItem tempItem;
|
||||
tempItem.uri = "temp://";
|
||||
tempItem.languageId = "";
|
||||
tempItem.version = 0;
|
||||
tempItem.text = text;
|
||||
|
||||
Document tempDoc(tempItem);
|
||||
std::string result = text;
|
||||
|
||||
for (const auto& edit : sortedEdits)
|
||||
else if ((ch & 0xF0) == 0xE0)
|
||||
{
|
||||
size_t start = tempDoc.PositionToOffset(edit.range.start);
|
||||
size_t end = tempDoc.PositionToOffset(edit.range.end);
|
||||
|
||||
result = result.substr(0, start) + edit.newText + result.substr(end);
|
||||
|
||||
// 更新临时文档
|
||||
tempDoc.SetContent(0, result);
|
||||
// 3-byte UTF-8
|
||||
offset += 3;
|
||||
}
|
||||
else if ((ch & 0xF8) == 0xF0)
|
||||
{
|
||||
// 4-byte UTF-8
|
||||
offset += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 错误的UTF-8序列,跳过一个字节
|
||||
offset += 1;
|
||||
}
|
||||
current_char++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return std::min(offset, static_cast<protocol::uinteger>(content.length()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,238 +1,31 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
#include <shared_mutex>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include "../protocol/protocol.hpp"
|
||||
|
||||
namespace lsp::services
|
||||
{
|
||||
class Document
|
||||
class DocumentService
|
||||
{
|
||||
public:
|
||||
Document(const protocol::TextDocumentItem& item);
|
||||
DocumentService() = default;
|
||||
~DocumentService() = default;
|
||||
|
||||
const protocol::DocumentUri& GetUri() const { return item_.uri; }
|
||||
const protocol::string& GetLanguageId() const { return item_.languageId; }
|
||||
protocol::integer GetVersion() const { return item_.version; }
|
||||
const protocol::string& GetText() const { return item_.text; }
|
||||
const protocol::TextDocumentItem& GetItem() const { return item_; }
|
||||
void OpenDocument(const protocol::DidOpenTextDocumentParams& params);
|
||||
void UpdateDocument(const protocol::DidChangeTextDocumentParams& params);
|
||||
void CloseDocument(const protocol::DidCloseTextDocumentParams& params);
|
||||
|
||||
protocol::TextDocumentIdentifier GetIdentifier() const
|
||||
{
|
||||
return protocol::TextDocumentIdentifier{item_.uri};
|
||||
}
|
||||
|
||||
protocol::VersionedTextDocumentIdentifier GetVersionedIdentifier() const
|
||||
{
|
||||
protocol::VersionedTextDocumentIdentifier id;
|
||||
id.uri = item_.uri;
|
||||
id.version = item_.version;
|
||||
return id;
|
||||
}
|
||||
|
||||
// ===== 位置和范围操作 =====
|
||||
size_t PositionToOffset(const protocol::Position& position) const;
|
||||
protocol::Position OffsetToPosition(size_t offset) const;
|
||||
std::string GetTextInRange(const protocol::Range& range) const;
|
||||
|
||||
// 内容更新
|
||||
void SetContent(protocol::integer version, const protocol::string& new_text);
|
||||
void ApplyContentChange(protocol::integer version, const std::vector<protocol::TextDocumentContentChangeEvent>& changes);
|
||||
|
||||
// 获取指定位置的字符
|
||||
std::optional<char> GetCharAt(const protocol::Position& position) const;
|
||||
|
||||
// ===== 行操作 =====
|
||||
const std::vector<std::string>& GetLines() const { return lines_; }
|
||||
size_t GetLineCount() const { return lines_.size(); }
|
||||
std::string GetLine(size_t lineNumber) const;
|
||||
std::string GetLineAt(const protocol::Position& position) const;
|
||||
|
||||
// ===== 单词和符号操作 =====
|
||||
std::string GetWordAt(const protocol::Position& position) const;
|
||||
protocol::Range GetWordRangeAt(const protocol::Position& position) const;
|
||||
|
||||
// ===== 实用方法 =====
|
||||
// 创建一个Location
|
||||
protocol::Location CreateLocation(const protocol::Range& range) const
|
||||
{
|
||||
return protocol::Location{item_.uri, range};
|
||||
}
|
||||
|
||||
// 创建一个TextDocumentPositionParams
|
||||
protocol::TextDocumentPositionParams CreatePositionParams(const protocol::Position& position) const
|
||||
{
|
||||
protocol::TextDocumentPositionParams params;
|
||||
params.textDocument = GetIdentifier();
|
||||
params.position = position;
|
||||
return params;
|
||||
}
|
||||
|
||||
// ===== 元数据 =====
|
||||
// 文档是否被修改(相对于上次保存)
|
||||
bool IsDirty() const { return is_dirty_; }
|
||||
void SetDirty(bool dirty) { is_dirty_ = dirty; }
|
||||
|
||||
// 最后修改时间
|
||||
std::chrono::system_clock::time_point GetLastModified() const { return last_modified_time_; }
|
||||
|
||||
void SetEncoding(protocol::PositionEncodingKind encoding) { encoding_ = encoding; }
|
||||
protocol::PositionEncodingKind GetEncoding() const { return encoding_; }
|
||||
std::optional<protocol::string> GetContent(const protocol::string& uri) const;
|
||||
|
||||
private:
|
||||
// 更新内部缓存
|
||||
void UpdateInternalState();
|
||||
void UpdateLines();
|
||||
void UpdateLineOffsets();
|
||||
void ApplyIncrementalChange(protocol::string& content, const protocol::TextDocumentContentChangeEvent& change);
|
||||
|
||||
// 辅助方法
|
||||
bool IsWordChar(char c) const;
|
||||
size_t CharacterToByteOffset(const std::string& line, std::int32_t character) const;
|
||||
std::int32_t ByteOffsetToCharacter(const std::string& line, size_t byteOffset) const;
|
||||
|
||||
// 应用单个内容变更
|
||||
void ApplyContentChange(const protocol::TextDocumentContentChangeEvent& change);
|
||||
|
||||
private:
|
||||
protocol::TextDocumentItem item_;
|
||||
|
||||
// 缓存行的信息
|
||||
std::vector<std::string> lines_;
|
||||
std::vector<size_t> line_offsets_;
|
||||
|
||||
bool is_dirty_ = false;
|
||||
std::chrono::system_clock::time_point last_modified_time_;
|
||||
protocol::PositionEncodingKind encoding_ = protocol::PositionEncodingKindLiterals::UTF16;
|
||||
};
|
||||
|
||||
/**
|
||||
* 文档管理器 - 使用protocol类型作为接口
|
||||
*/
|
||||
class DocumentManager
|
||||
{
|
||||
public:
|
||||
DocumentManager() = default;
|
||||
~DocumentManager() = default;
|
||||
|
||||
// 禁止拷贝
|
||||
DocumentManager(const DocumentManager&) = delete;
|
||||
DocumentManager& operator=(const DocumentManager&) = delete;
|
||||
|
||||
// ===== 文档生命周期管理 - 直接使用protocol类型 =====
|
||||
|
||||
// 处理 textDocument/didOpen
|
||||
void DidOpenTextDocument(const protocol::DidOpenTextDocumentParams& params);
|
||||
|
||||
// 处理 textDocument/didChange
|
||||
void DidChangeTextDocument(const protocol::DidChangeTextDocumentParams& params);
|
||||
|
||||
// 处理 textDocument/didClose
|
||||
void DidCloseTextDocument(const protocol::DidCloseTextDocumentParams& params);
|
||||
|
||||
// 处理 textDocument/didSave
|
||||
void DidSaveTextDocument(const protocol::DidSaveTextDocumentParams& params);
|
||||
|
||||
// ===== 文档访问 - 支持多种查询方式 =====
|
||||
|
||||
// 通过URI获取
|
||||
std::shared_ptr<Document> GetDocument(const std::string& uri) const;
|
||||
|
||||
// 通过标识符获取
|
||||
std::shared_ptr<Document> GetDocument(const protocol::TextDocumentIdentifier& identifier) const
|
||||
{
|
||||
return GetDocument(identifier.uri);
|
||||
}
|
||||
|
||||
// 通过版本化标识符获取
|
||||
std::shared_ptr<Document> GetDocument(const protocol::VersionedTextDocumentIdentifier& identifier) const
|
||||
{
|
||||
auto doc = GetDocument(identifier.uri);
|
||||
if (doc && identifier.version && doc->GetVersion() != identifier.version)
|
||||
{
|
||||
spdlog::warn("Version mismatch for {}: expected {}, got {}", identifier.uri, identifier.version, doc->GetVersion());
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
// 通过TextDocumentPositionParams获取
|
||||
std::shared_ptr<Document> GetDocument(const protocol::TextDocumentPositionParams& params) const
|
||||
{
|
||||
return GetDocument(params.textDocument);
|
||||
}
|
||||
|
||||
// ===== 批量操作 =====
|
||||
std::vector<std::string> GetAllUris() const;
|
||||
std::vector<std::shared_ptr<Document>> GetAllDocuments() const;
|
||||
std::vector<std::shared_ptr<Document>> GetDocumentsByLanguage(const std::string& languageId) const;
|
||||
|
||||
// ===== 查询 =====
|
||||
bool HasDocument(const std::string& uri) const;
|
||||
bool IsDocumentOpen(const protocol::TextDocumentIdentifier& identifier) const
|
||||
{
|
||||
return HasDocument(identifier.uri);
|
||||
}
|
||||
|
||||
size_t GetDocumentCount() const;
|
||||
|
||||
// ===== 诊断支持 =====
|
||||
// 获取需要诊断的文档(已修改的)
|
||||
std::vector<std::string> GetDirtyDocuments() const;
|
||||
|
||||
// ===== 工作区支持 =====
|
||||
// 设置工作区文件夹(用于相对路径解析)
|
||||
void SetWorkspaceFolders(const std::vector<protocol::WorkspaceFolder>& folders)
|
||||
{
|
||||
workspace_folders_ = folders;
|
||||
}
|
||||
|
||||
const std::vector<protocol::WorkspaceFolder>& GetWorkspaceFolders() const
|
||||
{
|
||||
return workspace_folders_;
|
||||
}
|
||||
|
||||
// 解析相对URI
|
||||
std::string ResolveUri(const std::string& uri) const;
|
||||
|
||||
// ===== 配置 =====
|
||||
struct Configuration {
|
||||
size_t max_document_size = 10 * 1024 * 1024; // 10MB
|
||||
bool validate_utf8 = true;
|
||||
protocol::PositionEncodingKind default_encoding = protocol::PositionEncodingKindLiterals::UTF16;
|
||||
};
|
||||
|
||||
void SetConfiguration(const Configuration& config) { config_ = config; }
|
||||
const Configuration& GetConfiguration() const { return config_; }
|
||||
static bool IsFullDocumentUpdate(const protocol::TextDocumentContentChangeEvent& change, const protocol::string& current_content);
|
||||
static protocol::uinteger PositionToOffset(const protocol::string& content, const protocol::Position& position);
|
||||
|
||||
private:
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::unordered_map<std::string, std::shared_ptr<Document>> documents_;
|
||||
std::vector<protocol::WorkspaceFolder> workspace_folders_;
|
||||
Configuration config_;
|
||||
std::unordered_map<protocol::string, protocol::TextDocumentItem> documents_;
|
||||
};
|
||||
|
||||
// ===== 工具函数 =====
|
||||
namespace utils
|
||||
{
|
||||
// URI处理
|
||||
std::string NormalizeUri(const std::string& uri);
|
||||
std::string UriToPath(const std::string& uri);
|
||||
std::string PathToUri(const std::string& path);
|
||||
bool IsFileUri(const std::string& uri);
|
||||
|
||||
// 创建TextEdit
|
||||
protocol::TextEdit CreateReplace(const protocol::Range& range, const std::string& newText);
|
||||
protocol::TextEdit CreateInsert(const protocol::Position& position, const std::string& text);
|
||||
protocol::TextEdit CreateDelete(const protocol::Range& range);
|
||||
|
||||
// 范围操作
|
||||
bool IsPositionInRange(const protocol::Position& position, const protocol::Range& range);
|
||||
bool IsRangeOverlapping(const protocol::Range& a, const protocol::Range& b);
|
||||
protocol::Range ExtendRange(const protocol::Range& range, int32_t lines);
|
||||
|
||||
// 应用编辑
|
||||
std::string ApplyTextEdits(const std::string& text, const std::vector<protocol::TextEdit>& edits);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <any>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <typeindex>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace lsp::services
|
||||
{
|
||||
class ServiceContainer
|
||||
{
|
||||
public:
|
||||
// 注册服务
|
||||
template<typename T>
|
||||
void RegisterService(std::shared_ptr<T> service)
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
if (!service)
|
||||
throw std::invalid_argument("Cannot register null service");
|
||||
services_[std::type_index(typeid(T))] = service;
|
||||
|
||||
spdlog::info("Registered service '{}' ", std::type_index(typeid(T)).name());
|
||||
}
|
||||
|
||||
// 获取服务
|
||||
template<typename T>
|
||||
T& Get() const
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
auto it = services_.find(std::type_index(typeid(T)));
|
||||
if (it != services_.end())
|
||||
return *std::any_cast<std::shared_ptr<T>>(it->second);
|
||||
throw std::runtime_error(std::string("Service not found:") + typeid(T).name());
|
||||
}
|
||||
private:
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::unordered_map<std::type_index, std::any> services_;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
#include <sstream>
|
||||
#include <regex>
|
||||
#include <algorithm>
|
||||
#include "symbol.hpp"
|
||||
|
||||
namespace lsp::services::symbol
|
||||
{
|
||||
|
||||
void SymbolService::UpdateDocument(const std::string& uri, const std::string& content)
|
||||
{
|
||||
// 从URI提取文件名作为unit名称
|
||||
std::string unit_name = uri.substr(uri.find_last_of("/\\") + 1);
|
||||
if (unit_name.find(".tsf") != std::string::npos)
|
||||
{
|
||||
unit_name = unit_name.substr(0, unit_name.find(".tsf"));
|
||||
}
|
||||
|
||||
// 解析符号
|
||||
auto symbols = ParseTsfUnit(content, unit_name);
|
||||
|
||||
// 更新文档符号
|
||||
document_symbols_[uri] = symbols;
|
||||
|
||||
// 更新unit导出符号(只包含public符号)
|
||||
std::vector<UnitSymbol> public_symbols;
|
||||
std::copy_if(symbols.begin(), symbols.end(), std::back_inserter(public_symbols), [](const UnitSymbol& s) { return s.is_public; });
|
||||
|
||||
if (!public_symbols.empty())
|
||||
{
|
||||
unit_exports_[unit_name] = public_symbols;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<protocol::DocumentSymbol> SymbolService::GetDocumentSymbols(const std::string& uri) const
|
||||
{
|
||||
std::vector<protocol::DocumentSymbol> result;
|
||||
|
||||
auto it = document_symbols_.find(uri);
|
||||
if (it != document_symbols_.end())
|
||||
{
|
||||
for (const auto& info : it->second)
|
||||
{
|
||||
result.push_back(info.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<UnitSymbol> SymbolService::GetCompletionSymbols(const std::string& uri) const
|
||||
{
|
||||
std::vector<UnitSymbol> result;
|
||||
|
||||
// TODO: 这里应该解析uses语句,确定哪些unit被导入
|
||||
// 现在简单返回所有public符号
|
||||
for (const auto& [unit_name, symbols] : unit_exports_)
|
||||
{
|
||||
result.insert(result.end(), symbols.begin(), symbols.end());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SymbolService::RemoveDocument(const std::string& uri)
|
||||
{
|
||||
document_symbols_.erase(uri);
|
||||
}
|
||||
|
||||
std::vector<UnitSymbol> SymbolService::ParseTsfUnit(const std::string& content, const std::string& unit_name)
|
||||
{
|
||||
std::vector<UnitSymbol> symbols;
|
||||
std::istringstream stream(content);
|
||||
std::string line;
|
||||
protocol::uinteger line_number = 0;
|
||||
bool in_interface = false;
|
||||
bool in_implementation = false;
|
||||
|
||||
while (std::getline(stream, line))
|
||||
{
|
||||
std::string trimmed_line = trim(line);
|
||||
|
||||
// 跟踪section
|
||||
if (trimmed_line == "interface")
|
||||
{
|
||||
in_interface = true;
|
||||
in_implementation = false;
|
||||
}
|
||||
else if (trimmed_line == "implementation")
|
||||
{
|
||||
in_interface = false;
|
||||
in_implementation = true;
|
||||
}
|
||||
|
||||
// 解析符号
|
||||
if (!trimmed_line.empty() && (in_interface || in_implementation))
|
||||
{
|
||||
// 解析常量
|
||||
if (trimmed_line.find("const ") == 0)
|
||||
{
|
||||
std::regex constRegex(R"(const\s+(\w+)\s*=\s*(.+);?)");
|
||||
std::smatch match;
|
||||
if (std::regex_search(trimmed_line, match, constRegex))
|
||||
{
|
||||
UnitSymbol info;
|
||||
info.symbol.name = match[1];
|
||||
info.symbol.detail = "= " + std::string(match[2]);
|
||||
info.symbol.kind = protocol::SymbolKind::kConstant;
|
||||
info.symbol.range = { { line_number, 0 }, { line_number, static_cast<protocol::uinteger>(line.length()) } };
|
||||
info.symbol.selectionRange = { { line_number, static_cast<protocol::uinteger>(line.find(match[1])) },
|
||||
{ line_number, static_cast<protocol::uinteger>(line.find(match[1]) + match[1].length()) } };
|
||||
info.signature = trimmed_line;
|
||||
info.unit_name = unit_name;
|
||||
info.is_public = in_interface;
|
||||
symbols.push_back(info);
|
||||
}
|
||||
}
|
||||
// 解析函数
|
||||
else if (trimmed_line.find("function ") == 0)
|
||||
{
|
||||
std::regex funcRegex(R"(function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*(\w+))?;?)");
|
||||
std::smatch match;
|
||||
if (std::regex_search(trimmed_line, match, funcRegex))
|
||||
{
|
||||
UnitSymbol info;
|
||||
info.symbol.name = match[1];
|
||||
info.symbol.kind = protocol::SymbolKind::kFunction;
|
||||
|
||||
// 构建函数签名
|
||||
std::string params = match[2];
|
||||
std::string returnType = match[3];
|
||||
info.symbol.detail = "(" + params + ")";
|
||||
if (!returnType.empty())
|
||||
{
|
||||
info.symbol.detail = info.symbol.detail.value() + " : " + returnType;
|
||||
}
|
||||
|
||||
info.symbol.range = { { line_number, 0 }, { line_number, static_cast<protocol::uinteger>(line.length()) } };
|
||||
info.symbol.selectionRange = { { line_number, static_cast<protocol::uinteger>(line.find(match[1])) },
|
||||
{ line_number, static_cast<protocol::uinteger>(line.find(match[1]) + match[1].length()) } };
|
||||
info.signature = trimmed_line;
|
||||
info.unit_name = unit_name;
|
||||
info.is_public = in_interface;
|
||||
symbols.push_back(info);
|
||||
}
|
||||
}
|
||||
// 解析类型定义
|
||||
else if (trimmed_line.find("type ") == 0)
|
||||
{
|
||||
std::regex typeRegex(R"(type\s+(\w+)\s*=\s*class)");
|
||||
std::smatch match;
|
||||
if (std::regex_search(trimmed_line, match, typeRegex))
|
||||
{
|
||||
UnitSymbol info;
|
||||
info.symbol.name = match[1];
|
||||
info.symbol.kind = protocol::SymbolKind::kClass;
|
||||
info.symbol.detail = "class";
|
||||
info.symbol.range = { { line_number, 0 }, { line_number, static_cast<protocol::uinteger>(line.length()) } };
|
||||
info.symbol.selectionRange = { { line_number, static_cast<protocol::uinteger>(line.find(match[1])) },
|
||||
{ line_number, static_cast<protocol::uinteger>(line.find(match[1]) + match[1].length()) } };
|
||||
info.signature = trimmed_line;
|
||||
info.unit_name = unit_name;
|
||||
info.is_public = in_interface;
|
||||
|
||||
// TODO: 解析类成员作为children
|
||||
|
||||
symbols.push_back(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line_number++;
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
std::string SymbolService::trim(const std::string& str)
|
||||
{
|
||||
size_t first = str.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos)
|
||||
return "";
|
||||
size_t last = str.find_last_not_of(" \t\r\n");
|
||||
return str.substr(first, (last - first + 1));
|
||||
}
|
||||
|
||||
} // namespace lsp::services
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "../protocol/protocol.hpp"
|
||||
|
||||
namespace lsp::services::symbol
|
||||
{
|
||||
struct UnitSymbol
|
||||
{
|
||||
protocol::DocumentSymbol symbol;
|
||||
std::string signature;
|
||||
std::string unit_name;
|
||||
bool is_public;
|
||||
};
|
||||
|
||||
class SymbolService
|
||||
{
|
||||
public:
|
||||
void UpdateDocument(const std::string& uri, const std::string& content);
|
||||
void RemoveDocument(const std::string& uri);
|
||||
std::vector<protocol::DocumentSymbol> GetDocumentSymbols(const std::string& uri) const;
|
||||
std::vector<UnitSymbol> GetCompletionSymbols(const std::string& uri) const;
|
||||
|
||||
private:
|
||||
std::vector<UnitSymbol> ParseTsfUnit(const std::string& content, const std::string& unit_name);
|
||||
protocol::SymbolKind GetSymbolKind(const std::string& line);
|
||||
std::string trim(const std::string& str);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::vector<UnitSymbol>> document_symbols_;
|
||||
std::unordered_map<std::string, std::vector<UnitSymbol>> unit_exports_;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "textDocument/completion",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///home/cobia/test_lsp/1.tsl"
|
||||
},
|
||||
"position": {
|
||||
"line": 2,
|
||||
"character": 5
|
||||
},
|
||||
"context": {
|
||||
"triggerKind": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "textDocument/didChange",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///home/cobia/test_lsp/1.tsl",
|
||||
"version": 4
|
||||
},
|
||||
"contentChanges": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 2,
|
||||
"character": 4
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"character": 4
|
||||
}
|
||||
},
|
||||
"rangeLength": 0,
|
||||
"text": "e"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "textDocument/didOpen",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///home/cobia/test_lsp/1.tsl",
|
||||
"languageId": "tsl",
|
||||
"version": 1,
|
||||
"text": "function aaa();\nbegin\nend;"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,487 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 0,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"processId": 61310,
|
||||
"clientInfo": {
|
||||
"name": "Visual Studio Code",
|
||||
"version": "1.102.0"
|
||||
},
|
||||
"locale": "en",
|
||||
"rootPath": "/home/cobia/test_lsp",
|
||||
"rootUri": "file:///home/cobia/test_lsp",
|
||||
"capabilities": {
|
||||
"workspace": {
|
||||
"applyEdit": true,
|
||||
"workspaceEdit": {
|
||||
"documentChanges": true,
|
||||
"resourceOperations": [
|
||||
"create",
|
||||
"rename",
|
||||
"delete"
|
||||
],
|
||||
"failureHandling": "textOnlyTransactional",
|
||||
"normalizesLineEndings": true,
|
||||
"changeAnnotationSupport": {
|
||||
"groupsOnLabel": true
|
||||
}
|
||||
},
|
||||
"configuration": true,
|
||||
"didChangeWatchedFiles": {
|
||||
"dynamicRegistration": true,
|
||||
"relativePatternSupport": true
|
||||
},
|
||||
"symbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"location.range"
|
||||
]
|
||||
}
|
||||
},
|
||||
"codeLens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"executeCommand": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"workspaceFolders": true,
|
||||
"foldingRange": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"semanticTokens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"fileOperations": {
|
||||
"dynamicRegistration": true,
|
||||
"didCreate": true,
|
||||
"didRename": true,
|
||||
"didDelete": true,
|
||||
"willCreate": true,
|
||||
"willRename": true,
|
||||
"willDelete": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"diagnostics": {
|
||||
"refreshSupport": true
|
||||
}
|
||||
},
|
||||
"textDocument": {
|
||||
"publishDiagnostics": {
|
||||
"relatedInformation": true,
|
||||
"versionSupport": false,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"codeDescriptionSupport": true,
|
||||
"dataSupport": true
|
||||
},
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"willSave": true,
|
||||
"willSaveWaitUntil": true,
|
||||
"didSave": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"contextSupport": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": true,
|
||||
"commitCharactersSupport": true,
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"deprecatedSupport": true,
|
||||
"preselectSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"insertReplaceSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"documentation",
|
||||
"detail",
|
||||
"additionalTextEdits"
|
||||
]
|
||||
},
|
||||
"insertTextModeSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"labelDetailsSupport": true
|
||||
},
|
||||
"insertTextMode": 2,
|
||||
"completionItemKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
]
|
||||
},
|
||||
"completionList": {
|
||||
"itemDefaults": [
|
||||
"commitCharacters",
|
||||
"editRange",
|
||||
"insertTextFormat",
|
||||
"insertTextMode",
|
||||
"data"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"dynamicRegistration": true,
|
||||
"contentFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
},
|
||||
"signatureHelp": {
|
||||
"dynamicRegistration": true,
|
||||
"signatureInformation": {
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"parameterInformation": {
|
||||
"labelOffsetSupport": true
|
||||
},
|
||||
"activeParameterSupport": true
|
||||
},
|
||||
"contextSupport": true
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"references": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentHighlight": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"hierarchicalDocumentSymbolSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"labelSupport": true
|
||||
},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": true,
|
||||
"isPreferredSupport": true,
|
||||
"disabledSupport": true,
|
||||
"dataSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"edit"
|
||||
]
|
||||
},
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"",
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports"
|
||||
]
|
||||
}
|
||||
},
|
||||
"honorsChangeAnnotations": true
|
||||
},
|
||||
"codeLens": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"formatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rangeFormatting": {
|
||||
"dynamicRegistration": true,
|
||||
"rangesSupport": true
|
||||
},
|
||||
"onTypeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rename": {
|
||||
"dynamicRegistration": true,
|
||||
"prepareSupport": true,
|
||||
"prepareSupportDefaultBehavior": 1,
|
||||
"honorsChangeAnnotations": true
|
||||
},
|
||||
"documentLink": {
|
||||
"dynamicRegistration": true,
|
||||
"tooltipSupport": true
|
||||
},
|
||||
"typeDefinition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"implementation": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"colorProvider": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"foldingRange": {
|
||||
"dynamicRegistration": true,
|
||||
"rangeLimit": 5000,
|
||||
"lineFoldingOnly": true,
|
||||
"foldingRangeKind": {
|
||||
"valueSet": [
|
||||
"comment",
|
||||
"imports",
|
||||
"region"
|
||||
]
|
||||
},
|
||||
"foldingRange": {
|
||||
"collapsedText": false
|
||||
}
|
||||
},
|
||||
"declaration": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"selectionRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"callHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"semanticTokens": {
|
||||
"dynamicRegistration": true,
|
||||
"tokenTypes": [
|
||||
"namespace",
|
||||
"type",
|
||||
"class",
|
||||
"enum",
|
||||
"interface",
|
||||
"struct",
|
||||
"typeParameter",
|
||||
"parameter",
|
||||
"variable",
|
||||
"property",
|
||||
"enumMember",
|
||||
"event",
|
||||
"function",
|
||||
"method",
|
||||
"macro",
|
||||
"keyword",
|
||||
"modifier",
|
||||
"comment",
|
||||
"string",
|
||||
"number",
|
||||
"regexp",
|
||||
"operator",
|
||||
"decorator"
|
||||
],
|
||||
"tokenModifiers": [
|
||||
"declaration",
|
||||
"definition",
|
||||
"readonly",
|
||||
"static",
|
||||
"deprecated",
|
||||
"abstract",
|
||||
"async",
|
||||
"modification",
|
||||
"documentation",
|
||||
"defaultLibrary"
|
||||
],
|
||||
"formats": [
|
||||
"relative"
|
||||
],
|
||||
"requests": {
|
||||
"range": true,
|
||||
"full": {
|
||||
"delta": true
|
||||
}
|
||||
},
|
||||
"multilineTokenSupport": false,
|
||||
"overlappingTokenSupport": false,
|
||||
"serverCancelSupport": true,
|
||||
"augmentsSyntaxTokens": true
|
||||
},
|
||||
"linkedEditingRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"typeHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"dynamicRegistration": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"tooltip",
|
||||
"textEdits",
|
||||
"label.tooltip",
|
||||
"label.location",
|
||||
"label.command"
|
||||
]
|
||||
}
|
||||
},
|
||||
"diagnostic": {
|
||||
"dynamicRegistration": true,
|
||||
"relatedDocumentSupport": false
|
||||
}
|
||||
},
|
||||
"window": {
|
||||
"showMessage": {
|
||||
"messageActionItem": {
|
||||
"additionalPropertiesSupport": true
|
||||
}
|
||||
},
|
||||
"showDocument": {
|
||||
"support": true
|
||||
},
|
||||
"workDoneProgress": true
|
||||
},
|
||||
"general": {
|
||||
"staleRequestSupport": {
|
||||
"cancel": true,
|
||||
"retryOnContentModified": [
|
||||
"textDocument/semanticTokens/full",
|
||||
"textDocument/semanticTokens/range",
|
||||
"textDocument/semanticTokens/full/delta"
|
||||
]
|
||||
},
|
||||
"regularExpressions": {
|
||||
"engine": "ECMAScript",
|
||||
"version": "ES2020"
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "marked",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"positionEncodings": [
|
||||
"utf-16"
|
||||
]
|
||||
},
|
||||
"notebookDocument": {
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"executionSummarySupport": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"trace": "off",
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "file:///home/cobia/test_lsp",
|
||||
"name": "test_lsp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
|
||||
[*.{json,toml,yml,gyp}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.scm]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{c,cc,h}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.rs]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{py,pyi}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.swift]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[parser.c]
|
||||
indent_size = 2
|
||||
|
||||
[{alloc,array,parser}.h]
|
||||
indent_size = 2
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
* text=auto eol=lf
|
||||
|
||||
# Generated source files
|
||||
src/*.json linguist-generated
|
||||
src/parser.c linguist-generated
|
||||
src/tree_sitter/* linguist-generated
|
||||
|
||||
# C bindings
|
||||
bindings/c/** linguist-generated
|
||||
CMakeLists.txt linguist-generated
|
||||
Makefile linguist-generated
|
||||
|
||||
# Rust bindings
|
||||
bindings/rust/* linguist-generated
|
||||
Cargo.toml linguist-generated
|
||||
Cargo.lock linguist-generated
|
||||
|
||||
# Node.js bindings
|
||||
bindings/node/* linguist-generated
|
||||
binding.gyp linguist-generated
|
||||
package.json linguist-generated
|
||||
package-lock.json linguist-generated
|
||||
|
||||
# Python bindings
|
||||
bindings/python/** linguist-generated
|
||||
setup.py linguist-generated
|
||||
pyproject.toml linguist-generated
|
||||
|
||||
# Go bindings
|
||||
bindings/go/* linguist-generated
|
||||
go.mod linguist-generated
|
||||
go.sum linguist-generated
|
||||
|
||||
# Swift bindings
|
||||
bindings/swift/** linguist-generated
|
||||
Package.swift linguist-generated
|
||||
Package.resolved linguist-generated
|
||||
|
||||
# Zig bindings
|
||||
build.zig linguist-generated
|
||||
build.zig.zon linguist-generated
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# Rust artifacts
|
||||
target/
|
||||
Cargo.lock
|
||||
|
||||
# Node artifacts
|
||||
build/
|
||||
prebuilds/
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
# Swift artifacts
|
||||
.build/
|
||||
Package.resolved
|
||||
|
||||
# Go artifacts
|
||||
_obj/
|
||||
|
||||
# Python artifacts
|
||||
.venv/
|
||||
dist/
|
||||
*.egg-info
|
||||
*.whl
|
||||
|
||||
# C artifacts
|
||||
*.a
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
*.dll
|
||||
*.pc
|
||||
*.exp
|
||||
*.lib
|
||||
|
||||
# Zig artifacts
|
||||
.zig-cache/
|
||||
zig-cache/
|
||||
zig-out/
|
||||
|
||||
# Example dirs
|
||||
/examples/*/
|
||||
|
||||
# Grammar volatiles
|
||||
*.wasm
|
||||
*.obj
|
||||
*.o
|
||||
|
||||
# Archives
|
||||
*.tar.gz
|
||||
*.tgz
|
||||
*.zip
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
select ["性别"],AvgOf(["英语成绩"]),CountOf( * ),groupfunc(["英语成绩"]) from EnglishScore group by ["性别"],groupfunc(["英语成绩"]) end;
|
||||
// r := sselect * from attrs where lName = lowerCase([0]) end;
|
||||
// R := select [1].*,[2].["英语成绩"] from A join B on [1].["学号"]=[2].["学号"] end;
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 生成 Tree-sitter 解析器
|
||||
echo "=== 生成 Tree-sitter 解析器 ==="
|
||||
tree-sitter generate || { echo "❌ tree-sitter generate 失败"; exit 1; }
|
||||
|
||||
# 要解析的根目录数组
|
||||
ROOT_DIRS=(
|
||||
"/mnt/d/code/tinysoft/OfficeXml-dev/funcext/OfficeXml/autounit"
|
||||
"/mnt/d/code/tinysoft/OfficeXml-dev/funcext/OfficeXml/openxml"
|
||||
"/mnt/d/code/tinysoft/OfficeXml-dev/funcext/OfficeXml/utils"
|
||||
"/mnt/d/code/tinysoft/OfficeXml-dev/funcext/OfficeXml/docx"
|
||||
"/mnt/d/code/tinysoft/OfficeXml-dev/generator"
|
||||
"/mnt/d/code/tinysoft/tsoffice/"
|
||||
"/mnt/d/code/tinysoft/pdfconverter"
|
||||
# "/mnt/d/code/tinysoft/PdfConverter"
|
||||
# 可以添加更多目录
|
||||
# "/path/to/third/directory"
|
||||
)
|
||||
|
||||
echo "=== 开始递归解析所有 .tsf 文件 ==="
|
||||
|
||||
# 错误标志
|
||||
has_error=false
|
||||
|
||||
# 遍历所有根目录
|
||||
for root_dir in "${ROOT_DIRS[@]}"; do
|
||||
echo "--- 处理目录: $root_dir"
|
||||
|
||||
# 检查目录是否存在
|
||||
if [ ! -d "$root_dir" ]; then
|
||||
echo "❌ 目录不存在: $root_dir"
|
||||
has_error=true
|
||||
continue
|
||||
fi
|
||||
|
||||
# 遍历当前目录下的所有 .tsf 文件
|
||||
while IFS= read -r -d '' file; do
|
||||
echo "--- 正在解析: $file"
|
||||
output=$(tree-sitter parse "$file" 2>&1)
|
||||
if echo "$output" | grep -q "ERROR\|MISSING"; then
|
||||
echo "❌ 语法错误在文件: $file"
|
||||
echo "$output"
|
||||
echo "--- 错误详情结束 ---"
|
||||
has_error=true
|
||||
# 如果希望遇到错误立即停止,取消注释下面这行
|
||||
break
|
||||
else
|
||||
echo "✓ 解析成功: $file"
|
||||
fi
|
||||
done < <(find "$root_dir" -type f -name "*.tsf" -print0)
|
||||
done
|
||||
|
||||
# 检查是否有错误
|
||||
if [ "$has_error" = true ]; then
|
||||
echo "❌ 解析过程中发现错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ 所有文件解析成功"
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,364 @@
|
|||
// Example TSF program demonstrating various language features
|
||||
|
||||
// Variable declarations
|
||||
var x, y, z: int;
|
||||
var name: string;
|
||||
var matrix: array of real;
|
||||
|
||||
// Constants
|
||||
const PI = 3.14159;
|
||||
const MAX_SIZE: int = 100;
|
||||
const GREETING = "Hello, World!";
|
||||
|
||||
// Global and static variables
|
||||
global config: object;
|
||||
static counter := 0;
|
||||
|
||||
// Basic arithmetic
|
||||
x := 10;
|
||||
y := 20;
|
||||
z := x + y * 2; // z = 50
|
||||
|
||||
// String operations
|
||||
name := "John" $ " " $ "Doe";
|
||||
|
||||
// Boolean operations
|
||||
var isValid := x > 0 and y < 100;
|
||||
var isReady := not (z = 0) or name <> "";
|
||||
|
||||
// Bitwise operations
|
||||
var flags := 0b1010;
|
||||
flags := flags .| 0b0101; // flags = 0b1111
|
||||
flags := flags shl 2; // flags = 0b111100
|
||||
|
||||
// Matrix operations
|
||||
var A, B, C: matrix;
|
||||
C := A :* B; // Matrix multiplication
|
||||
C := A :^ 2; // Matrix power
|
||||
|
||||
// Set operations
|
||||
var set1, set2, result: set;
|
||||
result := set1 union set2;
|
||||
result := set1 intersect set2;
|
||||
|
||||
// Ternary operator
|
||||
var max := x > y ? x : y;
|
||||
var sign := x > 0 ? 1 : x < 0 ? -1 : 0;
|
||||
|
||||
// Prefix and postfix operators
|
||||
++counter;
|
||||
var oldValue := counter++;
|
||||
|
||||
// Augmented assignments
|
||||
x += 10;
|
||||
y *= 2;
|
||||
flags .&= 0xFF;
|
||||
set1 union= set2;
|
||||
obj.a := 123;
|
||||
obj["a"] := 456;
|
||||
|
||||
// Special values
|
||||
var nothing := nil;
|
||||
var infinity := +inf;
|
||||
var negInfinity := -inf;
|
||||
|
||||
// array
|
||||
arr := array();
|
||||
arr := array(1, 2, 3);
|
||||
arr := array(1, "abc", true, nil);
|
||||
arr := array("key1": "value1", "key2": "value2");
|
||||
arr := array((1, "a"), array(2, "b"));
|
||||
arr := array(array(1, 2), array(3, 4));
|
||||
arr := array(array(array(1, 2), array(3, 4)), array(array(5, 6), array(7, 8)));
|
||||
arr := array(("key1": array(1, 2, 3)), ("key2": (4, 5, 6)));
|
||||
arr := array("outer": array("inner1": 1, "inner2": 2), "data": array(1, 2, 3));
|
||||
arr := array((array(1, 2), array("a", "b")), (array(3, 4), array("c", "d")));
|
||||
arr := array("data": array((1, "first"), (2, "second")), "meta": array("count": 2, "type": "test"));
|
||||
arr := array(var1, var2, array(var3, var4));
|
||||
arr := array(func1(), func2(arg), array(func3(), func4(arg1, arg2)));
|
||||
arr := array(func(), (a + b));
|
||||
arr := array((x + y), z, (a * b));
|
||||
arr := array(func(), (a + b), var1, (c * d));
|
||||
arr := array((a, b, c), (x + y), ("key": "value"));
|
||||
arr := array(1, func(), (expr), (a, b), "key": (value));
|
||||
|
||||
data := array(
|
||||
"users": array(
|
||||
("id": 1, "name": "Alice", "scores": array(85, 92, 78)),
|
||||
("id": 2, "name": "Bob", "scores": array(91, 87, 95))
|
||||
),
|
||||
"metadata": array(
|
||||
"total": 2,
|
||||
"average": array(88.0, 89.5, 86.5),
|
||||
"nested": array(
|
||||
"level1": array(
|
||||
"level2": array(1, 2, 3),
|
||||
"level2b": (true, false, nil)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// 访问嵌套数据
|
||||
first_user := data["users"][0];
|
||||
first_score := first_user["scores"][0];
|
||||
nested_value := data["metadata"]["nested"]["level1"]["level2"][1];
|
||||
|
||||
echo "First user score:", first_score;
|
||||
echo "Nested value:", nested_value;
|
||||
|
||||
// Function calls (hypothetical functions)
|
||||
process(data);
|
||||
calculate(x: 10, y: 20, mode: "fast");
|
||||
##process(data);
|
||||
|
||||
// Attribute access (hypothetical objects)
|
||||
// var length := myString.length;
|
||||
// config.settings.timeout := 3000;
|
||||
|
||||
// Array subscripting and slicing
|
||||
// var first := array[0];
|
||||
// var subArray := array[1:10];
|
||||
// var tail := array[5:];
|
||||
|
||||
// Complex expressions with precedence
|
||||
var result1 := 2 + 3 * 4 ^ 2; // 2 + 3 * 16 = 50
|
||||
var result2 := (2 + 3) * 4 ^ 2; // 5 * 16 = 80
|
||||
var result3 := not a > b and c <= d or e = f;
|
||||
|
||||
// Derivative operator (mathematical)
|
||||
// var derivative := !f;
|
||||
|
||||
// Expression operators
|
||||
// var reference := @value;
|
||||
// var address := &variable;
|
||||
|
||||
{ This is a block comment
|
||||
It can span multiple lines
|
||||
and contain any text }
|
||||
|
||||
(* This is a nested comment
|
||||
It can also span multiple lines
|
||||
and is Pascal-style *)
|
||||
|
||||
// Chained comparisons
|
||||
var inRange := 0 <= x <= 100;
|
||||
var ordered := a < b < c;
|
||||
|
||||
// Nested assignments
|
||||
var a := var b := var c := 0;
|
||||
|
||||
// Complex type specifications
|
||||
var complexType: array of array of real;
|
||||
var functionType: procedure of integer;
|
||||
|
||||
// const
|
||||
const a: real = 10;
|
||||
const b = "123";
|
||||
|
||||
// augmented_assignment
|
||||
a += 10;
|
||||
b += func();
|
||||
c /= ma[1];
|
||||
|
||||
// return
|
||||
return;
|
||||
return 10;
|
||||
return f(10);
|
||||
|
||||
// break
|
||||
break;
|
||||
|
||||
// continue
|
||||
continue;
|
||||
|
||||
// echo
|
||||
echo abc $ def $ "123" $ 456, funcstr(), "\n";
|
||||
|
||||
// raise
|
||||
raise abc() $ "123";
|
||||
|
||||
// inherited
|
||||
inherited abc();
|
||||
|
||||
// new
|
||||
obj := new abc();
|
||||
|
||||
// if
|
||||
if condition then
|
||||
f1();
|
||||
else
|
||||
raise "abc";
|
||||
|
||||
if condition1 then
|
||||
begin
|
||||
if f1() then echo 1;
|
||||
else echo 2;
|
||||
end
|
||||
else if condition2 then
|
||||
begin
|
||||
echo 3;
|
||||
end
|
||||
else begin
|
||||
echo 4;
|
||||
end
|
||||
|
||||
// for
|
||||
for i:=0 to 10 do
|
||||
echo i, "\n";
|
||||
|
||||
for k,v in arr do
|
||||
begin
|
||||
echo "k = 0", "\n";
|
||||
echo "v = 1", "\n";
|
||||
end
|
||||
|
||||
// while
|
||||
while true do
|
||||
echo 123;
|
||||
|
||||
// repeat
|
||||
repeat
|
||||
echo 1;
|
||||
a++;
|
||||
until a > 10;
|
||||
|
||||
// case
|
||||
case x of
|
||||
1,2: return abc;
|
||||
"acde": begin
|
||||
return def;
|
||||
end
|
||||
else begin
|
||||
a := 1;
|
||||
return a;
|
||||
end
|
||||
end;
|
||||
|
||||
// try
|
||||
try
|
||||
a := 1;
|
||||
except
|
||||
a := 2;
|
||||
end
|
||||
|
||||
// function
|
||||
function foo(a, b, c): real
|
||||
begin
|
||||
end
|
||||
|
||||
function foo(a, b: integer; c: string);
|
||||
begin
|
||||
end;
|
||||
|
||||
function foo(a: boolean; b: integer; c: string): real;
|
||||
begin
|
||||
end
|
||||
|
||||
function foo(a: boolean = true; b: integer = 123);
|
||||
begin
|
||||
end;
|
||||
|
||||
// function pointer
|
||||
pf := function(a, b)
|
||||
begin
|
||||
echo a;
|
||||
end;
|
||||
|
||||
// type
|
||||
type A = class
|
||||
public
|
||||
function create()
|
||||
begin
|
||||
end
|
||||
function foo1(a: real; b: real);virtual;
|
||||
function foo2(a: real; b: real);virtual;
|
||||
begin
|
||||
end
|
||||
function operator[](index);
|
||||
|
||||
property row read readrow;
|
||||
property col read readcol write wirtecol;
|
||||
|
||||
private
|
||||
[weakref]a1: real;
|
||||
static a2: real;
|
||||
a3: tslobj;
|
||||
end;
|
||||
|
||||
function A.create();
|
||||
begin
|
||||
pf := aa;
|
||||
end;
|
||||
|
||||
function operator A.[](index);
|
||||
begin
|
||||
a := 1;
|
||||
end;
|
||||
|
||||
function A.foo1(a: real; b: real);virtual;
|
||||
begin
|
||||
pf := function();
|
||||
begin
|
||||
end
|
||||
end
|
||||
|
||||
// select
|
||||
select * from abc end;
|
||||
select *, ["abc"] from abc end;
|
||||
select *, ["abc"] as "def" from abc end;
|
||||
select * from abc where func(["abc"]) end;
|
||||
select * from abc where ["abc"] > 1 end;
|
||||
R1 := select *,RoundTo((["英语成绩"]+["语文成绩"]+["历史成绩"]+["地理成绩"])/4,2) as "文科成绩" from ScoreList end;
|
||||
return select ['stockid'],['date'],['close']*['vol'] as nil from markettable end;
|
||||
A := select *,ThisOrder as "Order" from A order by ["AAA"] end;
|
||||
B := select * from EnglishScore where ["英语成绩"] > 85 order by ["英语成绩"] end;
|
||||
B := select drange(0 to 9) * from EnglishScore order by ["英语成绩"] desc end;
|
||||
B := select drange(1 of 10) from EnglishScore order by ["英语成绩"] desc end;
|
||||
a := select * from abc where ["abc"] > 1 end;
|
||||
b := select * from abc group by ["abc"] end;
|
||||
c := select * from abc group by func(["acbb"]) end;
|
||||
b := select * from abc group by ["abc"], ["def"] end;
|
||||
e := select * from abc where func(["abc"]) order by ["A"] desc end;
|
||||
select AvgOf(["英语成绩"]) from EnglishScore where ["英语成绩"]>85 end;
|
||||
return select ["性别"],AvgOf(["英语成绩"]),CountOf( * ),groupfunc(["英语成绩"]) from EnglishScore group by ["性别"],groupfunc(["英语成绩"]) end;
|
||||
return select AvgOf(["英语成绩"]), CountOf( * ), groupfunc(["英语成绩"]) from EnglishScore group by groupfunc(["英语成绩"]) having CountOf( * ) >1 end;
|
||||
R := select [1].*,[2].["英语成绩"] from A join B on [1].["学号"]=[2].["学号"] end;
|
||||
R := select [1].*,[2].["英语成绩"] from A cross join B where [1].["学号"]=[2].["学号"] end;
|
||||
R := select [1].*,[2].["英语成绩"] from A, B where [1].["学号"]=[2].["学号"] end;
|
||||
R := select [1].*,[2].["英语成绩"] from A join B with ([1].["学号"] on [2].["学号"]) end;
|
||||
R := select [1].*,[2].["英语成绩"] from A join B with ([1].["学号"],[1].["班级"] on [2].["学号"],[2].["班级"]) end;
|
||||
R := select [1].*,[2].["英语成绩"],[3].["语文成绩"] from A join B on [1].["学号"]=[2].["学号"] join C on [1].["学号"]=[3].["学号"] end;
|
||||
R := select [1].*,[2].["英语成绩"],[3].["俄语成绩"] from A left join B on [1].["学号"]=[2].["学号"] left join C on [1].["学号"]=[3].["学号"] end;
|
||||
R := select [1].["学号"]?:[2].["学号"] as "学号",[1].["英语成绩"],[2].["俄语成绩"] from B full join C on [1].["学号"]=[2].["学号"] end;
|
||||
R1 := select ThisRow from R where ThisRow>5 end;
|
||||
R2 := sselect ThisRow from R where ThisRow>5 end;
|
||||
R2 := vselect SumOf( ["英语成绩"] ) from B end;
|
||||
R1 := select ["性别"],["年龄"], AvgOf(["身高"]), select * from ThisGroup end as "详细信息" from R group by ["性别"],["年龄"] end;
|
||||
|
||||
// update
|
||||
update B set ["英语成绩"] = 79 where ["学号"] = "03" end;
|
||||
update B set ["英语成绩"]=79,["语文成绩"]=80 where ["学号"]="03" end;
|
||||
update children set ['origin_field'] = ['field'] end;
|
||||
update children set ['field'] = uppercase(['prefix']) + ['field'] where not ifnil(['ml']) and ['field'] in fields end;
|
||||
update children set ['new_field'] = format('XmlChild%s', ['field']) end;
|
||||
|
||||
// delete
|
||||
delete from A where ["学号"] = "01";
|
||||
delete from A;
|
||||
|
||||
// insert
|
||||
insert into a values("06","路人甲");
|
||||
insert into a insertfields(["学号"],["姓名"],["英语成绩"]) values("06","路人甲",80);
|
||||
|
||||
// []
|
||||
A[2,3];
|
||||
A[2:5,3:6];
|
||||
A[:,3:6];
|
||||
A[3,:];
|
||||
A[:,"columnName"];
|
||||
A[array(2,4,6)];
|
||||
A[array(2,3),array(1,2)];
|
||||
[a,b,c] := arr;
|
||||
|
||||
// End of example
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "tree-sitter-tsf",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "bingings/node",
|
||||
"scripts": {
|
||||
"test": "tree-sitter test"
|
||||
},
|
||||
"tree-sitter":
|
||||
[
|
||||
{
|
||||
"scope": "source.tsf",
|
||||
"file-types": ["tsf"]
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nan": "^2.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tree-sitter-cli": "^0.25.6"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef TREE_SITTER_ALLOC_H_
|
||||
#define TREE_SITTER_ALLOC_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Allow clients to override allocation functions
|
||||
#ifdef TREE_SITTER_REUSE_ALLOCATOR
|
||||
|
||||
extern void *(*ts_current_malloc)(size_t size);
|
||||
extern void *(*ts_current_calloc)(size_t count, size_t size);
|
||||
extern void *(*ts_current_realloc)(void *ptr, size_t size);
|
||||
extern void (*ts_current_free)(void *ptr);
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc ts_current_malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc ts_current_calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc ts_current_realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free ts_current_free
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free free
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ALLOC_H_
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
#ifndef TREE_SITTER_ARRAY_H_
|
||||
#define TREE_SITTER_ARRAY_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "./alloc.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4101)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#endif
|
||||
|
||||
#define Array(T) \
|
||||
struct { \
|
||||
T *contents; \
|
||||
uint32_t size; \
|
||||
uint32_t capacity; \
|
||||
}
|
||||
|
||||
/// Initialize an array.
|
||||
#define array_init(self) \
|
||||
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
|
||||
|
||||
/// Create an empty array.
|
||||
#define array_new() \
|
||||
{ NULL, 0, 0 }
|
||||
|
||||
/// Get a pointer to the element at a given `index` in the array.
|
||||
#define array_get(self, _index) \
|
||||
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
|
||||
|
||||
/// Get a pointer to the first element in the array.
|
||||
#define array_front(self) array_get(self, 0)
|
||||
|
||||
/// Get a pointer to the last element in the array.
|
||||
#define array_back(self) array_get(self, (self)->size - 1)
|
||||
|
||||
/// Clear the array, setting its size to zero. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_clear(self) ((self)->size = 0)
|
||||
|
||||
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
|
||||
/// less than the array's current capacity, this function has no effect.
|
||||
#define array_reserve(self, new_capacity) \
|
||||
_array__reserve((Array *)(self), array_elem_size(self), new_capacity)
|
||||
|
||||
/// Free any memory allocated for this array. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_delete(self) _array__delete((Array *)(self))
|
||||
|
||||
/// Push a new `element` onto the end of the array.
|
||||
#define array_push(self, element) \
|
||||
(_array__grow((Array *)(self), 1, array_elem_size(self)), \
|
||||
(self)->contents[(self)->size++] = (element))
|
||||
|
||||
/// Increase the array's size by `count` elements.
|
||||
/// New elements are zero-initialized.
|
||||
#define array_grow_by(self, count) \
|
||||
do { \
|
||||
if ((count) == 0) break; \
|
||||
_array__grow((Array *)(self), count, array_elem_size(self)); \
|
||||
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
|
||||
(self)->size += (count); \
|
||||
} while (0)
|
||||
|
||||
/// Append all elements from one array to the end of another.
|
||||
#define array_push_all(self, other) \
|
||||
array_extend((self), (other)->size, (other)->contents)
|
||||
|
||||
/// Append `count` elements to the end of the array, reading their values from the
|
||||
/// `contents` pointer.
|
||||
#define array_extend(self, count, contents) \
|
||||
_array__splice( \
|
||||
(Array *)(self), array_elem_size(self), (self)->size, \
|
||||
0, count, contents \
|
||||
)
|
||||
|
||||
/// Remove `old_count` elements from the array starting at the given `index`. At
|
||||
/// the same index, insert `new_count` new elements, reading their values from the
|
||||
/// `new_contents` pointer.
|
||||
#define array_splice(self, _index, old_count, new_count, new_contents) \
|
||||
_array__splice( \
|
||||
(Array *)(self), array_elem_size(self), _index, \
|
||||
old_count, new_count, new_contents \
|
||||
)
|
||||
|
||||
/// Insert one `element` into the array at the given `index`.
|
||||
#define array_insert(self, _index, element) \
|
||||
_array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
|
||||
|
||||
/// Remove one element from the array at the given `index`.
|
||||
#define array_erase(self, _index) \
|
||||
_array__erase((Array *)(self), array_elem_size(self), _index)
|
||||
|
||||
/// Pop the last element off the array, returning the element by value.
|
||||
#define array_pop(self) ((self)->contents[--(self)->size])
|
||||
|
||||
/// Assign the contents of one array to another, reallocating if necessary.
|
||||
#define array_assign(self, other) \
|
||||
_array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
|
||||
|
||||
/// Swap one array with another
|
||||
#define array_swap(self, other) \
|
||||
_array__swap((Array *)(self), (Array *)(other))
|
||||
|
||||
/// Get the size of the array contents
|
||||
#define array_elem_size(self) (sizeof *(self)->contents)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
///
|
||||
/// If an existing element is found to be equal to `needle`, then the `index`
|
||||
/// out-parameter is set to the existing value's index, and the `exists`
|
||||
/// out-parameter is set to true. Otherwise, `index` is set to an index where
|
||||
/// `needle` should be inserted in order to preserve the sorting, and `exists`
|
||||
/// is set to false.
|
||||
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using integer comparisons
|
||||
/// of a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_with`.
|
||||
#define array_search_sorted_by(self, field, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
#define array_insert_sorted_with(self, compare, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using integer comparisons of
|
||||
/// a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_by`.
|
||||
#define array_insert_sorted_by(self, field, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
// Private
|
||||
|
||||
typedef Array(void) Array;
|
||||
|
||||
/// This is not what you're looking for, see `array_delete`.
|
||||
static inline void _array__delete(Array *self) {
|
||||
if (self->contents) {
|
||||
ts_free(self->contents);
|
||||
self->contents = NULL;
|
||||
self->size = 0;
|
||||
self->capacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_erase`.
|
||||
static inline void _array__erase(Array *self, size_t element_size,
|
||||
uint32_t index) {
|
||||
assert(index < self->size);
|
||||
char *contents = (char *)self->contents;
|
||||
memmove(contents + index * element_size, contents + (index + 1) * element_size,
|
||||
(self->size - index - 1) * element_size);
|
||||
self->size--;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_reserve`.
|
||||
static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
|
||||
if (new_capacity > self->capacity) {
|
||||
if (self->contents) {
|
||||
self->contents = ts_realloc(self->contents, new_capacity * element_size);
|
||||
} else {
|
||||
self->contents = ts_malloc(new_capacity * element_size);
|
||||
}
|
||||
self->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_assign`.
|
||||
static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
|
||||
_array__reserve(self, element_size, other->size);
|
||||
self->size = other->size;
|
||||
memcpy(self->contents, other->contents, self->size * element_size);
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_swap`.
|
||||
static inline void _array__swap(Array *self, Array *other) {
|
||||
Array swap = *other;
|
||||
*other = *self;
|
||||
*self = swap;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
|
||||
static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
|
||||
uint32_t new_size = self->size + count;
|
||||
if (new_size > self->capacity) {
|
||||
uint32_t new_capacity = self->capacity * 2;
|
||||
if (new_capacity < 8) new_capacity = 8;
|
||||
if (new_capacity < new_size) new_capacity = new_size;
|
||||
_array__reserve(self, element_size, new_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_splice`.
|
||||
static inline void _array__splice(Array *self, size_t element_size,
|
||||
uint32_t index, uint32_t old_count,
|
||||
uint32_t new_count, const void *elements) {
|
||||
uint32_t new_size = self->size + new_count - old_count;
|
||||
uint32_t old_end = index + old_count;
|
||||
uint32_t new_end = index + new_count;
|
||||
assert(old_end <= self->size);
|
||||
|
||||
_array__reserve(self, element_size, new_size);
|
||||
|
||||
char *contents = (char *)self->contents;
|
||||
if (self->size > old_end) {
|
||||
memmove(
|
||||
contents + new_end * element_size,
|
||||
contents + old_end * element_size,
|
||||
(self->size - old_end) * element_size
|
||||
);
|
||||
}
|
||||
if (new_count > 0) {
|
||||
if (elements) {
|
||||
memcpy(
|
||||
(contents + index * element_size),
|
||||
elements,
|
||||
new_count * element_size
|
||||
);
|
||||
} else {
|
||||
memset(
|
||||
(contents + index * element_size),
|
||||
0,
|
||||
new_count * element_size
|
||||
);
|
||||
}
|
||||
}
|
||||
self->size += new_count - old_count;
|
||||
}
|
||||
|
||||
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
|
||||
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
|
||||
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
|
||||
do { \
|
||||
*(_index) = start; \
|
||||
*(_exists) = false; \
|
||||
uint32_t size = (self)->size - *(_index); \
|
||||
if (size == 0) break; \
|
||||
int comparison; \
|
||||
while (size > 1) { \
|
||||
uint32_t half_size = size / 2; \
|
||||
uint32_t mid_index = *(_index) + half_size; \
|
||||
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
|
||||
if (comparison <= 0) *(_index) = mid_index; \
|
||||
size -= half_size; \
|
||||
} \
|
||||
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
|
||||
if (comparison == 0) *(_exists) = true; \
|
||||
else if (comparison < 0) *(_index) += 1; \
|
||||
} while (0)
|
||||
|
||||
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
|
||||
/// parameter by reference in order to work with the generic sorting function above.
|
||||
#define _compare_int(a, b) ((int)*(a) - (int)(b))
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ARRAY_H_
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
#ifndef TREE_SITTER_PARSER_H_
|
||||
#define TREE_SITTER_PARSER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ts_builtin_sym_error ((TSSymbol)-1)
|
||||
#define ts_builtin_sym_end 0
|
||||
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
|
||||
|
||||
#ifndef TREE_SITTER_API_H_
|
||||
typedef uint16_t TSStateId;
|
||||
typedef uint16_t TSSymbol;
|
||||
typedef uint16_t TSFieldId;
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
typedef struct TSLanguageMetadata {
|
||||
uint8_t major_version;
|
||||
uint8_t minor_version;
|
||||
uint8_t patch_version;
|
||||
} TSLanguageMetadata;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
TSFieldId field_id;
|
||||
uint8_t child_index;
|
||||
bool inherited;
|
||||
} TSFieldMapEntry;
|
||||
|
||||
// Used to index the field and supertype maps.
|
||||
typedef struct {
|
||||
uint16_t index;
|
||||
uint16_t length;
|
||||
} TSMapSlice;
|
||||
|
||||
typedef struct {
|
||||
bool visible;
|
||||
bool named;
|
||||
bool supertype;
|
||||
} TSSymbolMetadata;
|
||||
|
||||
typedef struct TSLexer TSLexer;
|
||||
|
||||
struct TSLexer {
|
||||
int32_t lookahead;
|
||||
TSSymbol result_symbol;
|
||||
void (*advance)(TSLexer *, bool);
|
||||
void (*mark_end)(TSLexer *);
|
||||
uint32_t (*get_column)(TSLexer *);
|
||||
bool (*is_at_included_range_start)(const TSLexer *);
|
||||
bool (*eof)(const TSLexer *);
|
||||
void (*log)(const TSLexer *, const char *, ...);
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
TSParseActionTypeShift,
|
||||
TSParseActionTypeReduce,
|
||||
TSParseActionTypeAccept,
|
||||
TSParseActionTypeRecover,
|
||||
} TSParseActionType;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t type;
|
||||
TSStateId state;
|
||||
bool extra;
|
||||
bool repetition;
|
||||
} shift;
|
||||
struct {
|
||||
uint8_t type;
|
||||
uint8_t child_count;
|
||||
TSSymbol symbol;
|
||||
int16_t dynamic_precedence;
|
||||
uint16_t production_id;
|
||||
} reduce;
|
||||
uint8_t type;
|
||||
} TSParseAction;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
} TSLexMode;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
uint16_t reserved_word_set_id;
|
||||
} TSLexerMode;
|
||||
|
||||
typedef union {
|
||||
TSParseAction action;
|
||||
struct {
|
||||
uint8_t count;
|
||||
bool reusable;
|
||||
} entry;
|
||||
} TSParseActionEntry;
|
||||
|
||||
typedef struct {
|
||||
int32_t start;
|
||||
int32_t end;
|
||||
} TSCharacterRange;
|
||||
|
||||
struct TSLanguage {
|
||||
uint32_t abi_version;
|
||||
uint32_t symbol_count;
|
||||
uint32_t alias_count;
|
||||
uint32_t token_count;
|
||||
uint32_t external_token_count;
|
||||
uint32_t state_count;
|
||||
uint32_t large_state_count;
|
||||
uint32_t production_id_count;
|
||||
uint32_t field_count;
|
||||
uint16_t max_alias_sequence_length;
|
||||
const uint16_t *parse_table;
|
||||
const uint16_t *small_parse_table;
|
||||
const uint32_t *small_parse_table_map;
|
||||
const TSParseActionEntry *parse_actions;
|
||||
const char * const *symbol_names;
|
||||
const char * const *field_names;
|
||||
const TSMapSlice *field_map_slices;
|
||||
const TSFieldMapEntry *field_map_entries;
|
||||
const TSSymbolMetadata *symbol_metadata;
|
||||
const TSSymbol *public_symbol_map;
|
||||
const uint16_t *alias_map;
|
||||
const TSSymbol *alias_sequences;
|
||||
const TSLexerMode *lex_modes;
|
||||
bool (*lex_fn)(TSLexer *, TSStateId);
|
||||
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
|
||||
TSSymbol keyword_capture_token;
|
||||
struct {
|
||||
const bool *states;
|
||||
const TSSymbol *symbol_map;
|
||||
void *(*create)(void);
|
||||
void (*destroy)(void *);
|
||||
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
|
||||
unsigned (*serialize)(void *, char *);
|
||||
void (*deserialize)(void *, const char *, unsigned);
|
||||
} external_scanner;
|
||||
const TSStateId *primary_state_ids;
|
||||
const char *name;
|
||||
const TSSymbol *reserved_words;
|
||||
uint16_t max_reserved_word_set_size;
|
||||
uint32_t supertype_count;
|
||||
const TSSymbol *supertype_symbols;
|
||||
const TSMapSlice *supertype_map_slices;
|
||||
const TSSymbol *supertype_map_entries;
|
||||
TSLanguageMetadata metadata;
|
||||
};
|
||||
|
||||
static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
|
||||
uint32_t index = 0;
|
||||
uint32_t size = len - index;
|
||||
while (size > 1) {
|
||||
uint32_t half_size = size / 2;
|
||||
uint32_t mid_index = index + half_size;
|
||||
const TSCharacterRange *range = &ranges[mid_index];
|
||||
if (lookahead >= range->start && lookahead <= range->end) {
|
||||
return true;
|
||||
} else if (lookahead > range->end) {
|
||||
index = mid_index;
|
||||
}
|
||||
size -= half_size;
|
||||
}
|
||||
const TSCharacterRange *range = &ranges[index];
|
||||
return (lookahead >= range->start && lookahead <= range->end);
|
||||
}
|
||||
|
||||
/*
|
||||
* Lexer Macros
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define UNUSED __pragma(warning(suppress : 4101))
|
||||
#else
|
||||
#define UNUSED __attribute__((unused))
|
||||
#endif
|
||||
|
||||
#define START_LEXER() \
|
||||
bool result = false; \
|
||||
bool skip = false; \
|
||||
UNUSED \
|
||||
bool eof = false; \
|
||||
int32_t lookahead; \
|
||||
goto start; \
|
||||
next_state: \
|
||||
lexer->advance(lexer, skip); \
|
||||
start: \
|
||||
skip = false; \
|
||||
lookahead = lexer->lookahead;
|
||||
|
||||
#define ADVANCE(state_value) \
|
||||
{ \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ADVANCE_MAP(...) \
|
||||
{ \
|
||||
static const uint16_t map[] = { __VA_ARGS__ }; \
|
||||
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
|
||||
if (map[i] == lookahead) { \
|
||||
state = map[i + 1]; \
|
||||
goto next_state; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SKIP(state_value) \
|
||||
{ \
|
||||
skip = true; \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ACCEPT_TOKEN(symbol_value) \
|
||||
result = true; \
|
||||
lexer->result_symbol = symbol_value; \
|
||||
lexer->mark_end(lexer);
|
||||
|
||||
#define END_STATE() return result;
|
||||
|
||||
/*
|
||||
* Parse Table Macros
|
||||
*/
|
||||
|
||||
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
|
||||
|
||||
#define STATE(id) id
|
||||
|
||||
#define ACTIONS(id) id
|
||||
|
||||
#define SHIFT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value) \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_REPEAT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value), \
|
||||
.repetition = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_EXTRA() \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.extra = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define REDUCE(symbol_name, children, precedence, prod_id) \
|
||||
{{ \
|
||||
.reduce = { \
|
||||
.type = TSParseActionTypeReduce, \
|
||||
.symbol = symbol_name, \
|
||||
.child_count = children, \
|
||||
.dynamic_precedence = precedence, \
|
||||
.production_id = prod_id \
|
||||
}, \
|
||||
}}
|
||||
|
||||
#define RECOVER() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeRecover \
|
||||
}}
|
||||
|
||||
#define ACCEPT_INPUT() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeAccept \
|
||||
}}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_PARSER_H_
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json",
|
||||
"grammars": [
|
||||
{
|
||||
"name": "tsf",
|
||||
"camelcase": "Tsf",
|
||||
"title": "TSF",
|
||||
"scope": "source.tsf",
|
||||
"file-types": [
|
||||
"tsf",
|
||||
"tsl"
|
||||
],
|
||||
"injection-regex": "^tsf$",
|
||||
"class-name": "TreeSitterTsf"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"description": "tsf",
|
||||
"authors": [
|
||||
{
|
||||
"name": "csh"
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"repository": "https://github.com/tree-sitter/tree-sitter-tsf"
|
||||
}
|
||||
},
|
||||
"bindings": {
|
||||
"c": true
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue