@nogataka/smart-edit 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +244 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +7 -0
- package/dist/devtools/generate_prompt_factory.d.ts +5 -0
- package/dist/devtools/generate_prompt_factory.js +114 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +34 -0
- package/dist/interprompt/index.d.ts +2 -0
- package/dist/interprompt/index.js +1 -0
- package/dist/interprompt/jinja_template.d.ts +10 -0
- package/dist/interprompt/jinja_template.js +174 -0
- package/dist/interprompt/multilang_prompt.d.ts +54 -0
- package/dist/interprompt/multilang_prompt.js +302 -0
- package/dist/interprompt/prompt_factory.d.ts +16 -0
- package/dist/interprompt/prompt_factory.js +189 -0
- package/dist/interprompt/util/class_decorators.d.ts +1 -0
- package/dist/interprompt/util/class_decorators.js +1 -0
- package/dist/interprompt/util/index.d.ts +1 -0
- package/dist/interprompt/util/index.js +1 -0
- package/dist/serena/agent.d.ts +118 -0
- package/dist/serena/agent.js +675 -0
- package/dist/serena/agno.d.ts +111 -0
- package/dist/serena/agno.js +278 -0
- package/dist/serena/analytics.d.ts +24 -0
- package/dist/serena/analytics.js +119 -0
- package/dist/serena/cli.d.ts +9 -0
- package/dist/serena/cli.js +731 -0
- package/dist/serena/code_editor.d.ts +42 -0
- package/dist/serena/code_editor.js +239 -0
- package/dist/serena/config/context_mode.d.ts +41 -0
- package/dist/serena/config/context_mode.js +239 -0
- package/dist/serena/config/serena_config.d.ts +134 -0
- package/dist/serena/config/serena_config.js +718 -0
- package/dist/serena/constants.d.ts +18 -0
- package/dist/serena/constants.js +27 -0
- package/dist/serena/dashboard.d.ts +55 -0
- package/dist/serena/dashboard.js +472 -0
- package/dist/serena/generated/generated_prompt_factory.d.ts +27 -0
- package/dist/serena/generated/generated_prompt_factory.js +42 -0
- package/dist/serena/gui_log_viewer.d.ts +41 -0
- package/dist/serena/gui_log_viewer.js +436 -0
- package/dist/serena/mcp.d.ts +118 -0
- package/dist/serena/mcp.js +904 -0
- package/dist/serena/project.d.ts +62 -0
- package/dist/serena/project.js +321 -0
- package/dist/serena/prompt_factory.d.ts +20 -0
- package/dist/serena/prompt_factory.js +42 -0
- package/dist/serena/resources/config/contexts/agent.yml +8 -0
- package/dist/serena/resources/config/contexts/chatgpt.yml +28 -0
- package/dist/serena/resources/config/contexts/codex.yml +27 -0
- package/dist/serena/resources/config/contexts/context.template.yml +11 -0
- package/dist/serena/resources/config/contexts/desktop-app.yml +17 -0
- package/dist/serena/resources/config/contexts/ide-assistant.yml +26 -0
- package/dist/serena/resources/config/contexts/oaicompat-agent.yml +8 -0
- package/dist/serena/resources/config/internal_modes/jetbrains.yml +15 -0
- package/dist/serena/resources/config/modes/editing.yml +112 -0
- package/dist/serena/resources/config/modes/interactive.yml +11 -0
- package/dist/serena/resources/config/modes/mode.template.yml +7 -0
- package/dist/serena/resources/config/modes/no-onboarding.yml +8 -0
- package/dist/serena/resources/config/modes/onboarding.yml +16 -0
- package/dist/serena/resources/config/modes/one-shot.yml +15 -0
- package/dist/serena/resources/config/modes/planning.yml +15 -0
- package/dist/serena/resources/config/prompt_templates/simple_tool_outputs.yml +75 -0
- package/dist/serena/resources/config/prompt_templates/system_prompt.yml +66 -0
- package/dist/serena/resources/dashboard/dashboard.js +815 -0
- package/dist/serena/resources/dashboard/index.html +314 -0
- package/dist/serena/resources/dashboard/jquery.min.js +3 -0
- package/dist/serena/resources/dashboard/serena-icon-16.png +0 -0
- package/dist/serena/resources/dashboard/serena-icon-32.png +0 -0
- package/dist/serena/resources/dashboard/serena-icon-48.png +0 -0
- package/dist/serena/resources/dashboard/serena-logs-dark-mode.png +0 -0
- package/dist/serena/resources/dashboard/serena-logs.png +0 -0
- package/dist/serena/resources/project.template.yml +67 -0
- package/dist/serena/resources/serena_config.template.yml +85 -0
- package/dist/serena/symbol.d.ts +199 -0
- package/dist/serena/symbol.js +616 -0
- package/dist/serena/text_utils.d.ts +51 -0
- package/dist/serena/text_utils.js +267 -0
- package/dist/serena/tools/cmd_tools.d.ts +31 -0
- package/dist/serena/tools/cmd_tools.js +48 -0
- package/dist/serena/tools/config_tools.d.ts +53 -0
- package/dist/serena/tools/config_tools.js +176 -0
- package/dist/serena/tools/file_tools.d.ts +231 -0
- package/dist/serena/tools/file_tools.js +511 -0
- package/dist/serena/tools/index.d.ts +7 -0
- package/dist/serena/tools/index.js +7 -0
- package/dist/serena/tools/memory_tools.d.ts +60 -0
- package/dist/serena/tools/memory_tools.js +135 -0
- package/dist/serena/tools/symbol_tools.d.ts +165 -0
- package/dist/serena/tools/symbol_tools.js +362 -0
- package/dist/serena/tools/tools_base.d.ts +162 -0
- package/dist/serena/tools/tools_base.js +378 -0
- package/dist/serena/tools/workflow_tools.d.ts +35 -0
- package/dist/serena/tools/workflow_tools.js +161 -0
- package/dist/serena/util/class_decorators.d.ts +7 -0
- package/dist/serena/util/class_decorators.js +37 -0
- package/dist/serena/util/exception.d.ts +8 -0
- package/dist/serena/util/exception.js +53 -0
- package/dist/serena/util/file_system.d.ts +30 -0
- package/dist/serena/util/file_system.js +352 -0
- package/dist/serena/util/general.d.ts +11 -0
- package/dist/serena/util/general.js +42 -0
- package/dist/serena/util/git.d.ts +11 -0
- package/dist/serena/util/git.js +37 -0
- package/dist/serena/util/inspection.d.ts +45 -0
- package/dist/serena/util/inspection.js +221 -0
- package/dist/serena/util/logging.d.ts +46 -0
- package/dist/serena/util/logging.js +205 -0
- package/dist/serena/util/shell.d.ts +21 -0
- package/dist/serena/util/shell.js +95 -0
- package/dist/serena/util/thread.d.ts +23 -0
- package/dist/serena/util/thread.js +88 -0
- package/dist/serena/version.d.ts +1 -0
- package/dist/serena/version.js +23 -0
- package/dist/solidlsp/language_servers/autoload.d.ts +23 -0
- package/dist/solidlsp/language_servers/autoload.js +25 -0
- package/dist/solidlsp/language_servers/bash_language_server.d.ts +10 -0
- package/dist/solidlsp/language_servers/bash_language_server.js +64 -0
- package/dist/solidlsp/language_servers/clangd_language_server.d.ts +13 -0
- package/dist/solidlsp/language_servers/clangd_language_server.js +110 -0
- package/dist/solidlsp/language_servers/clojure_lsp.d.ts +13 -0
- package/dist/solidlsp/language_servers/clojure_lsp.js +137 -0
- package/dist/solidlsp/language_servers/common.d.ts +41 -0
- package/dist/solidlsp/language_servers/common.js +365 -0
- package/dist/solidlsp/language_servers/csharp_language_server.d.ts +21 -0
- package/dist/solidlsp/language_servers/csharp_language_server.js +694 -0
- package/dist/solidlsp/language_servers/dart_language_server.d.ts +10 -0
- package/dist/solidlsp/language_servers/dart_language_server.js +122 -0
- package/dist/solidlsp/language_servers/eclipse_jdtls.d.ts +24 -0
- package/dist/solidlsp/language_servers/eclipse_jdtls.js +671 -0
- package/dist/solidlsp/language_servers/erlang_language_server.d.ts +22 -0
- package/dist/solidlsp/language_servers/erlang_language_server.js +327 -0
- package/dist/solidlsp/language_servers/gopls.d.ts +12 -0
- package/dist/solidlsp/language_servers/gopls.js +59 -0
- package/dist/solidlsp/language_servers/intelephense.d.ts +13 -0
- package/dist/solidlsp/language_servers/intelephense.js +121 -0
- package/dist/solidlsp/language_servers/jedi_server.d.ts +18 -0
- package/dist/solidlsp/language_servers/jedi_server.js +234 -0
- package/dist/solidlsp/language_servers/kotlin_language_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/kotlin_language_server.js +474 -0
- package/dist/solidlsp/language_servers/lua_ls.d.ts +18 -0
- package/dist/solidlsp/language_servers/lua_ls.js +319 -0
- package/dist/solidlsp/language_servers/nixd_language_server.d.ts +17 -0
- package/dist/solidlsp/language_servers/nixd_language_server.js +341 -0
- package/dist/solidlsp/language_servers/pyright_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/pyright_server.js +180 -0
- package/dist/solidlsp/language_servers/r_language_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/r_language_server.js +184 -0
- package/dist/solidlsp/language_servers/ruby_common.d.ts +10 -0
- package/dist/solidlsp/language_servers/ruby_common.js +136 -0
- package/dist/solidlsp/language_servers/ruby_lsp.d.ts +18 -0
- package/dist/solidlsp/language_servers/ruby_lsp.js +230 -0
- package/dist/solidlsp/language_servers/rust_analyzer.d.ts +13 -0
- package/dist/solidlsp/language_servers/rust_analyzer.js +96 -0
- package/dist/solidlsp/language_servers/solargraph.d.ts +18 -0
- package/dist/solidlsp/language_servers/solargraph.js +208 -0
- package/dist/solidlsp/language_servers/sourcekit_lsp.d.ts +24 -0
- package/dist/solidlsp/language_servers/sourcekit_lsp.js +449 -0
- package/dist/solidlsp/language_servers/terraform_ls.d.ts +13 -0
- package/dist/solidlsp/language_servers/terraform_ls.js +139 -0
- package/dist/solidlsp/language_servers/typescript_language_server.d.ts +20 -0
- package/dist/solidlsp/language_servers/typescript_language_server.js +237 -0
- package/dist/solidlsp/language_servers/vts_language_server.d.ts +13 -0
- package/dist/solidlsp/language_servers/vts_language_server.js +121 -0
- package/dist/solidlsp/language_servers/zls.d.ts +20 -0
- package/dist/solidlsp/language_servers/zls.js +254 -0
- package/dist/solidlsp/ls.d.ts +197 -0
- package/dist/solidlsp/ls.js +507 -0
- package/dist/solidlsp/ls_config.d.ts +43 -0
- package/dist/solidlsp/ls_config.js +157 -0
- package/dist/solidlsp/ls_exceptions.d.ts +5 -0
- package/dist/solidlsp/ls_exceptions.js +14 -0
- package/dist/solidlsp/ls_handler.d.ts +54 -0
- package/dist/solidlsp/ls_handler.js +406 -0
- package/dist/solidlsp/ls_request.d.ts +31 -0
- package/dist/solidlsp/ls_request.js +42 -0
- package/dist/solidlsp/ls_types.d.ts +7 -0
- package/dist/solidlsp/ls_types.js +8 -0
- package/dist/solidlsp/lsp_protocol_handler/server.d.ts +61 -0
- package/dist/solidlsp/lsp_protocol_handler/server.js +68 -0
- package/dist/solidlsp/util/subprocess_util.d.ts +6 -0
- package/dist/solidlsp/util/subprocess_util.js +11 -0
- package/dist/solidlsp/util/zip.d.ts +25 -0
- package/dist/solidlsp/util/zip.js +188 -0
- package/package.json +65 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { createSerenaLogger } from '../../serena/util/logging.js';
|
|
5
|
+
import { Language } from '../ls_config.js';
|
|
6
|
+
import { SolidLanguageServer, registerLanguageServer, coerceLogLevel } from '../ls.js';
|
|
7
|
+
import { NodeLanguageServerHandler } from '../ls_handler.js';
|
|
8
|
+
import { buildRubyExcludePatterns, ensureRubyAvailable, findBundleExecutable, findCommand, gemfileLockContains, installGem } from './ruby_common.js';
|
|
9
|
+
const RUBY_LSP_ASSUME_ENV = 'SERENA_ASSUME_RUBY_LSP';
|
|
10
|
+
const RUBY_LSP_PATH_ENV = 'SERENA_RUBY_LSP_PATH';
|
|
11
|
+
const RUBY_LSP_LOGGER_NAME = 'solidlsp.language_servers.ruby_lsp';
|
|
12
|
+
const DEFAULT_REQUEST_TIMEOUT = 30; // seconds
|
|
13
|
+
const RUBY_IGNORED_DIRECTORIES = [
|
|
14
|
+
'vendor',
|
|
15
|
+
'.bundle',
|
|
16
|
+
'tmp',
|
|
17
|
+
'log',
|
|
18
|
+
'coverage',
|
|
19
|
+
'.yardoc',
|
|
20
|
+
'doc',
|
|
21
|
+
'node_modules',
|
|
22
|
+
'storage',
|
|
23
|
+
'public/packs',
|
|
24
|
+
'public/webpack',
|
|
25
|
+
'public/assets'
|
|
26
|
+
];
|
|
27
|
+
export class RubyLspLanguageServer extends SolidLanguageServer {
|
|
28
|
+
handlerInstance;
|
|
29
|
+
initialized = false;
|
|
30
|
+
constructor(config, loggerLike, repositoryRootPath, options = {}) {
|
|
31
|
+
const augmentedConfig = {
|
|
32
|
+
...config,
|
|
33
|
+
ignoredPaths: mergeIgnoredPaths(config.ignoredPaths, RUBY_IGNORED_DIRECTORIES)
|
|
34
|
+
};
|
|
35
|
+
const { logger: runtimeLogger } = createSerenaLogger({
|
|
36
|
+
name: RUBY_LSP_LOGGER_NAME,
|
|
37
|
+
emitToConsole: false,
|
|
38
|
+
level: loggerLike?.level === undefined ? undefined : coerceLogLevel(loggerLike.level)
|
|
39
|
+
});
|
|
40
|
+
const command = resolveRubyLspCommand(repositoryRootPath, runtimeLogger);
|
|
41
|
+
const handler = new NodeLanguageServerHandler({
|
|
42
|
+
cmd: command,
|
|
43
|
+
cwd: repositoryRootPath
|
|
44
|
+
}, {
|
|
45
|
+
requestTimeoutSeconds: options?.timeout ?? DEFAULT_REQUEST_TIMEOUT
|
|
46
|
+
});
|
|
47
|
+
super(augmentedConfig, loggerLike, repositoryRootPath, {
|
|
48
|
+
...options,
|
|
49
|
+
handler,
|
|
50
|
+
timeout: options?.timeout ?? DEFAULT_REQUEST_TIMEOUT,
|
|
51
|
+
solidlspSettings: options?.solidlspSettings
|
|
52
|
+
});
|
|
53
|
+
this.handlerInstance = handler;
|
|
54
|
+
this.registerHandlers();
|
|
55
|
+
}
|
|
56
|
+
start() {
|
|
57
|
+
const shouldInitialize = !this.initialized;
|
|
58
|
+
super.start();
|
|
59
|
+
if (shouldInitialize) {
|
|
60
|
+
this.initializeLanguageServer();
|
|
61
|
+
this.initialized = true;
|
|
62
|
+
}
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
stop(shutdownTimeout = 2.0) {
|
|
66
|
+
super.stop(shutdownTimeout);
|
|
67
|
+
this.initialized = false;
|
|
68
|
+
}
|
|
69
|
+
registerHandlers() {
|
|
70
|
+
const noop = () => undefined;
|
|
71
|
+
this.handlerInstance.onRequest('client/registerCapability', noop);
|
|
72
|
+
this.handlerInstance.onRequest('workspace/executeClientCommand', () => []);
|
|
73
|
+
this.handlerInstance.onNotification('language/status', (payload) => {
|
|
74
|
+
const params = payload;
|
|
75
|
+
if (params?.type) {
|
|
76
|
+
this.logger.info(`ruby-lsp status: ${params.type}${params.message ? ` (${params.message})` : ''}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
this.handlerInstance.onNotification('$/progress', (payload) => {
|
|
80
|
+
const info = payload && typeof payload === 'object' ? payload : null;
|
|
81
|
+
if (info && 'value' in info) {
|
|
82
|
+
this.logger.debug(`ruby-lsp progress: ${JSON.stringify(info)}`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
this.handlerInstance.onNotification('window/logMessage', (payload) => {
|
|
86
|
+
const message = extractWindowMessage(payload);
|
|
87
|
+
if (message) {
|
|
88
|
+
this.logger.info(`ruby-lsp: ${message}`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
this.handlerInstance.onNotification('textDocument/publishDiagnostics', noop);
|
|
92
|
+
}
|
|
93
|
+
initializeLanguageServer() {
|
|
94
|
+
this.logger.info('ruby-lsp を初期化しています');
|
|
95
|
+
const params = this.buildInitializeParams();
|
|
96
|
+
const response = this.handlerInstance.sendRequest('initialize', params);
|
|
97
|
+
this.verifyCapabilities(response?.capabilities ?? null);
|
|
98
|
+
this.handlerInstance.notify.initialized({});
|
|
99
|
+
}
|
|
100
|
+
buildInitializeParams() {
|
|
101
|
+
const rootUri = pathToFileURL(this.repositoryRootPath).href;
|
|
102
|
+
return {
|
|
103
|
+
processId: process.pid,
|
|
104
|
+
rootPath: this.repositoryRootPath,
|
|
105
|
+
rootUri,
|
|
106
|
+
capabilities: buildClientCapabilities(),
|
|
107
|
+
initializationOptions: buildInitializationOptions(this.repositoryRootPath),
|
|
108
|
+
workspaceFolders: [
|
|
109
|
+
{
|
|
110
|
+
uri: rootUri,
|
|
111
|
+
name: path.basename(this.repositoryRootPath)
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
verifyCapabilities(capabilities) {
|
|
117
|
+
if (!capabilities || typeof capabilities !== 'object') {
|
|
118
|
+
throw new Error('ruby-lsp 初期化レスポンスに capabilities が含まれていません。');
|
|
119
|
+
}
|
|
120
|
+
const textDocumentCaps = capabilities;
|
|
121
|
+
if (!Object.prototype.hasOwnProperty.call(textDocumentCaps, 'textDocumentSync')) {
|
|
122
|
+
throw new Error('ruby-lsp が textDocumentSync capability を報告しませんでした。');
|
|
123
|
+
}
|
|
124
|
+
if (!Object.prototype.hasOwnProperty.call(textDocumentCaps, 'completionProvider')) {
|
|
125
|
+
throw new Error('ruby-lsp が completionProvider capability を報告しませんでした。');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function resolveRubyLspCommand(repositoryRootPath, logger) {
|
|
130
|
+
const override = process.env[RUBY_LSP_PATH_ENV]?.trim();
|
|
131
|
+
if (override) {
|
|
132
|
+
logger.info(`SERENA_RUBY_LSP_PATH を使用: ${override}`);
|
|
133
|
+
return parseCommandOverride(override);
|
|
134
|
+
}
|
|
135
|
+
if (process.env[RUBY_LSP_ASSUME_ENV] === '1') {
|
|
136
|
+
logger.info('SERENA_ASSUME_RUBY_LSP=1 のためランタイム検証をスキップします');
|
|
137
|
+
const fallback = findCommand('ruby-lsp') ?? 'ruby-lsp';
|
|
138
|
+
return [fallback];
|
|
139
|
+
}
|
|
140
|
+
ensureRubyAvailable(logger, repositoryRootPath);
|
|
141
|
+
const gemfilePath = path.join(repositoryRootPath, 'Gemfile');
|
|
142
|
+
const gemfileLockPath = path.join(repositoryRootPath, 'Gemfile.lock');
|
|
143
|
+
const isBundlerProject = fs.existsSync(gemfilePath);
|
|
144
|
+
if (isBundlerProject) {
|
|
145
|
+
const bundleCommand = findBundleExecutable(repositoryRootPath);
|
|
146
|
+
if (!bundleCommand) {
|
|
147
|
+
logger.warn('Gemfile は見つかりましたが bundle コマンドが見つかりません。PATH または bin/bundle を確認してください。');
|
|
148
|
+
}
|
|
149
|
+
else if (gemfileLockContains(gemfileLockPath, 'ruby-lsp')) {
|
|
150
|
+
logger.info(`bundle exec ruby-lsp を使用します (${bundleCommand})`);
|
|
151
|
+
return [bundleCommand, 'exec', 'ruby-lsp'];
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
logger.warn('Gemfile.lock に ruby-lsp が含まれていません。グローバルインストールを探します。');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const globalRubyLsp = findCommand('ruby-lsp');
|
|
158
|
+
if (globalRubyLsp) {
|
|
159
|
+
logger.info(`PATH から ruby-lsp を検出: ${globalRubyLsp}`);
|
|
160
|
+
return [globalRubyLsp];
|
|
161
|
+
}
|
|
162
|
+
installGem('ruby-lsp', logger, repositoryRootPath);
|
|
163
|
+
const installedRubyLsp = findCommand('ruby-lsp');
|
|
164
|
+
if (installedRubyLsp) {
|
|
165
|
+
logger.info(`gem install 後の ruby-lsp を検出: ${installedRubyLsp}`);
|
|
166
|
+
return [installedRubyLsp];
|
|
167
|
+
}
|
|
168
|
+
logger.warn('ruby-lsp のパス解決に失敗しました。最終手段として ruby-lsp を直接実行します。');
|
|
169
|
+
return ['ruby-lsp'];
|
|
170
|
+
}
|
|
171
|
+
function mergeIgnoredPaths(existing, additions) {
|
|
172
|
+
const merged = new Set(existing ?? []);
|
|
173
|
+
for (const entry of additions) {
|
|
174
|
+
merged.add(entry);
|
|
175
|
+
}
|
|
176
|
+
return Array.from(merged);
|
|
177
|
+
}
|
|
178
|
+
function extractWindowMessage(payload) {
|
|
179
|
+
if (!payload || typeof payload !== 'object') {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const message = payload.message;
|
|
183
|
+
return typeof message === 'string' ? message : null;
|
|
184
|
+
}
|
|
185
|
+
function buildClientCapabilities() {
|
|
186
|
+
const symbolKinds = Array.from({ length: 26 }, (_, index) => index + 1);
|
|
187
|
+
return {
|
|
188
|
+
workspace: {
|
|
189
|
+
workspaceEdit: { documentChanges: true },
|
|
190
|
+
configuration: true
|
|
191
|
+
},
|
|
192
|
+
window: {
|
|
193
|
+
workDoneProgress: true
|
|
194
|
+
},
|
|
195
|
+
textDocument: {
|
|
196
|
+
documentSymbol: {
|
|
197
|
+
hierarchicalDocumentSymbolSupport: true,
|
|
198
|
+
symbolKind: { valueSet: symbolKinds }
|
|
199
|
+
},
|
|
200
|
+
completion: {
|
|
201
|
+
completionItem: {
|
|
202
|
+
snippetSupport: true,
|
|
203
|
+
commitCharactersSupport: true
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function buildInitializationOptions(repositoryRootPath) {
|
|
210
|
+
return {
|
|
211
|
+
experimentalFeaturesEnabled: false,
|
|
212
|
+
featuresConfiguration: {},
|
|
213
|
+
indexing: {
|
|
214
|
+
includedPatterns: ['**/*.rb', '**/*.rake', '**/*.ru', '**/*.erb'],
|
|
215
|
+
excludedPatterns: buildRubyExcludePatterns(repositoryRootPath)
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function parseCommandOverride(value) {
|
|
220
|
+
const trimmed = value.trim();
|
|
221
|
+
if (!trimmed) {
|
|
222
|
+
return ['ruby-lsp'];
|
|
223
|
+
}
|
|
224
|
+
const matches = trimmed.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
|
|
225
|
+
if (!matches) {
|
|
226
|
+
return [trimmed];
|
|
227
|
+
}
|
|
228
|
+
return matches.map((segment) => segment.replace(/^['"]|['"]$/g, ''));
|
|
229
|
+
}
|
|
230
|
+
registerLanguageServer(Language.RUBY, RubyLspLanguageServer);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LogLevel } from '../../serena/util/logging.js';
|
|
2
|
+
import { SolidLanguageServer, type LanguageServerConfigLike, type SolidLspSettingsInit } from '../ls.js';
|
|
3
|
+
import { NodeLanguageServerHandler } from '../ls_handler.js';
|
|
4
|
+
export declare class RustAnalyzerLanguageServer extends SolidLanguageServer {
|
|
5
|
+
protected readonly handler: NodeLanguageServerHandler;
|
|
6
|
+
constructor(config: LanguageServerConfigLike, loggerLike: {
|
|
7
|
+
level?: number | LogLevel;
|
|
8
|
+
} | null, repositoryRootPath: string, options?: {
|
|
9
|
+
timeout?: number | null;
|
|
10
|
+
solidlspSettings?: SolidLspSettingsInit;
|
|
11
|
+
});
|
|
12
|
+
private registerHandlers;
|
|
13
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { Language } from '../ls_config.js';
|
|
4
|
+
import { ensureDefaultSubprocessOptions } from '../util/subprocess_util.js';
|
|
5
|
+
import { SolidLanguageServer, registerLanguageServer } from '../ls.js';
|
|
6
|
+
import { NodeLanguageServerHandler } from '../ls_handler.js';
|
|
7
|
+
function getRustupVersion() {
|
|
8
|
+
const result = spawnSync('rustup', ['--version'], ensureDefaultSubprocessOptions({ encoding: 'utf-8' }));
|
|
9
|
+
if (result.status === 0 && result.stdout) {
|
|
10
|
+
return result.stdout.trim();
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
function getRustAnalyzerViaRustup() {
|
|
15
|
+
const result = spawnSync('rustup', ['which', 'rust-analyzer'], ensureDefaultSubprocessOptions({ encoding: 'utf-8' }));
|
|
16
|
+
if (result.status === 0 && result.stdout) {
|
|
17
|
+
return result.stdout.trim();
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function whichBinary(command) {
|
|
22
|
+
const locator = process.platform === 'win32' ? 'where' : 'which';
|
|
23
|
+
const result = spawnSync(locator, [command], ensureDefaultSubprocessOptions({ encoding: 'utf-8' }));
|
|
24
|
+
if (result.status === 0 && result.stdout) {
|
|
25
|
+
const firstLine = result.stdout.split(/\r?\n/)[0]?.trim();
|
|
26
|
+
return firstLine && firstLine.length > 0 ? firstLine : null;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function ensureRustAnalyzerBinary() {
|
|
31
|
+
const overridePath = process.env.SERENA_RUST_ANALYZER_PATH;
|
|
32
|
+
if (overridePath) {
|
|
33
|
+
return overridePath;
|
|
34
|
+
}
|
|
35
|
+
const fromRustup = getRustAnalyzerViaRustup();
|
|
36
|
+
if (fromRustup && fs.existsSync(fromRustup)) {
|
|
37
|
+
return fromRustup;
|
|
38
|
+
}
|
|
39
|
+
const fromPath = whichBinary('rust-analyzer');
|
|
40
|
+
if (fromPath) {
|
|
41
|
+
return fromPath;
|
|
42
|
+
}
|
|
43
|
+
const rustupVersion = getRustupVersion();
|
|
44
|
+
if (!rustupVersion) {
|
|
45
|
+
throw new Error('Neither rust-analyzer nor rustup is installed. Install Rust via https://rustup.rs/ or provide rust-analyzer in PATH.');
|
|
46
|
+
}
|
|
47
|
+
const installResult = spawnSync('rustup', ['component', 'add', 'rust-analyzer'], ensureDefaultSubprocessOptions({ encoding: 'utf-8' }));
|
|
48
|
+
if (installResult.status !== 0) {
|
|
49
|
+
throw new Error(`Failed to install rust-analyzer via rustup: ${installResult.stderr ?? installResult.stdout}`);
|
|
50
|
+
}
|
|
51
|
+
const installedPath = getRustAnalyzerViaRustup();
|
|
52
|
+
if (installedPath && fs.existsSync(installedPath)) {
|
|
53
|
+
return installedPath;
|
|
54
|
+
}
|
|
55
|
+
throw new Error('rust-analyzer installation succeeded but binary not found in PATH.');
|
|
56
|
+
}
|
|
57
|
+
function mergeIgnoredPaths(existing) {
|
|
58
|
+
const merged = new Set(existing ?? []);
|
|
59
|
+
merged.add('target');
|
|
60
|
+
return Array.from(merged);
|
|
61
|
+
}
|
|
62
|
+
export class RustAnalyzerLanguageServer extends SolidLanguageServer {
|
|
63
|
+
handler;
|
|
64
|
+
constructor(config, loggerLike, repositoryRootPath, options = {}) {
|
|
65
|
+
const augmentedConfig = {
|
|
66
|
+
...config,
|
|
67
|
+
ignoredPaths: mergeIgnoredPaths(config.ignoredPaths)
|
|
68
|
+
};
|
|
69
|
+
const binaryPath = ensureRustAnalyzerBinary();
|
|
70
|
+
const handler = new NodeLanguageServerHandler({
|
|
71
|
+
cmd: binaryPath,
|
|
72
|
+
cwd: repositoryRootPath
|
|
73
|
+
});
|
|
74
|
+
super(augmentedConfig, loggerLike, repositoryRootPath, {
|
|
75
|
+
...options,
|
|
76
|
+
handler,
|
|
77
|
+
solidlspSettings: options?.solidlspSettings
|
|
78
|
+
});
|
|
79
|
+
this.handler = handler;
|
|
80
|
+
this.registerHandlers();
|
|
81
|
+
}
|
|
82
|
+
registerHandlers() {
|
|
83
|
+
const noop = () => undefined;
|
|
84
|
+
this.handler.onNotification('window/logMessage', (payload) => {
|
|
85
|
+
if (payload && typeof payload === 'object' && 'message' in payload) {
|
|
86
|
+
this.logger.info(`rust-analyzer: ${payload.message ?? ''}`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
this.handler.onNotification('$/progress', noop);
|
|
90
|
+
this.handler.onNotification('textDocument/publishDiagnostics', noop);
|
|
91
|
+
this.handler.onNotification('experimental/serverStatus', noop);
|
|
92
|
+
this.handler.onRequest('client/registerCapability', noop);
|
|
93
|
+
this.handler.onRequest('workspace/executeClientCommand', () => []);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
registerLanguageServer(Language.RUST, RustAnalyzerLanguageServer);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type LogLevel } from '../../serena/util/logging.js';
|
|
2
|
+
import { SolidLanguageServer, type LanguageServerConfigLike, type SolidLspSettingsInit } from '../ls.js';
|
|
3
|
+
export declare class SolargraphLanguageServer extends SolidLanguageServer {
|
|
4
|
+
private readonly handlerInstance;
|
|
5
|
+
private initialized;
|
|
6
|
+
constructor(config: LanguageServerConfigLike, loggerLike: {
|
|
7
|
+
level?: number | LogLevel;
|
|
8
|
+
} | null, repositoryRootPath: string, options?: {
|
|
9
|
+
timeout?: number | null;
|
|
10
|
+
solidlspSettings?: SolidLspSettingsInit;
|
|
11
|
+
});
|
|
12
|
+
start(): this;
|
|
13
|
+
stop(shutdownTimeout?: number): void;
|
|
14
|
+
private registerHandlers;
|
|
15
|
+
private initializeLanguageServer;
|
|
16
|
+
private buildInitializeParams;
|
|
17
|
+
private verifyCapabilities;
|
|
18
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { createSerenaLogger } from '../../serena/util/logging.js';
|
|
5
|
+
import { Language } from '../ls_config.js';
|
|
6
|
+
import { SolidLanguageServer, registerLanguageServer, coerceLogLevel } from '../ls.js';
|
|
7
|
+
import { NodeLanguageServerHandler } from '../ls_handler.js';
|
|
8
|
+
import { buildRubyExcludePatterns, ensureRubyAvailable, findBundleExecutable, findCommand, gemfileLockContains, installGem } from './ruby_common.js';
|
|
9
|
+
const SOLARGRAPH_ASSUME_ENV = 'SERENA_ASSUME_SOLARGRAPH';
|
|
10
|
+
const SOLARGRAPH_PATH_ENV = 'SERENA_SOLARGRAPH_PATH';
|
|
11
|
+
const SOLARGRAPH_LOGGER_NAME = 'solidlsp.language_servers.solargraph';
|
|
12
|
+
const DEFAULT_TIMEOUT = 120; // seconds
|
|
13
|
+
const SOLARGRAPH_IGNORED_DIRECTORIES = [
|
|
14
|
+
'vendor',
|
|
15
|
+
'.bundle',
|
|
16
|
+
'tmp',
|
|
17
|
+
'log',
|
|
18
|
+
'coverage',
|
|
19
|
+
'.yardoc',
|
|
20
|
+
'doc',
|
|
21
|
+
'node_modules',
|
|
22
|
+
'storage'
|
|
23
|
+
];
|
|
24
|
+
export class SolargraphLanguageServer extends SolidLanguageServer {
|
|
25
|
+
handlerInstance;
|
|
26
|
+
initialized = false;
|
|
27
|
+
constructor(config, loggerLike, repositoryRootPath, options = {}) {
|
|
28
|
+
const adjustedConfig = {
|
|
29
|
+
...config,
|
|
30
|
+
ignoredPaths: mergeIgnoredPaths(config.ignoredPaths, SOLARGRAPH_IGNORED_DIRECTORIES)
|
|
31
|
+
};
|
|
32
|
+
const { logger: runtimeLogger } = createSerenaLogger({
|
|
33
|
+
name: SOLARGRAPH_LOGGER_NAME,
|
|
34
|
+
emitToConsole: false,
|
|
35
|
+
level: loggerLike?.level === undefined ? undefined : coerceLogLevel(loggerLike.level)
|
|
36
|
+
});
|
|
37
|
+
const command = resolveSolargraphCommand(repositoryRootPath, runtimeLogger);
|
|
38
|
+
const handler = new NodeLanguageServerHandler({
|
|
39
|
+
cmd: command,
|
|
40
|
+
cwd: repositoryRootPath
|
|
41
|
+
}, {
|
|
42
|
+
requestTimeoutSeconds: options?.timeout ?? DEFAULT_TIMEOUT
|
|
43
|
+
});
|
|
44
|
+
super(adjustedConfig, loggerLike, repositoryRootPath, {
|
|
45
|
+
...options,
|
|
46
|
+
handler,
|
|
47
|
+
timeout: options?.timeout ?? DEFAULT_TIMEOUT,
|
|
48
|
+
solidlspSettings: options?.solidlspSettings
|
|
49
|
+
});
|
|
50
|
+
this.handlerInstance = handler;
|
|
51
|
+
this.registerHandlers();
|
|
52
|
+
}
|
|
53
|
+
start() {
|
|
54
|
+
const shouldInitialize = !this.initialized;
|
|
55
|
+
super.start();
|
|
56
|
+
if (shouldInitialize) {
|
|
57
|
+
this.initializeLanguageServer();
|
|
58
|
+
this.initialized = true;
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
stop(shutdownTimeout = 2.0) {
|
|
63
|
+
super.stop(shutdownTimeout);
|
|
64
|
+
this.initialized = false;
|
|
65
|
+
}
|
|
66
|
+
registerHandlers() {
|
|
67
|
+
const noop = () => undefined;
|
|
68
|
+
this.handlerInstance.onRequest('client/registerCapability', (params) => {
|
|
69
|
+
this.logger.debug(`solargraph registerCapability: ${JSON.stringify(params)}`);
|
|
70
|
+
return [];
|
|
71
|
+
});
|
|
72
|
+
this.handlerInstance.onRequest('workspace/executeClientCommand', () => []);
|
|
73
|
+
this.handlerInstance.onNotification('language/status', (payload) => {
|
|
74
|
+
const params = payload;
|
|
75
|
+
if (params?.type) {
|
|
76
|
+
this.logger.info(`solargraph status: ${params.type}${params.message ? ` (${params.message})` : ''}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
this.handlerInstance.onNotification('window/logMessage', (payload) => {
|
|
80
|
+
const message = extractWindowMessage(payload);
|
|
81
|
+
if (message) {
|
|
82
|
+
this.logger.info(`solargraph: ${message}`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
this.handlerInstance.onNotification('$/progress', noop);
|
|
86
|
+
this.handlerInstance.onNotification('textDocument/publishDiagnostics', noop);
|
|
87
|
+
this.handlerInstance.onNotification('language/actionableNotification', noop);
|
|
88
|
+
}
|
|
89
|
+
initializeLanguageServer() {
|
|
90
|
+
this.logger.info('Solargraph を初期化しています');
|
|
91
|
+
const params = this.buildInitializeParams();
|
|
92
|
+
const response = this.handlerInstance.sendRequest('initialize', params);
|
|
93
|
+
this.verifyCapabilities(response?.capabilities ?? null);
|
|
94
|
+
this.handlerInstance.notify.initialized({});
|
|
95
|
+
}
|
|
96
|
+
buildInitializeParams() {
|
|
97
|
+
const rootUri = pathToFileURL(this.repositoryRootPath).href;
|
|
98
|
+
return {
|
|
99
|
+
processId: process.pid,
|
|
100
|
+
rootPath: this.repositoryRootPath,
|
|
101
|
+
rootUri,
|
|
102
|
+
capabilities: buildClientCapabilities(),
|
|
103
|
+
initializationOptions: {
|
|
104
|
+
exclude: buildRubyExcludePatterns(this.repositoryRootPath)
|
|
105
|
+
},
|
|
106
|
+
trace: 'verbose',
|
|
107
|
+
workspaceFolders: [
|
|
108
|
+
{
|
|
109
|
+
uri: rootUri,
|
|
110
|
+
name: path.basename(this.repositoryRootPath)
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
verifyCapabilities(capabilities) {
|
|
116
|
+
if (!capabilities || typeof capabilities !== 'object') {
|
|
117
|
+
throw new Error('Solargraph 初期化レスポンスに capabilities が含まれていません。');
|
|
118
|
+
}
|
|
119
|
+
const textDocumentCaps = capabilities;
|
|
120
|
+
if (!Object.prototype.hasOwnProperty.call(textDocumentCaps, 'textDocumentSync')) {
|
|
121
|
+
throw new Error('Solargraph が textDocumentSync capability を報告しませんでした。');
|
|
122
|
+
}
|
|
123
|
+
if (!Object.prototype.hasOwnProperty.call(textDocumentCaps, 'completionProvider')) {
|
|
124
|
+
throw new Error('Solargraph が completionProvider capability を報告しませんでした。');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function resolveSolargraphCommand(repositoryRootPath, logger) {
|
|
129
|
+
const override = process.env[SOLARGRAPH_PATH_ENV]?.trim();
|
|
130
|
+
if (override) {
|
|
131
|
+
logger.info(`SERENA_SOLARGRAPH_PATH を使用: ${override}`);
|
|
132
|
+
return parseCommandOverride(override, true);
|
|
133
|
+
}
|
|
134
|
+
if (process.env[SOLARGRAPH_ASSUME_ENV] === '1') {
|
|
135
|
+
logger.info('SERENA_ASSUME_SOLARGRAPH=1 によりランタイム検証をスキップします');
|
|
136
|
+
const fallback = findCommand('solargraph') ?? 'solargraph';
|
|
137
|
+
return [fallback, 'stdio'];
|
|
138
|
+
}
|
|
139
|
+
ensureRubyAvailable(logger, repositoryRootPath);
|
|
140
|
+
const gemfilePath = path.join(repositoryRootPath, 'Gemfile');
|
|
141
|
+
const gemfileLockPath = path.join(repositoryRootPath, 'Gemfile.lock');
|
|
142
|
+
const isBundlerProject = fs.existsSync(gemfilePath);
|
|
143
|
+
if (isBundlerProject) {
|
|
144
|
+
const bundleCommand = findBundleExecutable(repositoryRootPath);
|
|
145
|
+
if (!bundleCommand) {
|
|
146
|
+
throw new Error('Bundler プロジェクトですが bundle コマンドが見つかりません。Bundler をインストールし PATH を設定してください。');
|
|
147
|
+
}
|
|
148
|
+
if (!gemfileLockContains(gemfileLockPath, 'solargraph')) {
|
|
149
|
+
throw new Error("Gemfile.lock に solargraph が含まれていません。Gemfile に `gem 'solargraph'` を追加して `bundle install` を実行してください。");
|
|
150
|
+
}
|
|
151
|
+
logger.info(`bundle exec solargraph を使用します (${bundleCommand})`);
|
|
152
|
+
return [bundleCommand, 'exec', 'solargraph', 'stdio'];
|
|
153
|
+
}
|
|
154
|
+
const globalSolargraph = findCommand('solargraph');
|
|
155
|
+
if (globalSolargraph) {
|
|
156
|
+
logger.info(`PATH から solargraph を検出: ${globalSolargraph}`);
|
|
157
|
+
return [globalSolargraph, 'stdio'];
|
|
158
|
+
}
|
|
159
|
+
installGem('solargraph', logger, repositoryRootPath);
|
|
160
|
+
const installedSolargraph = findCommand('solargraph');
|
|
161
|
+
if (installedSolargraph) {
|
|
162
|
+
logger.info(`gem install 後の solargraph を検出: ${installedSolargraph}`);
|
|
163
|
+
return [installedSolargraph, 'stdio'];
|
|
164
|
+
}
|
|
165
|
+
logger.warn('solargraph のパス解決に失敗しました。最終手段として solargraph stdio を使用します。');
|
|
166
|
+
return ['solargraph', 'stdio'];
|
|
167
|
+
}
|
|
168
|
+
function mergeIgnoredPaths(existing, additions) {
|
|
169
|
+
const merged = new Set(existing ?? []);
|
|
170
|
+
for (const entry of additions) {
|
|
171
|
+
merged.add(entry);
|
|
172
|
+
}
|
|
173
|
+
return Array.from(merged);
|
|
174
|
+
}
|
|
175
|
+
function extractWindowMessage(payload) {
|
|
176
|
+
if (!payload || typeof payload !== 'object') {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const message = payload.message;
|
|
180
|
+
return typeof message === 'string' ? message : null;
|
|
181
|
+
}
|
|
182
|
+
function buildClientCapabilities() {
|
|
183
|
+
const symbolKinds = Array.from({ length: 26 }, (_, index) => index + 1);
|
|
184
|
+
return {
|
|
185
|
+
workspace: {
|
|
186
|
+
workspaceEdit: { documentChanges: true }
|
|
187
|
+
},
|
|
188
|
+
textDocument: {
|
|
189
|
+
documentSymbol: {
|
|
190
|
+
hierarchicalDocumentSymbolSupport: true,
|
|
191
|
+
symbolKind: { valueSet: symbolKinds }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function parseCommandOverride(value, appendStdio) {
|
|
197
|
+
const trimmed = value.trim();
|
|
198
|
+
if (!trimmed) {
|
|
199
|
+
return appendStdio ? ['solargraph', 'stdio'] : ['solargraph'];
|
|
200
|
+
}
|
|
201
|
+
const matches = trimmed.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
|
|
202
|
+
const parts = matches ? matches.map((segment) => segment.replace(/^['"]|['"]$/g, '')) : [trimmed];
|
|
203
|
+
if (appendStdio && parts[parts.length - 1] !== 'stdio') {
|
|
204
|
+
parts.push('stdio');
|
|
205
|
+
}
|
|
206
|
+
return parts;
|
|
207
|
+
}
|
|
208
|
+
registerLanguageServer(Language.RUBY_SOLARGRAPH, SolargraphLanguageServer);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LogLevel } from '../../serena/util/logging.js';
|
|
2
|
+
import { SolidLanguageServer, type LanguageServerConfigLike, type ReferenceInSymbol, type ReferencingSymbolsOptions, type SolidLspSettingsInit } from '../ls.js';
|
|
3
|
+
import { NodeLanguageServerHandler } from '../ls_handler.js';
|
|
4
|
+
export declare class SourceKitLanguageServer extends SolidLanguageServer {
|
|
5
|
+
protected readonly handler: NodeLanguageServerHandler;
|
|
6
|
+
private readonly versionDescription;
|
|
7
|
+
private initialized;
|
|
8
|
+
private firstReferenceDelayApplied;
|
|
9
|
+
private initializationTimestamp;
|
|
10
|
+
constructor(config: LanguageServerConfigLike, loggerLike: {
|
|
11
|
+
level?: number | LogLevel;
|
|
12
|
+
} | null, repositoryRootPath: string, options?: {
|
|
13
|
+
timeout?: number | null;
|
|
14
|
+
solidlspSettings?: SolidLspSettingsInit;
|
|
15
|
+
});
|
|
16
|
+
start(): this;
|
|
17
|
+
stop(shutdownTimeout?: number): void;
|
|
18
|
+
requestReferencingSymbols(options: ReferencingSymbolsOptions): ReferenceInSymbol[];
|
|
19
|
+
private registerHandlers;
|
|
20
|
+
private initializeLanguageServer;
|
|
21
|
+
private buildInitializeParams;
|
|
22
|
+
private applyInitialReferenceDelay;
|
|
23
|
+
private computeInitialReferenceDelayMs;
|
|
24
|
+
}
|