Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mozuku-lsp/include/lsp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class LSPServer {
json onSemanticTokensFull(const json &id, const json &params);
json onSemanticTokensRange(const json &id, const json &params);
json onHover(const json &id, const json &params);
json onSelectionRange(const json &id, const json &params);

DocumentState &ensureDocument(const std::string &uri);
DocumentState *findDocument(const std::string &uri);
Expand Down
157 changes: 156 additions & 1 deletion mozuku-lsp/src/lsp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "comment_extractor.hpp"
#include "mozuku/core/debug.hpp"
#include "pos_analyzer.hpp"
#include "text_processor.hpp"
#include "utf16.hpp"
#include "wikipedia.hpp"

Expand Down Expand Up @@ -109,6 +110,8 @@ void LSPServer::handle(const json &req) {
req.value("params", json::object())));
} else if (method == "textDocument/hover") {
reply(onHover(req["id"], req.value("params", json::object())));
} else if (method == "textDocument/selectionRange") {
reply(onSelectionRange(req["id"], req.value("params", json::object())));
} else if (method == "shutdown") {
reply(json{{"jsonrpc", "2.0"}, {"id", req["id"]}, {"result", nullptr}});
} else if (method == "exit") {
Expand Down Expand Up @@ -244,7 +247,8 @@ json LSPServer::onInitialize(const json &id, const json &params) {
{"tokenModifiers", tokenModifiers_}}},
{"range", true},
{"full", true}}},
{"hoverProvider", true}}}}}};
{"hoverProvider", true},
{"selectionRangeProvider", true}}}}}};
}

