From 024140ba0b00c0dd1f46fb2ee253fce9f1c870dc Mon Sep 17 00:00:00 2001 From: liushitong Date: Mon, 4 Aug 2025 19:02:31 +0800 Subject: [PATCH] [LSP]: add getColAndLineByOffset Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICQ4CY Signed-off-by: liushitong Change-Id: Ifaa1bbf2791f1eb45834a42652b8a2ec2acf33cf --- ets2panda/bindings/native/src/lsp.cpp | 23 +++++++- .../src/common/Es2pandaNativeModule.ts | 12 +++++ ets2panda/bindings/src/lsp/lspNode.ts | 10 ++++ ets2panda/bindings/src/lsp/lsp_helper.ts | 8 ++- ets2panda/bindings/test/cases.ts | 4 ++ .../test/expected/getColAndLineByOffset.json | 6 +++ .../getColAndLineByOffset1.ets | 53 +++++++++++++++++++ ets2panda/lexer/token/sourceLocation.cpp | 43 +++++++++++++++ ets2panda/lexer/token/sourceLocation.h | 1 + ets2panda/lsp/include/api.h | 1 + ets2panda/lsp/src/api.cpp | 7 +++ .../unit/lsp/get_offset_by_col_and_line.cpp | 18 ++++++- 12 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 ets2panda/bindings/test/expected/getColAndLineByOffset.json create mode 100644 ets2panda/bindings/test/testcases/getColAndLineByOffset/getColAndLineByOffset1.ets diff --git a/ets2panda/bindings/native/src/lsp.cpp b/ets2panda/bindings/native/src/lsp.cpp index 1054d5b948..f4f29b2440 100644 --- a/ets2panda/bindings/native/src/lsp.cpp +++ b/ets2panda/bindings/native/src/lsp.cpp @@ -1838,4 +1838,25 @@ KNativePointer impl_getDefinitionDataFromNode(KNativePointer astNodePtr, KString LSPAPI const *impl = GetImpl(); return new DefinitionInfo(impl->getDefinitionDataFromNode(ast, nodeNamePtr.data())); } -TS_INTEROP_2(getDefinitionDataFromNode, KNativePointer, KNativePointer, KStringPtr) \ No newline at end of file +TS_INTEROP_2(getDefinitionDataFromNode, KNativePointer, KNativePointer, KStringPtr) + +KInt impl_getSourceLocationLine(KNativePointer locationPtr) +{ + auto *location = reinterpret_cast *>(locationPtr); + return location->first; +} +TS_INTEROP_1(getSourceLocationLine, KInt, KNativePointer) + +KInt impl_getSourceLocationColumn(KNativePointer locationPtr) +{ + auto *location = reinterpret_cast *>(locationPtr); + return location->second; +} +TS_INTEROP_1(getSourceLocationColumn, KInt, KNativePointer) + +KNativePointer impl_getColAndLineByOffset(KStringPtr &sourceCodePtr, KInt offset) +{ + LSPAPI const *impl = GetImpl(); + return new std::pair(impl->getColAndLineByOffset(sourceCodePtr.data(), offset)); +} +TS_INTEROP_2(getColAndLineByOffset, KNativePointer, KStringPtr, KInt) diff --git a/ets2panda/bindings/src/common/Es2pandaNativeModule.ts b/ets2panda/bindings/src/common/Es2pandaNativeModule.ts index 1693d391f6..e667da211d 100644 --- a/ets2panda/bindings/src/common/Es2pandaNativeModule.ts +++ b/ets2panda/bindings/src/common/Es2pandaNativeModule.ts @@ -630,6 +630,14 @@ export class Es2pandaNativeModule { throw new Error('Not implemented'); } + _getSourceLocationLine(ptr: KNativePointer): KInt { + throw new Error('Not implemented'); + } + + _getSourceLocationColumn(ptr: KNativePointer): KInt { + throw new Error('Not implemented'); + } + _getHighlightTextSpan(ptr: KNativePointer): KPtr { throw new Error('Not implemented'); } @@ -948,6 +956,10 @@ export class Es2pandaNativeModule { throw new Error('Not implemented'); } + _getColAndLineByOffset(sourceCode: String, offset: KInt): KInt { + throw new Error('Not implemented'); + } + _MemInitialize(pandaLibPath: KStringPtr): void { throw new Error('Not implemented'); } diff --git a/ets2panda/bindings/src/lsp/lspNode.ts b/ets2panda/bindings/src/lsp/lspNode.ts index 492350d708..33650fc8e4 100644 --- a/ets2panda/bindings/src/lsp/lspNode.ts +++ b/ets2panda/bindings/src/lsp/lspNode.ts @@ -229,6 +229,16 @@ export class LspTextSpan extends LspNode { readonly length: KInt; } +export class LspSourceLocation extends LspNode { + constructor(peer: KNativePointer) { + super(peer); + this.line = global.es2panda._getSourceLocationLine(peer); + this.column = global.es2panda._getSourceLocationColumn(peer); + } + readonly line: number; + readonly column: number; +} + export interface TextSpan { start: KInt; length: KInt; diff --git a/ets2panda/bindings/src/lsp/lsp_helper.ts b/ets2panda/bindings/src/lsp/lsp_helper.ts index c715a854bd..a90676decb 100644 --- a/ets2panda/bindings/src/lsp/lsp_helper.ts +++ b/ets2panda/bindings/src/lsp/lsp_helper.ts @@ -47,7 +47,8 @@ import { LspRenameLocation, LspRenameInfoType, LspRenameInfoSuccess, - LspRenameInfoFailure + LspRenameInfoFailure, + LspSourceLocation } from './lspNode'; import { passStringArray, unpackString } from '../common/private'; import { Es2pandaContextState } from '../generated/Es2pandaEnums'; @@ -243,6 +244,11 @@ export class Lsp { return global.es2panda._getOffsetByColAndLine(sourceCode, line, column); } + getColAndLineByOffset(filename: String, offset: number): LspSourceLocation { + const sourceCode = this.getFileSource(filename.valueOf()); + return new LspSourceLocation(global.es2panda._getColAndLineByOffset(sourceCode, offset)); + } + getDefinitionAtPosition(filename: String, offset: number, nodeInfos?: NodeInfo[]): LspDefinitionData { if (nodeInfos) { return this.getDefinitionAtPositionByNodeInfos(filename, nodeInfos); diff --git a/ets2panda/bindings/test/cases.ts b/ets2panda/bindings/test/cases.ts index 583e8a93d3..cd9a6dd119 100644 --- a/ets2panda/bindings/test/cases.ts +++ b/ets2panda/bindings/test/cases.ts @@ -187,6 +187,10 @@ export const basicCases: TestCases = { expectedFilePath: resolveTestPath('test/expected/getOffsetByColAndLine.json'), '1': [resolveTestPath('test/testcases/getOffsetByColAndLine/getOffsetByColAndLine1.ets'), 51, 14] }, + getColAndLineByOffset: { + expectedFilePath: resolveTestPath('test/expected/getColAndLineByOffset.json'), + '1': [resolveTestPath('test/testcases/getColAndLineByOffset/getColAndLineByOffset1.ets'), 1035] + }, entry: { expectedFilePath: '', '1': [resolveTestPath('test/testcases/entry/Index.ets'), 615] diff --git a/ets2panda/bindings/test/expected/getColAndLineByOffset.json b/ets2panda/bindings/test/expected/getColAndLineByOffset.json new file mode 100644 index 0000000000..775479eeba --- /dev/null +++ b/ets2panda/bindings/test/expected/getColAndLineByOffset.json @@ -0,0 +1,6 @@ +{ + "1": { + "line": 35, + "column": 8 + } +} diff --git a/ets2panda/bindings/test/testcases/getColAndLineByOffset/getColAndLineByOffset1.ets b/ets2panda/bindings/test/testcases/getColAndLineByOffset/getColAndLineByOffset1.ets new file mode 100644 index 0000000000..aece61fb54 --- /dev/null +++ b/ets2panda/bindings/test/testcases/getColAndLineByOffset/getColAndLineByOffset1.ets @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// comment of line + +import { memo, __memo_context_type, __memo_id_type } from '@ohos.arkui.stateManagement' +import { + Entry + Text, + TextAttributes, + Column, + Component, + Button, + ButtonAttribute, + ClickEvent, + UserView +} from '@ohos.arkui.component' +import { State, MutableSate, stateOf, observableProxy } from '@ohos.arkui.stateManagement' +import hilog from '@ohos.hilog' + +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + } + .onClick(() => { + console.info("hello, :") + console.log(result.toString()) + }) + .width('100%') + } + .height('100%') + } +} diff --git a/ets2panda/lexer/token/sourceLocation.cpp b/ets2panda/lexer/token/sourceLocation.cpp index cb094910fc..ce80ab7c21 100644 --- a/ets2panda/lexer/token/sourceLocation.cpp +++ b/ets2panda/lexer/token/sourceLocation.cpp @@ -101,6 +101,49 @@ SourceLocation LineIndex::GetLocation(SourcePosition pos) const noexcept return SourceLocation(line + 1, col + 1, pos.Program()); } +std::pair LineIndex::GetLocation(size_t offset) const noexcept +{ + if (entries_.empty() || offset == 0) { + return {1, 1}; + } + + size_t line = 0; + size_t left = 0; + size_t right = entries_.size(); + + while (left < right) { + size_t mid = left + (right - left) / 2; + if (entries_[mid].lineStart <= offset) { + line = mid; + left = mid + 1; + } else { + right = mid; + } + } + + if (line >= entries_.size()) { + return {entries_.size() + 1, 1}; + } + + const auto &entry = entries_[line]; + size_t remainingOffset = offset - entry.lineStart; + size_t col = 0; + + for (const auto &range : entry.ranges) { + size_t rangeBytes = range.cnt * range.byteSize; + + if (remainingOffset < rangeBytes) { + col += remainingOffset / range.byteSize; + break; + } + + col += range.cnt; + remainingOffset -= rangeBytes; + } + + return {line + 1, col + 1}; +} + size_t LineIndex::GetOffset(SourceLocation loc) const noexcept { ES2PANDA_ASSERT(loc.line != 0); diff --git a/ets2panda/lexer/token/sourceLocation.h b/ets2panda/lexer/token/sourceLocation.h index 491199d36a..b0bf909e3f 100644 --- a/ets2panda/lexer/token/sourceLocation.h +++ b/ets2panda/lexer/token/sourceLocation.h @@ -207,6 +207,7 @@ public: ~LineIndex() = default; SourceLocation GetLocation(SourcePosition pos) const noexcept; + std::pair GetLocation(size_t offset) const noexcept; size_t GetOffset(SourceLocation loc) const noexcept; private: diff --git a/ets2panda/lsp/include/api.h b/ets2panda/lsp/include/api.h index 925159082e..8feb3be682 100644 --- a/ets2panda/lsp/include/api.h +++ b/ets2panda/lsp/include/api.h @@ -546,6 +546,7 @@ typedef struct LSPAPI { InlayHintList (*provideInlayHints)(es2panda_Context *context, const TextSpan *span); SignatureHelpItems (*getSignatureHelpItems)(es2panda_Context *context, size_t position); size_t (*getOffsetByColAndLine)(const std::string &sourceCode, size_t line, size_t column); + std::pair (*getColAndLineByOffset)(const std::string &sourceCode, size_t offset); std::vector (*getCodeFixesAtPosition)(es2panda_Context *context, size_t start_position, size_t end_position, std::vector &errorCodes, CodeFixOptions &codeFixOptions); diff --git a/ets2panda/lsp/src/api.cpp b/ets2panda/lsp/src/api.cpp index 50fb219f82..8f942b3daa 100644 --- a/ets2panda/lsp/src/api.cpp +++ b/ets2panda/lsp/src/api.cpp @@ -437,6 +437,12 @@ size_t GetOffsetByColAndLine(const std::string &sourceCode, size_t line, size_t return index.GetOffset(lexer::SourceLocation(line, column, nullptr)); } +std::pair GetColAndLineByOffset(const std::string &sourceCode, size_t offset) +{ + auto index = lexer::LineIndex(util::StringView(sourceCode)); + return index.GetLocation(offset); +} + std::vector GetCodeFixesAtPosition(es2panda_Context *context, size_t startPosition, size_t endPosition, std::vector &errorCodes, CodeFixOptions &codeFixOptions) @@ -543,6 +549,7 @@ LSPAPI g_lspImpl = {GetDefinitionAtPosition, ProvideInlayHints, GetSignatureHelpItems, GetOffsetByColAndLine, + GetColAndLineByOffset, GetCodeFixesAtPosition, GetCombinedCodeFix, GetNameOrDottedNameSpan, diff --git a/ets2panda/test/unit/lsp/get_offset_by_col_and_line.cpp b/ets2panda/test/unit/lsp/get_offset_by_col_and_line.cpp index 008c9285db..cfa26d3737 100644 --- a/ets2panda/test/unit/lsp/get_offset_by_col_and_line.cpp +++ b/ets2panda/test/unit/lsp/get_offset_by_col_and_line.cpp @@ -22,7 +22,7 @@ namespace { class LSPOffsetTests : public LSPAPITests {}; -TEST_F(LSPAPITests, getOffsetComment) +TEST_F(LSPOffsetTests, getOffsetComment) { LSPAPI const *lspApi = GetImpl(); const size_t line = 4; @@ -37,4 +37,20 @@ let aaa = 'default string'; ASSERT_EQ(res, expectedOffset); } +TEST_F(LSPOffsetTests, getLineAndColByOffset) +{ + LSPAPI const *lspApi = GetImpl(); + const size_t offset = 28; + auto res = lspApi->getColAndLineByOffset(R"delimiter( +// comment of line 2 + +let aaa = 'default string'; +)delimiter", + offset); + const size_t expectedLine = 4; + const size_t expectedCol = 6; + ASSERT_EQ(res.first, expectedLine); + ASSERT_EQ(res.second, expectedCol); +} + } // namespace -- Gitee