/application/electron/src/services/files/service.file.search.ts

https://github.com/esrlabs/chipmunk · TypeScript · 132 lines · 113 code · 10 blank · 9 comment · 18 complexity · 4387fbc3548738a181af437602d89852 MD5 · raw file

  1. import ServiceElectron, { IPCMessages } from '../service.electron';
  2. import ServicePaths from '../service.paths';
  3. import Logger from '../../tools/env.logger';
  4. import { Subscription } from '../../tools/index';
  5. import { IService } from '../../interfaces/interface.service';
  6. import { spawn, ChildProcess } from 'child_process';
  7. import * as os from 'os';
  8. /**
  9. * @class ServiceFileSearch
  10. * @description Providers access to file parsers from render
  11. */
  12. class ServiceFileSearch implements IService {
  13. private _logger: Logger = new Logger('ServiceFileSearch');
  14. private _cmd: string = '';
  15. // Should detect by executable file
  16. private _subscription: { [key: string]: Subscription } = {};
  17. /**
  18. * Initialization function
  19. * @returns Promise<void>
  20. */
  21. public init(): Promise<void> {
  22. return new Promise((resolve, reject) => {
  23. this._cmd = ServicePaths.getRG();
  24. ServiceElectron.IPC.subscribe(IPCMessages.FilesSearchRequest, this._onFilesSearchRequest.bind(this)).then((subscription: Subscription) => {
  25. this._subscription.FilesSearchRequest = subscription;
  26. resolve();
  27. }).catch((error: Error) => {
  28. this._logger.error(`Fail to init module due error: ${error.message}`);
  29. reject(error);
  30. });
  31. });
  32. }
  33. public destroy(): Promise<void> {
  34. return new Promise((resolve) => {
  35. Object.keys(this._subscription).forEach((key: string) => {
  36. this._subscription[key].destroy();
  37. });
  38. resolve();
  39. });
  40. }
  41. public getName(): string {
  42. return 'ServiceFileSearch';
  43. }
  44. private _onFilesSearchRequest(request: IPCMessages.TMessage, response: (instance: IPCMessages.TMessage) => any) {
  45. const req: IPCMessages.FilesSearchRequest = request as IPCMessages.FilesSearchRequest;
  46. this._getMatchesCount(req.files, req.requests.map((r) => {
  47. return new RegExp(r.source, r.flags);
  48. })).then((matches: { [key: string]: number }) => {
  49. response(new IPCMessages.FilesSearchResponse({
  50. matches: matches,
  51. }));
  52. }).catch((error: Error) => {
  53. response(new IPCMessages.FilesSearchResponse({
  54. matches: {},
  55. error: error.message,
  56. }));
  57. });
  58. }
  59. private _getMatchesCount(files: string[], regExp: RegExp | RegExp[]): Promise<{ [key: string]: number }> {
  60. return new Promise((resolve, reject) => {
  61. setImmediate(() => {
  62. if (!(regExp instanceof Array)) {
  63. regExp = [regExp];
  64. }
  65. const args: string[] = [
  66. '--count',
  67. '--text', // https://github.com/BurntSushi/ripgrep/issues/306 this issue is about a case, when not printable symble is in a file
  68. '-i',
  69. '-e',
  70. `(${regExp.map(r => r.source).join('|')})`,
  71. ];
  72. args.push(...files);
  73. let output: string = '';
  74. let errors: string = '';
  75. const process: ChildProcess = spawn(this._cmd, args, {
  76. stdio: [ 'pipe', 'pipe', 'pipe' ],
  77. });
  78. process.stdout?.on('data', (chunk: Buffer | string) => {
  79. if (chunk instanceof Buffer) {
  80. chunk = chunk.toString();
  81. }
  82. if (typeof chunk !== 'string') {
  83. return;
  84. }
  85. output += chunk;
  86. });
  87. process.stderr?.on('data', (chunk: Buffer | string) => {
  88. if (chunk instanceof Buffer) {
  89. chunk = chunk.toString();
  90. }
  91. if (typeof chunk !== 'string') {
  92. return;
  93. }
  94. errors += chunk;
  95. });
  96. process.once('close', () => {
  97. if (errors.trim() !== '') {
  98. return reject(new Error(this._logger.warn(`Fail to make search due error: ${errors}`)));
  99. }
  100. const results: { [key: string]: number } = {};
  101. output.split(/[\n\r]/).forEach((result: string) => {
  102. const parts: string[] = result.split(':');
  103. if (os.platform() === 'win32' && parts.length === 3) {
  104. parts[0] += ':' + parts[1];
  105. parts.splice(1, 1);
  106. }
  107. if (parts.length !== 2) {
  108. return this._logger.warn(`Fail to parser RG result line: ${result}`);
  109. }
  110. results[parts[0]] = parseInt(parts[1], 10);
  111. });
  112. resolve(results);
  113. });
  114. process.once('error', (error: Error) => {
  115. this._logger.error(`Error during calling rg: ${error.message}`);
  116. reject(error);
  117. });
  118. });
  119. });
  120. }
  121. }
  122. export default (new ServiceFileSearch());