void LSPServer::onInitialized() {
Expand Down Expand Up @@ -473,6 +477,157 @@ json LSPServer::onHover(const json &id, const json &params) {
return json{{"jsonrpc", "2.0"}, {"id", id}, {"result", nullptr}};
}

json LSPServer::onSelectionRange(const json &id, const json &params) {
std::string uri = params["textDocument"]["uri"];
auto *document = findDocument(uri);
if (!document) {
return json{{"jsonrpc", "2.0"}, {"id", id}, {"result", json::array()}};
}

if (!params.contains("positions") || !params["positions"].is_array()) {
return json{{"jsonrpc", "2.0"}, {"id", id}, {"result", json::array()}};
}

// トークンキャッシュを確保
if (!document->tokensCached) {
if (!analyzer_->isInitialized()) {
analyzer_->initialize(config_);
}
auto prepared = prepareDocument(*document);
document->tokens = analyzer_->analyzeText(prepared.analysisText);
document->tokensCached = true;
}

const std::string &text = document->text;
TextOffsetMapper mapper(text);

// 句読点区切りの文境界を取得
auto sentences = MoZuku::text::TextProcessor::splitIntoSentences(text);

json result = json::array();

for (const auto &posJson : params["positions"]) {
int line = posJson["line"];
int character = posJson["character"];
size_t byteOffset = mapper.positionToByteOffset(line, character);

// === Level 3 (最外): 段落 (改行区切り) ===
size_t paraStart = 0;
for (size_t i = byteOffset; i > 0; --i) {
if (text[i - 1] == '\n') {
paraStart = i;
break;
}
}

size_t paraEnd = text.size();
for (size_t i = byteOffset; i < text.size(); ++i) {
if (text[i] == '\n') {
paraEnd = i;
break;
}
}

Position paraStartPos = mapper.byteOffsetToPosition(paraStart);
Position paraEndPos = mapper.byteOffsetToPosition(paraEnd);

// 最外の段落レンジから構築開始
json selRange = {
{"range",
{{"start",
{{"line", paraStartPos.line},
{"character", paraStartPos.character}}},
{"end",
{{"line", paraEndPos.line}, {"character", paraEndPos.character}}}}}};

// === Level 2: 行/文 (句読点区切り) ===
for (const auto &sentence : sentences) {
if (byteOffset >= sentence.start && byteOffset < sentence.end) {
// 末尾の改行・CRを除外した文末位置を算出
size_t sentEnd = sentence.end;
if (sentEnd > 0 && sentEnd <= text.size() &&
text[sentEnd - 1] == '\n') {
sentEnd--;
}
if (sentEnd > 0 && sentEnd <= text.size() &&
text[sentEnd - 1] == '\r') {
sentEnd--;
}

// 段落と異なる場合のみ文レベルを追加
if (sentence.start != paraStart || sentEnd != paraEnd) {
Position sentStartPos = mapper.byteOffsetToPosition(sentence.start);
Position sentEndPos = mapper.byteOffsetToPosition(sentEnd);

selRange = {{"range",
{{"start",
{{"line", sentStartPos.line},
{"character", sentStartPos.character}}},
{"end",
{{"line", sentEndPos.line},
{"character", sentEndPos.character}}}}},
{"parent", selRange}};
}
break;
}
}

// === Level 1 (最内): 形態素 ===
// カーソルが形態素境界(終端)にある場合も直前トークンを選べるようにする
const TokenData *selectedToken = nullptr;
const TokenData *lineFirstToken = nullptr;
const TokenData *lineLastToken = nullptr;

for (const auto &token : document->tokens) {
if (token.line != line) {
continue;
}

if (!lineFirstToken || token.startChar < lineFirstToken->startChar) {
lineFirstToken = &token;
}
if (!lineLastToken || token.endChar > lineLastToken->endChar) {
lineLastToken = &token;
}

// 通常ケース: トークン内部
if (character >= token.startChar && character < token.endChar) {
selectedToken = &token;
break;
}

// 境界ケース: トークン終端ちょうど
if (!selectedToken && character == token.endChar) {
selectedToken = &token;
}
}

// 行頭/行末などで境界上にある場合のフォールバック
if (!selectedToken) {
if (lineFirstToken && character <= lineFirstToken->startChar) {
selectedToken = lineFirstToken;
} else if (lineLastToken && character >= lineLastToken->endChar) {
selectedToken = lineLastToken;
}
}

if (selectedToken) {
selRange = {{"range",
{{"start",
{{"line", selectedToken->line},
{"character", selectedToken->startChar}}},
{"end",
{{"line", selectedToken->line},
{"character", selectedToken->endChar}}}}},
{"parent", selRange}};
}

result.push_back(selRange);
}

return json{{"jsonrpc", "2.0"}, {"id", id}, {"result", result}};
}

void LSPServer::analyzeAndPublish(const std::string &uri) {
auto &document = ensureDocument(uri);
const std::string &text = document.text;
Expand Down
14 changes: 8 additions & 6 deletions vim-mozuku/autoload/mozuku.vim
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ function! s:command_candidates(command_name) abort
endif

let l:exe = s:exe_name(a:command_name)
let l:repo_root = s:repo_root()

" 開発中バイナリを優先: ワークスペース出力 -> 拡張同梱 -> システムPATH
call s:add_unique(l:candidates, l:repo_root . '/mozuku-lsp/build/' . l:exe)
call s:add_unique(l:candidates, l:repo_root . '/mozuku-lsp/build/install/bin/' . l:exe)
call s:add_unique(l:candidates, l:repo_root . '/vscode-mozuku/bin/' . l:exe)
call s:add_unique(l:candidates, l:repo_root . '/build/' . l:exe)
call s:add_unique(l:candidates, l:repo_root . '/build/install/bin/' . l:exe)

for l:name in [a:command_name, l:exe]
let l:resolved = exepath(l:name)
Expand Down Expand Up @@ -207,12 +215,6 @@ function! s:command_candidates(command_name) abort
endfor
endif

let l:repo_root = s:repo_root()
call s:add_unique(l:candidates, l:repo_root . '/build/install/bin/' . l:exe)
call s:add_unique(l:candidates, l:repo_root . '/build/' . l:exe)
call s:add_unique(l:candidates, l:repo_root . '/mozuku-lsp/build/install/bin/' . l:exe)
call s:add_unique(l:candidates, l:repo_root . '/mozuku-lsp/build/' . l:exe)

return l:candidates
endfunction

Expand Down
38 changes: 21 additions & 17 deletions vscode-mozuku/src/server-discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,6 @@ export function resolveServerPath(
addResolvedPath("環境変数 MOZUKU_LSP", envValue);
}

addCommandSearch(
"設定済みコマンド",
configuredValue && !hasPathSep(configuredValue)
? configuredValue
: undefined,
);
addCommandSearch(
"環境変数 MOZUKU_LSP",
envValue && !hasPathSep(envValue) ? envValue : undefined,
);
addCommandSearch("デフォルトコマンド", exeName);

add(
"パッケージ済み",
vscode.Uri.joinPath(ctx.extensionUri, "bin", exeName).fsPath,
Expand All @@ -147,11 +135,11 @@ export function resolveServerPath(
);

if (workspaceRoot) {
// 開発ワークスペースでは nested `mozuku-lsp/build` が最新になりやすいので優先
add(
"ワークスペース-install",
path.join(workspaceRoot, "build", "install", "bin", exeName),
"ワークスペース-build",
path.join(workspaceRoot, "mozuku-lsp", "build", exeName),
);
add("ワークスペース-build", path.join(workspaceRoot, "build", exeName));
add(
"ワークスペース-install",
path.join(
Expand All @@ -163,10 +151,12 @@ export function resolveServerPath(
exeName,
),
);

add(
"ワークスペース-build",
path.join(workspaceRoot, "mozuku-lsp", "build", exeName),
"ワークスペース-install",
path.join(workspaceRoot, "build", "install", "bin", exeName),
);
add("ワークスペース-build", path.join(workspaceRoot, "build", exeName));
}

add(
Expand All @@ -186,6 +176,20 @@ export function resolveServerPath(
path.join(extensionRoot, "..", "mozuku-lsp", "build", exeName),
);

// PATH 探索は最後: システムにインストール済みの古いバイナリより、
// ワークスペース/拡張同梱の開発中バイナリを優先する
addCommandSearch(
"設定済みコマンド",
configuredValue && !hasPathSep(configuredValue)
? configuredValue
: undefined,
);
addCommandSearch(
"環境変数 MOZUKU_LSP",
envValue && !hasPathSep(envValue) ? envValue : undefined,
);
addCommandSearch("デフォルトコマンド", exeName);

for (const candidate of candidates) {
if (fs.existsSync(candidate.path)) {
if (isDebug) {
Expand Down
Loading