diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts index 1e10ed2a8..17d863383 100644 --- a/src/api/IBMiContent.ts +++ b/src/api/IBMiContent.ts @@ -799,6 +799,59 @@ export default class IBMiContent { return items; } + /** + * Get path info + * @param path + * @return IFSFile + */ + async getFileInfo(path: string): Promise { + const { 'stat': STAT } = this.ibmi.remoteFeatures; + + let item: IFSFile = { type: 'streamfile', name: '', path: '' }; + let fileInfoResult: CommandResult; + + if (STAT) { + fileInfoResult = (await this.ibmi.sendCommand({ + command: `${STAT} --dereference --printf="%A\t%h\t%U\t%G\t%s\t%Y\t%n\n" ${Tools.escapePath(path)}` + })); + + if (fileInfoResult.stdout !== '') { + const fileInfo = fileInfoResult.stdout; + + let auth: string, hardLinks: string, owner: string, group: string, size: string, modified: string, name: string; + [auth, hardLinks, owner, group, size, modified, name] = fileInfo.split(`\t`); + + if (name !== `..` && name !== `.`) { + const type = (auth.startsWith(`d`) ? `directory` : `streamfile`); + item = { + type: type, + name: name, + path: path, + size: Number(size), + modified: new Date(Number(modified) * 1000), + owner: owner + }; + }; + } + } else { + fileInfoResult = (await this.ibmi.sendCommand({ + command: `${this.ibmi.remoteFeatures.ls} -a -p -L ${Tools.escapePath(path)}` + })); + + if (fileInfoResult.stdout !== '') { + const fileInfo = fileInfoResult.stdout; + const type = (fileInfo.endsWith(`/`) ? `directory` : `streamfile`); + item = { + type: type, + name: (type === `directory` ? fileInfo.substring(0, fileInfo.length - 1) : fileInfo), + path: path + }; + } + } + + return item; + } + async memberResolve(member: string, files: QsysPath[]): Promise { const inAmerican = (s: string) => { return this.ibmi.sysNameInAmerican(s) }; const inLocal = (s: string) => { return this.ibmi.sysNameInLocal(s) }; diff --git a/src/api/Search.ts b/src/api/Search.ts index 68987bf83..b80135066 100644 --- a/src/api/Search.ts +++ b/src/api/Search.ts @@ -8,6 +8,7 @@ export namespace Search { export async function searchMembers(connection: IBMi, library: string, sourceFile: string, searchTerm: string, members: string|IBMiMember[], readOnly?: boolean,): Promise { const config = connection.getConfig(); const content = connection.getContent(); + const infoComponent = connection.getComponent(GetMemberInfo.ID); if (connection && config && content) { let detailedMembers: IBMiMember[]|undefined; @@ -47,26 +48,24 @@ export namespace Search { const [lib, spf] = dir.split(`/`); return detailedMembers!.some(member => member.name === name && member.library === lib && member.file === spf); }); + } - } else { - // Else, we need to fetch the member info for each hit so we can display the correct extension - const infoComponent = connection?.getComponent(GetMemberInfo.ID); - const memberInfos: IBMiMember[] = hits.map(hit => { - const { name, dir } = path.parse(hit.path); - const [library, file] = dir.split(`/`); - - return { - name, - library, - file, - extension: `` - }; - }); + // We need to fetch the member info for each hit so we can display the correct extension and info. + const memberInfos: IBMiMember[] = hits.map(hit => { + const { name, dir } = path.parse(hit.path); + const [library, file] = dir.split(`/`); - detailedMembers = await infoComponent?.getMultipleMemberInfo(connection, memberInfos); - } + return { + name, + library, + file, + extension: `` + }; + }); - // Then fix the extensions in the hit + detailedMembers = await infoComponent?.getMultipleMemberInfo(connection, memberInfos); + + // Add extension and member info to the hit. for (const hit of hits) { const { name, dir } = path.parse(hit.path); const [lib, spf] = dir.split(`/`); @@ -75,6 +74,7 @@ export namespace Search { if (foundMember) { hit.path = connection.sysNameInLocal(`${asp ? `${asp}/` : ``}${lib}/${spf}/${name}.${foundMember.extension}`); + hit.member = foundMember; } } @@ -110,9 +110,13 @@ export namespace Search { }); if (grepRes.code == 0) { + const hits = parseGrepOutput(grepRes.stdout); + for (var i = 0; i < hits.length; i++) { + hits[i].file = await connection.content.getFileInfo(hits[i].path) + }; return { term: searchTerm, - hits: parseGrepOutput(grepRes.stdout) + hits: hits } } } else { @@ -141,9 +145,13 @@ export namespace Search { }); if (findRes.code == 0 && findRes.stdout) { + const hits = parseFindOutput(findRes.stdout); + for (var i = 0; i < hits.length; i++) { + hits[i].file = await connection.content.getFileInfo(hits[i].path) + }; return { term: findTerm, - hits: parseFindOutput(findRes.stdout) + hits: hits } } } else { diff --git a/src/api/components/getMemberInfo.ts b/src/api/components/getMemberInfo.ts index dd591a35f..9b6e719c7 100644 --- a/src/api/components/getMemberInfo.ts +++ b/src/api/components/getMemberInfo.ts @@ -56,7 +56,11 @@ export class GetMemberInfo implements IBMiComponent { async getMemberInfo(connection: IBMi, library: string, sourceFile: string, member: string): Promise { const config = connection.config!; const tempLib = config.tempLibrary; - const statement = `select * from table(${tempLib}.${this.procedureName}('${library}', '${sourceFile}', '${member}'))`; + const statement = ``.concat(`select Library, File, Member, Attr, Extension`, + ` , extract(epoch from (CREATED))*1000 as CREATED`, + ` , extract(epoch from (CHANGED))*1000 as CHANGED`, + ` , Description, isSource`, + ` from table(${tempLib}.${this.procedureName}('${library}', '${sourceFile}', '${member}'))`); let results: Tools.DB2Row[] = []; if (config.enableSQL) { @@ -88,7 +92,11 @@ export class GetMemberInfo implements IBMiComponent { const config = connection.config!; const tempLib = config.tempLibrary; const statement = members - .map(member => `select * from table(${tempLib}.${this.procedureName}('${member.library}', '${member.file}', '${member.name}'))`) + .map(member => ``.concat(`select Library, File, Member, Attr, Extension`, + ` , extract(epoch from (CREATED))*1000 as CREATED`, + ` , extract(epoch from (CHANGED))*1000 as CHANGED`, + ` , Description, isSource`, + ` from table(${tempLib}.${this.procedureName}('${member.library}', '${member.file}', '${member.name}'))`)) .join(' union all '); let results: Tools.DB2Row[] = []; diff --git a/src/api/types.ts b/src/api/types.ts index 701423006..3364371fb 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -92,7 +92,7 @@ export interface IBMiObject extends QsysPath { owner?: string } -export interface IBMiMember { +export type IBMiMember = { library: string file: string name: string @@ -105,7 +105,7 @@ export interface IBMiMember { changed?: Date } -export interface IFSFile { +export type IFSFile = { type: "directory" | "streamfile" name: string path: string @@ -175,6 +175,8 @@ export type SearchHit = { lines: SearchHitLine[] readonly?: boolean label?: string + file?: IFSFile + member?: IBMiMember } export type SearchHitLine = { diff --git a/src/ui/views/searchView.ts b/src/ui/views/searchView.ts index d5cf072d3..fe6cb3ae2 100644 --- a/src/ui/views/searchView.ts +++ b/src/ui/views/searchView.ts @@ -1,5 +1,6 @@ import path from 'path'; import vscode from "vscode"; +import { VscodeTools } from "../Tools"; import { DefaultOpenMode, SearchHit, SearchHitLine, SearchResults, WithPath } from "../../typings"; export function initializeSearchView(context: vscode.ExtensionContext) { @@ -78,7 +79,9 @@ class HitSource extends vscode.TreeItem implements WithPath { this.iconPath = vscode.ThemeIcon.File; this.path = result.path; this._readonly = result.readonly; - this.tooltip = result.path; + this.tooltip = result.file ? VscodeTools.ifsFileToToolTip(this.path, result.file) : + result.member? VscodeTools.memberToToolTip(this.path, result.member) : + result.path; if (hits) { this.description = `${hits} hit${hits === 1 ? `` : `s`}`;