70 if (!doc)
return result;
72 int charPos =
static_cast<int>(pos.
character);
73 int lineNum =
static_cast<int>(pos.
line);
81 for (
size_t i = 0; i < text.size(); i++) {
82 if (text[i] ==
'\n') {
83 if (lineIdx == lineNum) {
84 lineText = text.substr(start, i - start);
91 if (lineText.empty() && lineIdx == lineNum) {
92 lineText = text.substr(start);
98 if (charPos > 0 && charPos <=
static_cast<int>(lineText.size())) {
99 std::string before = lineText.substr(0, charPos);
100 size_t wordStart = before.size();
101 while (wordStart > 0 && (std::isalnum(before[wordStart - 1]) || before[wordStart - 1] ==
'_'))
103 prefix = before.substr(wordStart);
107 bool afterDot =
false;
108 std::string dotPrefix;
109 if (charPos > 0 && charPos <=
static_cast<int>(lineText.size())) {
110 std::string before = lineText.substr(0, charPos);
111 while (!before.empty() && std::isspace(before.back()))
115 if (!before.empty()) {
117 size_t lastDot = before.rfind(
'.');
118 if (lastDot != std::string::npos) {
120 if (lastDot == before.size() - 1) {
123 std::string beforeDot = before.substr(0, lastDot);
125 size_t idEnd = beforeDot.size();
126 size_t idStart = idEnd;
127 while (idStart > 0 && (std::isalnum(beforeDot[idStart - 1]) || beforeDot[idStart - 1] ==
'_'))
129 if (idStart < idEnd) {
130 dotPrefix = beforeDot.substr(idStart, idEnd - idStart);
134 std::string afterDotStr = before.substr(lastDot + 1);
136 if (prefix.rfind(afterDotStr, 0) == 0 || afterDotStr.rfind(prefix, 0) == 0) {
138 prefix = afterDotStr;
139 std::string beforeDot = before.substr(0, lastDot);
140 size_t idEnd = beforeDot.size();
141 size_t idStart = idEnd;
142 while (idStart > 0 && (std::isalnum(beforeDot[idStart - 1]) || beforeDot[idStart - 1] ==
'_'))
144 if (idStart < idEnd) {
145 dotPrefix = beforeDot.substr(idStart, idEnd - idStart);
154 const std::string beforeCursor = charPos >= 0 && charPos <= static_cast<int>(lineText.size())
155 ? lineText.substr(0, charPos) : lineText;
156 const size_t firstNonSpace = beforeCursor.find_first_not_of(
" \t");
157 const std::string useText = firstNonSpace == std::string::npos ?
"" : beforeCursor.substr(firstNonSpace);
158 if (useText.rfind(
"use", 0) == 0 &&
159 (useText.size() == 3 || std::isspace(
static_cast<unsigned char>(useText[3])))) {
160 const std::string useTail = useText.substr(3);
161 const size_t quote = useTail.find(
'"');
162 if (quote != std::string::npos && useTail.find(
'"', quote + 1) == std::string::npos) {
164 }
else if (quote == std::string::npos) {
168 }
else if (!doc->
parseSucceeded && afterDot && !dotPrefix.empty()) {
174 if (result.
items.empty()) {
182 }
else if (afterDot && !dotPrefix.empty()) {
186 if (result.
items.empty()) {
189 result.
items.insert(result.
items.end(), crossItems.begin(), crossItems.end());
191 result.
items.insert(result.
items.end(), kwCompletions.begin(), kwCompletions.end());
193 }
else if (afterDot) {
198 result.
items.insert(result.
items.end(), crossItems.begin(), crossItems.end());
200 result.
items.insert(result.
items.end(), kwCompletions.begin(), kwCompletions.end());
205 result.
items.insert(result.
items.end(), crossItems.begin(), crossItems.end());
207 result.
items.insert(result.
items.end(), kwCompletions.begin(), kwCompletions.end());
211 std::sort(result.
items.begin(), result.
items.end(),
213 return a.sortText.value_or(a.label) < b.sortText.value_or(b.label);
220 std::vector<CompletionItem> result;
224 std::vector<const Symbol *> matching;
225 for (
auto &sym : doc->
symbols) {
226 if (cursorLine >= 0 &&
230 if (sym.name == wParent) matching.push_back(&sym);
233 const Symbol *bestMatch =
nullptr;
234 if (!matching.empty()) {
235 bestMatch = matching[0];
236 if (cursorLine >= 0 && matching.size() > 1) {
237 for (
auto *m : matching) {
238 if (
static_cast<int>(m->line) <= cursorLine &&
239 static_cast<int>(m->line) >
static_cast<int>(bestMatch->
line)) {
247 const auto &sym = *bestMatch;
248 if (sym.name == wParent) {
250 if (!sym.children.empty()) {
251 for (
auto &child : sym.children) {
253 if (name.rfind(prefix, 0) != 0)
continue;
257 result.push_back(item);
264 if (!result.empty())
return result;
271 for (
auto &sym : doc->
symbols) {
276 if (name.rfind(prefix, 0) != 0)
continue;
281 result.push_back(item);
289 if (cross.name == wParent && !cross.children.empty() &&
291 for (
auto &child : cross.children) {
293 if (name.rfind(prefix, 0) != 0)
continue;
298 result.push_back(item);
300 if (!result.empty())
return result;
306 if (cross.parentName == wParent) {
308 if (name.rfind(prefix, 0) != 0)
continue;
312 result.push_back(item);
441 Document *doc,
const std::string &prefix) {
442 std::vector<CompletionItem> result;
443 std::set<std::string> moduleNames = {
"builtin"};
447 if (!fs::is_directory(root, ec))
continue;
448 for (
const auto &entry : fs::directory_iterator(root, ec)) {
450 const fs::path path = entry.path();
451 if (entry.is_regular_file(ec) && path.extension() ==
".hoshi") {
452 moduleNames.insert(path.stem().string());
453 }
else if (entry.is_directory(ec) && fs::is_regular_file(path /
"index.hoshi", ec)) {
454 moduleNames.insert(path.filename().string());
459 for (
const auto &name : moduleNames) {
460 if (!prefix.empty() && name.rfind(prefix, 0) != 0)
continue;
464 item.
detail =
"use " + name +
" \"" + name +
"\"";
467 result.push_back(std::move(item));
473 Document *doc,
const std::string &pathPrefix) {
474 std::vector<CompletionItem> result;
475 const size_t slash = pathPrefix.find_last_of(
"/\\");
476 const std::string directoryPart = slash == std::string::npos ?
"" : pathPrefix.substr(0, slash + 1);
477 const std::string entryPrefix = slash == std::string::npos ? pathPrefix : pathPrefix.substr(slash + 1);
478 std::set<std::string> entries;
481 const fs::path directory = root / fs::path(directoryPart);
483 if (!fs::is_directory(directory, ec))
continue;
484 for (
const auto &entry : fs::directory_iterator(directory, ec)) {
486 const std::string name = entry.path().filename().string();
487 if (name.empty() || name[0] ==
'.' ||
488 (!entryPrefix.empty() && name.rfind(entryPrefix, 0) != 0)) {
491 if (entry.is_directory(ec)) {
492 entries.insert(name +
"/");
493 }
else if (entry.is_regular_file(ec) && entry.path().extension() ==
".hoshi") {
494 entries.insert(name);
499 for (
const auto &entry : entries) {
501 item.
label = directoryPart + entry;
503 item.
detail = entry.back() ==
'/' ?
"directory" :
"Hoshi module";
506 result.push_back(std::move(item));
537 Document *doc,
const std::string &parent,
const std::string &prefix) {
538 std::vector<CompletionItem> result;
543 std::string importPath;
546 std::string usePattern =
"use " + parent +
" \"";
547 size_t pos = text.find(usePattern);
548 if (pos != std::string::npos) {
549 size_t pathStart = pos + usePattern.size();
550 size_t pathEnd = text.find(
'"', pathStart);
551 if (pathEnd != std::string::npos) {
552 importPath = text.substr(pathStart, pathEnd - pathStart);
557 if (importPath.empty()) {
558 std::string importKw =
"import ";
560 while ((ipos = text.find(importKw, ipos)) != std::string::npos) {
561 size_t fromPos = text.find(
"from \"", ipos);
562 if (fromPos != std::string::npos) {
563 size_t pathStart = fromPos + 6;
564 size_t pathEnd = text.find(
'"', pathStart);
565 if (pathEnd != std::string::npos) {
566 std::string maybePath = text.substr(pathStart, pathEnd - pathStart);
568 if (maybePath == parent ||
569 maybePath.rfind(
"/" + parent) == maybePath.size() - parent.size() - 1 ||
570 maybePath.rfind(
"\\" + parent) == maybePath.size() - parent.size() - 1) {
571 importPath = maybePath;
576 ipos = fromPos != std::string::npos ? fromPos + 1 : ipos + 1;
580 if (importPath.empty()) {
583 std::string letPattern =
"let " + parent +
" = ";
584 size_t letPos = text.find(letPattern);
585 if (letPos == std::string::npos) {
586 letPattern =
"let " + parent +
": ";
587 letPos = text.find(letPattern);
589 if (letPos != std::string::npos) {
590 std::string afterLet = text.substr(letPos + letPattern.size());
592 size_t dotPos = afterLet.find(
'.');
593 std::string typeName, moduleName;
594 if (dotPos != std::string::npos && dotPos < afterLet.find(
'(')) {
596 moduleName = afterLet.substr(0, dotPos);
597 size_t typeStart = dotPos + 1;
598 size_t typeEnd = typeStart;
599 while (typeEnd < afterLet.size() && (std::isalnum(afterLet[typeEnd]) || afterLet[typeEnd] ==
'_'))
601 typeName = afterLet.substr(typeStart, typeEnd - typeStart);
604 while (typeEnd < afterLet.size() && (std::isalnum(afterLet[typeEnd]) || afterLet[typeEnd] ==
'_'))
606 typeName = afterLet.substr(0, typeEnd);
610 if (!moduleName.empty()) {
611 std::string fp = doc->
uri;
612 if (fp.rfind(
"file://", 0) == 0) fp = fp.substr(7);
613 std::string useP =
"use " + moduleName +
" \"";
614 size_t upos = text.find(useP);
615 if (upos != std::string::npos) {
616 size_t ps = upos + useP.size();
617 size_t pe = text.find(
'"', ps);
618 if (pe != std::string::npos) {
620 text.substr(ps, pe - ps), fp);
621 if (!resolvedPath.empty()) {
629 std::string lookupType = typeName;
630 if (std::islower(typeName[0]) && !moduleName.empty()) {
633 if (!irRetType.empty()) {
634 lookupType = irRetType;
640 for (
auto &sym : allSyms) {
643 size_t lastDot = retType.rfind(
'.');
644 if (lastDot != std::string::npos) retType = retType.substr(lastDot + 1);
645 size_t tmpl = retType.find(
'<');
646 if (tmpl != std::string::npos) retType = retType.substr(0, tmpl);
647 while (!retType.empty() && std::isspace(retType.back())) retType.pop_back();
648 if (!retType.empty()) { lookupType = retType;
break; }
655 if (std::isupper(lookupType[0])) {
657 if (!completions.empty())
return completions;
665 std::string filePath = doc->
uri;
666 if (filePath.rfind(
"file://", 0) == 0) {
667 filePath = filePath.substr(7);
670 if (resolvedPath.empty())
return result;
676 for (
const auto &sym : modSymbols) {
677 if (sym.isLocal)
continue;
679 if (!prefix.empty() && name.rfind(prefix, 0) != 0)
continue;
686 result.push_back(item);
694 std::vector<CompletionItem> result;
698 for (
auto &sym : doc->
symbols) {
699 if (sym.name == typeName &&
703 for (
auto &child : sym.children) {
705 if (name.rfind(prefix, 0) != 0)
continue;
710 result.push_back(item);
714 if (sym.parentName == typeName &&
718 if (name.rfind(prefix, 0) != 0)
continue;
723 result.push_back(item);
726 if (found && !result.empty())
return result;
736 for (
auto &cross : allCross) {
737 if (cross.name == typeName &&
740 for (
auto &child : cross.children) {
742 if (name.rfind(prefix, 0) != 0)
continue;
747 result.push_back(item);
750 if (cross.parentName == typeName &&
754 if (name.rfind(prefix, 0) != 0)
continue;
759 result.push_back(item);