1+ import * as vscode from 'vscode' ;
2+ import { MetadataView } from '../metadataView' ;
3+ import { MetadataItem } from '../model/metadataItem' ;
4+ import { TreeItem } from '../ConfigurationFormats/utils' ;
5+
6+ export class QuickNavigateMetadataCommand {
7+ private metadataView : MetadataView ;
8+
9+ constructor ( metadataView : MetadataView ) {
10+ this . metadataView = metadataView ;
11+ }
12+
13+ public async execute ( ) {
14+ // Получаем все метаданные из просмотрщика
15+ const allMetadata = await this . getAllMetadataItems ( ) ;
16+
17+ if ( ! allMetadata || allMetadata . length === 0 ) {
18+ vscode . window . showInformationMessage ( 'Метаданные не найдены. Убедитесь, что открыт проект 1С.' ) ;
19+ return ;
20+ }
21+
22+ // Создаем интерфейс быстрого выбора
23+ const quickPick = vscode . window . createQuickPick < MetadataQuickPickItem > ( ) ;
24+ quickPick . placeholder = 'Введите часть названия объекта метаданных (например, "общего назначения")' ;
25+ quickPick . matchOnDescription = true ;
26+ quickPick . matchOnDetail = true ;
27+
28+ // Начальный список всех элементов
29+ const allItems = allMetadata . map ( item => this . createQuickPickItem ( item ) ) ;
30+ quickPick . items = allItems ;
31+
32+ // Обработчик изменения текста поиска
33+ quickPick . onDidChangeValue ( ( ) => {
34+ if ( quickPick . value . length > 0 ) {
35+ const searchTerms = quickPick . value . toLowerCase ( ) . split ( ' ' ) ;
36+ quickPick . items = allItems . filter ( item => {
37+ return this . matchesSearchTerms ( item , searchTerms ) ;
38+ } ) ;
39+ } else {
40+ quickPick . items = allItems ;
41+ }
42+ } ) ;
43+
44+ // Обработчик выбора элемента
45+ quickPick . onDidAccept ( async ( ) => {
46+ const selectedItem = quickPick . selectedItems [ 0 ] ;
47+ if ( selectedItem ) {
48+ quickPick . hide ( ) ;
49+
50+ // Находим элемент метаданных, который был выбран
51+ const metadataItem = selectedItem . metadata ;
52+
53+ // Открываем элемент в просмотрщике метаданных
54+ await this . openMetadataItem ( metadataItem ) ;
55+ }
56+ } ) ;
57+
58+ // Показываем интерфейс быстрого выбора
59+ quickPick . show ( ) ;
60+ }
61+
62+ private async getAllMetadataItems ( ) : Promise < MetadataItem [ ] > {
63+ // Получаем все элементы метаданных
64+ const treeItems = await this . getTreeItems ( ) ;
65+ return this . convertTreeItemsToMetadataItems ( treeItems ) ;
66+ }
67+
68+ private async getTreeItems ( ) : Promise < TreeItem [ ] > {
69+ // Проверяем наличие dataProvider в metadataView
70+ if ( ! this . metadataView . dataProvider ) {
71+ return [ ] ;
72+ }
73+
74+ // Получаем корневые элементы через dataProvider
75+ const rootItems = await this . metadataView . dataProvider . getChildren ( ) ;
76+ if ( ! rootItems || rootItems . length === 0 ) {
77+ return [ ] ;
78+ }
79+
80+ // Ищем конфигурации (обычно первый элемент)
81+ const configurationsItem = rootItems [ 0 ] ;
82+ if ( ! configurationsItem ) {
83+ return [ ] ;
84+ }
85+
86+ // Получаем дочерние элементы конфигураций
87+ const configChildren = await this . metadataView . dataProvider . getChildren ( configurationsItem ) ;
88+ return configChildren || [ ] ;
89+ }
90+
91+ private convertTreeItemsToMetadataItems ( items : TreeItem [ ] , parent ?: MetadataItem ) : MetadataItem [ ] {
92+ const result : MetadataItem [ ] = [ ] ;
93+
94+ items . forEach ( item => {
95+ // Преобразуем TreeItem в MetadataItem
96+ const metadataItem : MetadataItem = {
97+ id : item . id || '' ,
98+ name : typeof item . label === 'string' ? item . label : item . label ?. label || '' ,
99+ type : item . contextValue || '' ,
100+ file : item . path ,
101+ parent : parent
102+ } ;
103+
104+ result . push ( metadataItem ) ;
105+
106+ // Рекурсивно обрабатываем дочерние элементы
107+ if ( item . children && item . children . length > 0 ) {
108+ const childItems = this . convertTreeItemsToMetadataItems ( item . children , metadataItem ) ;
109+ metadataItem . children = childItems ;
110+ result . push ( ...childItems ) ;
111+ }
112+ } ) ;
113+
114+ return result ;
115+ }
116+
117+ private createQuickPickItem ( metadata : MetadataItem ) : MetadataQuickPickItem {
118+ // Формируем путь для отображения
119+ const path = this . getMetadataPath ( metadata ) ;
120+
121+ return {
122+ label : metadata . name ,
123+ description : path ,
124+ detail : metadata . type ,
125+ metadata : metadata
126+ } ;
127+ }
128+
129+ private getMetadataPath ( metadata : MetadataItem ) : string {
130+ // Формируем путь в виде "Тип\Имя\Подтип"
131+ let current : MetadataItem | undefined = metadata ;
132+ const pathParts : string [ ] = [ ] ;
133+
134+ while ( current ) {
135+ pathParts . unshift ( current . name ) ;
136+ current = current . parent ;
137+ }
138+
139+ return pathParts . join ( '\\' ) ;
140+ }
141+
142+ private matchesSearchTerms ( item : MetadataQuickPickItem , searchTerms : string [ ] ) : boolean {
143+ // Собираем все возможные варианты названия
144+ const itemLabel = item . label . toLowerCase ( ) ;
145+ const itemDescription = item . description ?. toLowerCase ( ) || '' ;
146+ const itemDetail = item . detail ?. toLowerCase ( ) || '' ;
147+
148+ // Разбиваем название метаданных на части (например, "ОбщегоНазначенияБТС" -> "общего", "назначения", "бтс")
149+ const nameParts : string [ ] = [ ] ;
150+
151+ // Разбиваем CamelCase строки
152+ const splitCamelCase = ( text : string ) : string [ ] => {
153+ // Преобразуем кириллицу из CamelCase в отдельные слова
154+ return text . replace ( / ( [ а - я А - Я ] ) (? = [ А - Я ] ) / g, '$1 ' ) . toLowerCase ( ) . split ( ' ' ) ;
155+ } ;
156+
157+ // Получаем части имени из различных источников
158+ const labelParts = splitCamelCase ( item . label ) ;
159+ nameParts . push ( ...labelParts ) ;
160+
161+ // Добавляем полное имя метаданных (например "CommonModule.ОбщегоНазначения")
162+ if ( itemDescription . includes ( '\\' ) ) {
163+ const parts = itemDescription . split ( '\\' ) ;
164+ for ( const part of parts ) {
165+ // Разбиваем каждую часть пути на слова
166+ nameParts . push ( ...splitCamelCase ( part ) ) ;
167+
168+ // Добавляем также полные пути для поиска
169+ nameParts . push ( part ) ;
170+ }
171+ }
172+
173+ // Добавляем тип метаданных (для поиска по типу)
174+ if ( itemDetail ) {
175+ nameParts . push ( itemDetail ) ;
176+ }
177+
178+ // Собираем все строки для поиска
179+ const textToSearch = [
180+ // Полное название элемента
181+ itemLabel ,
182+ // Все части для сопоставления с сокращениями
183+ ...nameParts ,
184+ // Полный путь
185+ itemDescription ,
186+ // Тип
187+ itemDetail
188+ ] . join ( ' ' ) . toLowerCase ( ) ;
189+
190+ // Проверяем, содержится ли каждый поисковый термин в строке для поиска
191+ // или соответствует ли он началу какой-либо части названия (для сокращений)
192+ return searchTerms . every ( term => {
193+ // Прямое вхождение в какую-либо строку поиска
194+ if ( textToSearch . includes ( term ) ) {
195+ return true ;
196+ }
197+
198+ // Проверка на сокращения (например "общ" -> "общего")
199+ // Ищем части, которые начинаются с поискового термина
200+ for ( const part of nameParts ) {
201+ if ( part . startsWith ( term ) ) {
202+ return true ;
203+ }
204+ }
205+
206+ // Также проверяем совпадение с транслитерацией
207+ // Например, "obsh" -> "общего"
208+ const translitMap : { [ key : string ] : string } = {
209+ 'a' : 'а' , 'b' : 'б' , 'v' : 'в' , 'g' : 'г' , 'd' : 'д' , 'e' : 'е' , 'yo' : 'ё' , 'zh' : 'ж' ,
210+ 'z' : 'з' , 'i' : 'и' , 'j' : 'й' , 'k' : 'к' , 'l' : 'л' , 'm' : 'м' , 'n' : 'н' , 'o' : 'о' ,
211+ 'p' : 'п' , 'r' : 'р' , 's' : 'с' , 't' : 'т' , 'u' : 'у' , 'f' : 'ф' , 'h' : 'х' , 'ts' : 'ц' ,
212+ 'ch' : 'ч' , 'sh' : 'ш' , 'sch' : 'щ' , 'y' : 'ы' , 'yu' : 'ю' , 'ya' : 'я'
213+ } ;
214+
215+ // Преобразуем латинский поисковый термин в кириллицу для поиска
216+ let translitTerm = term ;
217+ for ( const [ latin , cyrillic ] of Object . entries ( translitMap ) ) {
218+ translitTerm = translitTerm . replace ( new RegExp ( latin , 'g' ) , cyrillic ) ;
219+ }
220+
221+ if ( translitTerm !== term && textToSearch . includes ( translitTerm ) ) {
222+ return true ;
223+ }
224+
225+ return false ;
226+ } ) ;
227+ }
228+
229+ private async openMetadataItem ( metadata : MetadataItem ) : Promise < void > {
230+ // Если у элемента есть путь к файлу, открываем его
231+ if ( metadata . file ) {
232+ try {
233+ const document = await vscode . workspace . openTextDocument ( metadata . file ) ;
234+ await vscode . window . showTextDocument ( document ) ;
235+ } catch ( error ) {
236+ console . error ( `Ошибка при открытии файла ${ metadata . file } :` , error ) ;
237+ }
238+ }
239+
240+ // Находим соответствующий TreeItem в дереве
241+ // Используем dataProvider для обхода дерева
242+ const treeItem = await this . findTreeItemById ( metadata . id ) ;
243+
244+ if ( treeItem ) {
245+ // Выполняем команду для открытия соответствующего модуля
246+ // в зависимости от типа элемента метаданных
247+ await this . executeCommandForTreeItem ( treeItem ) ;
248+
249+ // Выделяем элемент в дереве метаданных
250+ await this . revealTreeItem ( treeItem ) ;
251+ }
252+ }
253+
254+ private async findTreeItemById ( id : string ) : Promise < TreeItem | undefined > {
255+ if ( ! this . metadataView . dataProvider ) {
256+ return undefined ;
257+ }
258+
259+ // Получаем корневые элементы
260+ const rootItems = await this . metadataView . dataProvider . getChildren ( ) ;
261+ if ( ! rootItems || rootItems . length === 0 ) {
262+ return undefined ;
263+ }
264+
265+ // Ищем элемент по ID с помощью рекурсивной функции
266+ return this . findTreeItemByIdRecursive ( rootItems , id ) ;
267+ }
268+
269+ private async findTreeItemByIdRecursive ( items : TreeItem [ ] , id : string ) : Promise < TreeItem | undefined > {
270+ for ( const item of items ) {
271+ if ( item . id === id ) {
272+ return item ;
273+ }
274+
275+ if ( item . children && item . children . length > 0 ) {
276+ const found = await this . findTreeItemByIdRecursive ( item . children , id ) ;
277+ if ( found ) {
278+ return found ;
279+ }
280+ } else if ( this . metadataView . dataProvider ) {
281+ // Если у элемента нет детей в памяти, попробуем получить их через dataProvider
282+ const children = await this . metadataView . dataProvider . getChildren ( item ) ;
283+ if ( children && children . length > 0 ) {
284+ const found = await this . findTreeItemByIdRecursive ( children , id ) ;
285+ if ( found ) {
286+ return found ;
287+ }
288+ }
289+ }
290+ }
291+
292+ return undefined ;
293+ }
294+
295+ private async executeCommandForTreeItem ( item : TreeItem ) : Promise < void > {
296+ // Выполняем соответствующую команду в зависимости от типа элемента
297+ if ( item . command ) {
298+ if ( typeof item . command === 'string' ) {
299+ await vscode . commands . executeCommand ( item . command ) ;
300+ } else if ( item . command . command ) {
301+ await vscode . commands . executeCommand ( item . command . command , ...( item . command . arguments || [ ] ) ) ;
302+ }
303+ } else if ( item . contextValue ) {
304+ // Определяем, какую команду выполнить на основе contextValue
305+ if ( item . contextValue . includes ( 'form' ) ) {
306+ await vscode . commands . executeCommand ( 'metadataViewer.openForm' , item ) ;
307+ } else if ( item . contextValue . includes ( 'object' ) ) {
308+ await vscode . commands . executeCommand ( 'metadataViewer.openObjectModule' , item ) ;
309+ } else if ( item . contextValue . includes ( 'manager' ) ) {
310+ await vscode . commands . executeCommand ( 'metadataViewer.openManagerModule' , item ) ;
311+ } else if ( item . contextValue . includes ( 'module' ) ) {
312+ await vscode . commands . executeCommand ( 'metadataViewer.openModule' , item ) ;
313+ }
314+ }
315+ }
316+
317+ private async revealTreeItem ( item : TreeItem ) : Promise < void > {
318+ // Прокручиваем дерево к выбранному элементу
319+ await vscode . commands . executeCommand ( 'metadataView.reveal' , item ) ;
320+ }
321+ }
322+
323+ // Интерфейс для элементов быстрого выбора
324+ interface MetadataQuickPickItem extends vscode . QuickPickItem {
325+ metadata : MetadataItem ;
326+ }
0 commit comments