Skip to content

[clangd] Add support to rename Objective-C selectors #78872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
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
5 changes: 3 additions & 2 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -844,14 +844,15 @@ void ClangdLSPServer::onWorkspaceSymbol(
}

void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams &Params,
Callback<std::optional<Range>> Reply) {
Callback<PrepareRenameResult> Reply) {
Server->prepareRename(
Params.textDocument.uri.file(), Params.position, /*NewName*/ std::nullopt,
Opts.Rename,
[Reply = std::move(Reply)](llvm::Expected<RenameResult> Result) mutable {
if (!Result)
return Reply(Result.takeError());
return Reply(std::move(Result->Target));
PrepareRenameResult PrepareResult{Result->Target, Result->OldName};
return Reply(std::move(PrepareResult));
});
}

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/ClangdLSPServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
void onWorkspaceSymbol(const WorkspaceSymbolParams &,
Callback<std::vector<SymbolInformation>>);
void onPrepareRename(const TextDocumentPositionParams &,
Callback<std::optional<Range>>);
Callback<PrepareRenameResult>);
void onRename(const RenameParams &, Callback<WorkspaceEdit>);
void onHover(const TextDocumentPositionParams &,
Callback<std::optional<Hover>>);
Expand Down
3 changes: 1 addition & 2 deletions clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,8 +578,7 @@ void ClangdServer::prepareRename(PathRef File, Position Pos,
// prepareRename is latency-sensitive: we don't query the index, as we
// only need main-file references
auto Results =
clangd::rename({Pos, NewName.value_or("__clangd_rename_placeholder"),
InpAST->AST, File, /*FS=*/nullptr,
clangd::rename({Pos, NewName, InpAST->AST, File, /*FS=*/nullptr,
/*Index=*/nullptr, RenameOpts});
if (!Results) {
// LSP says to return null on failure, but that will result in a generic
Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,10 @@ llvm::json::Value toJSON(const DocumentSymbol &S) {
return std::move(Result);
}

llvm::json::Value toJSON(const PrepareRenameResult &R) {
return llvm::json::Object{{"range", R.range}, {"placeholder", R.placeholder}};
}

llvm::json::Value toJSON(const WorkspaceEdit &WE) {
llvm::json::Object Result;
if (WE.changes) {
Expand Down
11 changes: 11 additions & 0 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,17 @@ struct CodeActionParams {
};
bool fromJSON(const llvm::json::Value &, CodeActionParams &, llvm::json::Path);

struct PrepareRenameResult {
/// The range of the string to rename.
Range range;
/// A placeholder text of the string content to be renamed.
///
/// This is useful to populate the rename field with an Objective-C selector
/// name (eg. `performAction:with:`) when renaming Objective-C methods.
std::string placeholder;
};
llvm::json::Value toJSON(const PrepareRenameResult &WE);

/// The edit should either provide changes or documentChanges. If the client
/// can handle versioned document edits and if documentChanges are present,
/// the latter are preferred over changes.
Expand Down
18 changes: 9 additions & 9 deletions clang-tools-extra/clangd/SourceCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -625,16 +625,16 @@ llvm::StringMap<unsigned> collectIdentifiers(llvm::StringRef Content,
return Identifiers;
}

std::vector<Range> collectIdentifierRanges(llvm::StringRef Identifier,
llvm::StringRef Content,
const LangOptions &LangOpts) {
std::vector<Range>
collectIdentifierRanges(llvm::StringRef Identifier,
const syntax::UnexpandedTokenBuffer &Tokens) {
std::vector<Range> Ranges;
lex(Content, LangOpts,
[&](const syntax::Token &Tok, const SourceManager &SM) {
if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
return;
Ranges.push_back(halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
});
const SourceManager &SM = Tokens.sourceManager();
for (const syntax::Token &Tok : Tokens.tokens()) {
if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier)
continue;
Ranges.push_back(halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
}
return Ranges;
}

Expand Down
6 changes: 3 additions & 3 deletions clang-tools-extra/clangd/SourceCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,9 @@ llvm::StringMap<unsigned> collectIdentifiers(llvm::StringRef Content,
const format::FormatStyle &Style);

/// Collects all ranges of the given identifier in the source code.
std::vector<Range> collectIdentifierRanges(llvm::StringRef Identifier,
llvm::StringRef Content,
const LangOptions &LangOpts);
std::vector<Range>
collectIdentifierRanges(llvm::StringRef Identifier,
const syntax::UnexpandedTokenBuffer &Tokens);

/// Collects words from the source code.
/// Unlike collectIdentifiers:
Expand Down
27 changes: 24 additions & 3 deletions clang-tools-extra/clangd/index/SymbolCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,38 @@ std::optional<RelationKind> indexableRelation(const index::SymbolRelation &R) {
bool isSpelled(SourceLocation Loc, const NamedDecl &ND) {
auto Name = ND.getDeclName();
const auto NameKind = Name.getNameKind();
if (NameKind != DeclarationName::Identifier &&
NameKind != DeclarationName::CXXConstructorName)
bool PrefixComparison;
switch (NameKind) {
case DeclarationName::Identifier:
case DeclarationName::CXXConstructorName:
case DeclarationName::ObjCZeroArgSelector:
PrefixComparison = false;
break;
case DeclarationName::ObjCOneArgSelector:
case DeclarationName::ObjCMultiArgSelector:
PrefixComparison = true;
break;
default:
return false;
}
const auto &AST = ND.getASTContext();
const auto &SM = AST.getSourceManager();
const auto &LO = AST.getLangOpts();
clang::Token Tok;
if (clang::Lexer::getRawToken(Loc, Tok, SM, LO))
return false;
auto StrName = Name.getAsString();
return clang::Lexer::getSpelling(Tok, SM, LO) == StrName;
std::string LexerSpelling = clang::Lexer::getSpelling(Tok, SM, LO);
if (PrefixComparison) {
// The lexer spelling at the source location is only the first label of an
// Objective-C selector, eg. if `StrName` is `performAction:with:`, then the
// token at the requested location is `performAction`. Re-building the
// entire selector from the lexer is too complicated here, so just perform
// a prefix comparison.
return StringRef(StrName).starts_with(LexerSpelling);
} else {
return StrName == LexerSpelling;
}
}
} // namespace

Expand Down
Loading