diff --git a/package-lock.json b/package-lock.json index a2d4f0b..c5adf85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,21 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "requires": { + "debug": "^4.1.1" + } + }, + "@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true + }, "@types/node": { "version": "12.19.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.7.tgz", @@ -175,6 +190,15 @@ } } }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -319,9 +343,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "inflight": { @@ -438,9 +462,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -452,6 +476,12 @@ "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=", "dev": true }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -478,6 +508,14 @@ "requires": { "is-core-module": "^2.1.0", "path-parse": "^1.0.6" + }, + "dependencies": { + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + } } } } @@ -500,18 +538,18 @@ }, "dependencies": { "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "dev": true } } @@ -571,12 +609,6 @@ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -645,9 +677,9 @@ } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -656,9 +688,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "shallow-copy": { @@ -700,6 +732,17 @@ "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", "dev": true }, + "simple-git": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.23.0.tgz", + "integrity": "sha512-P9ggTW8vb/21CAL/AmnACAhqBDfnqSSZVpV7WuFtsFR9HLunf5IqQvk+OXAQTfkcZep8pKnt3DV3o7w3TegEkQ==", + "dev": true, + "requires": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.4" + } + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -793,15 +836,10 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" - }, "typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true }, "validate-npm-package-license": { diff --git a/package.json b/package.json index fe516c9..50dec7b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,10 @@ "onCommand:tsar.transform.rename", "onCommand:tsar.parallel.openmp", "onCommand:tsar.parallel.dvmh", - "onCommand:tsar.parallel.dvmhsm" + "onCommand:tsar.parallel.dvmhsm", + "onCommand:tsar.gitgraph", + "onCommand:tsar.refactorTsarProject", + "onCommand:tsar.addToTsarProject" ], "main": "./out/src/extension", "contributes": { @@ -64,7 +67,7 @@ "type": "boolean", "default": false }, - "tsar-advisor.advanced.analysisServer" : { + "tsar-advisor.advanced.analysisServer": { "description": "Analysis server which is used to analyze a project. Set path to the analysis server executable if it is not available in the PATH environment variable.", "type": "string", "default": "tsar-server" @@ -110,6 +113,10 @@ "command": "tsar.start", "title": "TSAR Analyze" }, + { + "command": "tsar.gitgraph", + "title": "TSAR Graph" + }, { "command": "tsar.stop", "title": "TSAR Close session", @@ -189,6 +196,14 @@ "command": "tsar.call.graph.unsafe", "title": "View Unsafe Control Flow", "icon": "icons/call-graph.svg" + }, + { + "command": "tsar.refactorTsarProject", + "title": "TSAR Refactor TSAR Project" + }, + { + "command": "tsar.addToTsarProject", + "title": "TSAR Add to Project" } ], "menus": { @@ -268,6 +283,16 @@ "submenu": "tsar.submenu.parallel", "when": "resourceLangId == 'c' || resourceLangId == 'cpp'", "group": "1_tsar" + }, + { + "command": "tsar.gitgraph", + "when": "resourceLangId == 'c' || resourceLangId == 'cpp'", + "group": "1_tsar" + }, + { + "command": "tsar.refactorTsarProject", + "when": "resourceLangId == 'c' || resourceLangId == 'cpp'", + "group": "1_tsar" } ], "explorer/context": [ @@ -290,6 +315,21 @@ "submenu": "tsar.submenu.parallel", "when": "resourceLangId == 'c' || resourceLangId == 'cpp'", "group": "1_tsar" + }, + { + "command": "tsar.gitgraph", + "when": "resourceLangId == 'c' || resourceLangId == 'cpp'", + "group": "1_tsar" + }, + { + "command": "tsar.refactorTsarProject", + "when": "resourceLangId == 'c' || resourceLangId == 'cpp'", + "group": "1_tsar" + }, + { + "command": "tsar.addToTsarProject", + "when": "resourceLangId == 'c' || resourceLangId == 'cpp'", + "group": "1_tsar" } ], "tsar.submenu.tfm": [ @@ -374,7 +414,8 @@ "@types/node": "^12.8.1", "catw": "~1.0.1", "npm-run-all": "~4.1.5", - "rimraf": "~3.0.0" + "rimraf": "~3.0.0", + "simple-git": "~3.23.0" }, "extensionDependencies": [], "dependencies": { diff --git a/src/extension.ts b/src/extension.ts index 00ce98e..5b61809 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -16,13 +16,17 @@ import * as lt from './loopTree'; import * as at from './aliasTree'; import * as msg from './messages'; import {onReject} from './functions'; -import {ProjectEngine, Project } from './project'; +import {ProjectEngine, Project} from './project'; import {ProjectProvider} from './general'; import {CalleeFuncProvider, CalleeFuncProviderState} from './calleeFunc'; import * as t from './transformProvider'; import server from './tools'; import { FileListProvider } from './fileList'; import { LoopTreeViewProvider } from './loopExplorer'; +import simpleGit, { SimpleGit } from 'simple-git' +import {exec, execSync} from 'child_process' +import * as child_process from 'child_process'; + /** * Open log file (log.Extension.log), returns true on success. @@ -55,6 +59,613 @@ function openLog(): boolean { return true; } +/* +Matches commit hashes to the column number +Return value: [{ hash: string, + children: string[], + height: number}] +*/ +async function getCommitGraph(repositoryPath: string) { + let commits = await getCommitsChildren(repositoryPath); + + const orderedCommits = getSortedCommits(repositoryPath); + const hashIdx: Map = new Map(); + + orderedCommits.forEach((value, index) => { + hashIdx.set(value.hash, index); + }); + + const height: number[] = Array(Object.keys(orderedCommits).length).fill(0); + const subTree: string[] = []; + + function dfs(commit_hash: string) { + if(commits[commit_hash].length == 0 || height[hashIdx.get(commit_hash)] != 0){ + subTree.push(commit_hash); + let newHeight = 0; + for(let idx = hashIdx.get(subTree[0]); idx <= hashIdx.get(subTree[subTree.length - 1]); idx++){ + if(height[idx] > newHeight){ + newHeight = height[idx]; + } + } + + for(let idx = 1; idx <= subTree.length - 2; idx++){ + height[hashIdx.get(subTree[idx])] = newHeight +1; + } + + if(commits[commit_hash].length == 0 && height[hashIdx.get(commit_hash)] == 0){ + height[hashIdx.get(commit_hash)] = newHeight + 1; + } + + subTree.length = 0; + + return; + } + + for (const neighbor of commits[commit_hash]) { + subTree.push(commit_hash); + dfs(neighbor); + } + } + + dfs(orderedCommits[0].hash); + height[0] = 1; + + let answer = []; + for(const commit of orderedCommits){ + answer.push({ + hash: commit.hash, + children: commits[commit.hash], + height: height[hashIdx.get(commit.hash)] + }); + } + return answer; +} + + +function getSortedCommits(repositoryPath: string): { hash: string; date: number}[] { + const gitLogOutput = execSync('git log --pretty=format:"%H %at" --all', { cwd: repositoryPath }).toString(); + + const lines = gitLogOutput.split('\n'); + + const commits = lines.map(line => { + const [hash, dateString] = line.split(' '); + return { + hash, + date: parseInt(dateString) + }; + }); + + commits.sort((a, b) => a.date - b.date); + + return commits; +} + +async function getCommitsChildren(repositoryPath: string): Promise<{ [key: string]: string[] } | null> { + return new Promise((resolve, reject) => { + exec('git rev-list --all --children', { cwd: repositoryPath }, (error, stdout, stderr) => { + if (error) { + console.error('Error executing git log:', stderr); + reject(null); + return; + } + + const commitGraph: { [key: string]: string[] } = {}; + const lines = stdout.trim().split('\n'); + + lines.forEach(line => { + const [commitHash, ...parentHashes] = line.split(' '); + commitGraph[commitHash] = parentHashes; + }); + + resolve(commitGraph); + }); + }); +} + + +function getHEADCommit(repositoryPath: string): string { + return execSync('git rev-parse HEAD', { cwd: repositoryPath }).toString().trim(); +} + + +function getCommitsData(repositoryPath: string): { + hash: string, + authorName: string, + authorEmail: string, + authorDate: string, + message: string}[] { + const gitLogOutput = execSync('git log --format="%H%n%an%n%ae%n%ai%n%s" --all', { cwd: repositoryPath }).toString(); + + const lines = gitLogOutput.split('\n'); + let commits = []; + + for (let i = 0; i < lines.length - 1; i += 5) { + commits.push({ + hash: lines[i], + authorName: lines[i + 1], + authorEmail: lines[i + 2], + authorDate: lines[i + 3].slice(0, -6), + message: lines[i + 4] + }); + } + + return commits; +} + +function getGraphWebviewContent(commitGraph, HEAD, commitsData) { + const commitGraphString = JSON.stringify(commitGraph); + const commitsDateString = JSON.stringify(commitsData); + + const x = 210; // Initial x position of git graph + const y = 50; // Initial y position of git graph + const xOffset = 100; // Horizontal distance between nodes + const yOffset = 100; // Vertical distance between nodes + const textWidth = 500; // Width of column with commit message + + let max_height = 1; // Width of graph + + commitGraph.forEach((commit)=> { + if (commit.height > max_height) { + max_height = commit.height; + } + }); + + return ` + + + Git Graph + + + + +
+
+ +
+
+

Commit Details

+

Hash:

+

Message:

+

Author Name:

+

Author Email:

+

Date:

+
+
+ + + `; +} + +async function waitForFiles(filePaths: string[], timeout: number = 10000) { + const checkInterval = 100; // проверять каждые 100 мс + let elapsedTime = 0; + + return new Promise((resolve, reject) => { + const interval = setInterval(() => { + const allFilesExist = filePaths.every(file => fs.existsSync(file)); + if (allFilesExist) { + clearInterval(interval); + resolve(); + } + elapsedTime += checkInterval; + if (elapsedTime >= timeout) { + clearInterval(interval); + reject(new Error(`Files were not created in ${timeout} ms: ${filePaths.join(', ')}`)); + } + }, checkInterval); + }); +} + +function getWorkspaceRoot(fileUri: vscode.Uri) { + /// Получаем все открытые рабочие папки + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders || workspaceFolders.length === 0) { + vscode.window.showErrorMessage('No workspace folder is open.'); + return undefined; + } + + // Ищем в какой рабочей области находится файл + const containingFolder = workspaceFolders.find(folder => + fileUri.fsPath.startsWith(folder.uri.fsPath) + ); + + if (!containingFolder) { + vscode.window.showErrorMessage('The file is not part of any open workspace folder.'); + return undefined; + } + + return containingFolder.uri.fsPath.toString(); +} + + +function addFileToCompileCommands(filePath: string) { + const fileUri = vscode.Uri.file(filePath); + const workspaceRoot = getWorkspaceRoot(fileUri); + if (!workspaceRoot) { + return; + } + + const compileCommandsPath = path.join(workspaceRoot, 'compile_commands.json'); + + // Создаем файл, если он не существует + if (!fs.existsSync(compileCommandsPath)) { + fs.writeFileSync(compileCommandsPath, '[]', 'utf8'); + } + + let data = JSON.parse(fs.readFileSync(compileCommandsPath, 'utf8')); + + const absoluteFilePath = path.isAbsolute(filePath) + ? filePath + : path.join(workspaceRoot, filePath); + + // Проверяем, существует ли запись для файла + const fileAlreadyExists = data.some((entry: any) => { + // Преобразуем путь файла в абсолютный, если он относительный + const normalizedEntryPath = path.isAbsolute(entry.file) + ? path.normalize(entry.file) + : path.join(entry.directory, path.normalize(entry.file)); + + return normalizedEntryPath === absoluteFilePath; + }); + + if (!fileAlreadyExists) { + // Добавляем новую запись + data.push({ + directory: workspaceRoot, + file: absoluteFilePath, + command: '' + }); + + fs.writeFileSync(compileCommandsPath, JSON.stringify(data, null, 2), 'utf8'); + } +} + +function addFileToTsarProject(filePath: string) { + const fileUri = vscode.Uri.file(filePath); + const workspaceRoot = getWorkspaceRoot(fileUri); + if (!workspaceRoot) { + return; + } + + const tsarProjectPath = path.join(workspaceRoot, 'tsar_project_file.json'); + + if (!fs.existsSync(tsarProjectPath)) { + fs.writeFileSync(tsarProjectPath, '[]', 'utf8'); + } + + let data = JSON.parse(fs.readFileSync(tsarProjectPath, 'utf8')); + + const absoluteFilePath = path.isAbsolute(filePath) + ? filePath + : path.join(workspaceRoot, filePath); + + if (data.includes(absoluteFilePath)) { + return; + } + + data.push(absoluteFilePath); + + // Сохраняем обновленный массив обратно в файл + fs.writeFileSync(tsarProjectPath, JSON.stringify(data, null, 2), 'utf8'); +} + + export function activate(context: vscode.ExtensionContext) { if (!openLog()) return; @@ -76,22 +687,169 @@ export function activate(context: vscode.ExtensionContext) { [t.TransformationProvider.scheme, new t.TransformationProvider], [at.AliasTreeProvider.scheme, new at.AliasTreeProvider] ); - let start = vscode.commands.registerCommand( - 'tsar.start', (uri:vscode.Uri) => { - vscode.workspace.openTextDocument(uri) - .then((success) => { - return engine.start(success, - server.tools.find(t=>{return t.name === 'tsar'})); - }) - .then( - async project => { - await engine.runTool(project); - project.providerState(FileListProvider.scheme).active = true; - project.send(new msg.FileList); - vscode.commands.executeCommand('tsar.function.list', project.uri); + + let gitGraph = vscode.commands.registerCommand('tsar.gitgraph', async (uri: vscode.Uri) => { + const repositoryPath = path.dirname(uri.fsPath); + + const GitFilePath = path.join(path.dirname(uri.fsPath), '.tsar_git'); + if (!fs.existsSync(GitFilePath)) { + fs.writeFileSync(GitFilePath, '', 'utf-8'); // here can be written information that should be saved for git + } + + let git = simpleGit(repositoryPath); + git.checkIsRepo() + .then(async (isRepo) => { + + if (!isRepo) { + await git.init(); + + } else { + const panel = vscode.window.createWebviewPanel( + 'commitGraph', + 'Commit Graph', + vscode.ViewColumn.One, + { + enableScripts: true + } + ); + + const graph = await getCommitGraph(repositoryPath); + const HEADCommit = getHEADCommit(repositoryPath); + const commitsData = getCommitsData(repositoryPath); + + panel.webview.html = getGraphWebviewContent(graph, HEADCommit, commitsData); + + panel.webview.onDidReceiveMessage( + async message => { + switch (message.command) { + case 'gitCheckout': + try { + await git.checkout(message.commitHash); + panel.webview.postMessage({ command: 'gitCheckout', commitHash: message.commitHash }) + } catch(err){ + vscode.window.showErrorMessage(err.message, "Close"); + } + break; + } }, - reason => { onReject(reason, uri) }) - }); + undefined, + context.subscriptions + ); + } + }) + }); + + let refactorProject = vscode.commands.registerCommand( + 'tsar.refactorTsarProject', async (uri:vscode.Uri) => { + // Пользователь должен выбрать, какие файлы должны составлять проект + let selected; + + // Должен быть выбран хотя бы один файл + while (!selected || selected.length === 0) { + const files = await vscode.workspace.findFiles('**/*.{c,cpp}'); + + const items = files.map(file => ({ + label: path.basename(file.fsPath), + description: file.fsPath + })); + + selected = await vscode.window.showQuickPick(items, { + canPickMany: true, + placeHolder: 'Choose files for compilation (.c or .cpp)' + }); + + if (!selected || selected.length === 0) { + vscode.window.showWarningMessage('You must select at least one file!'); + } + } + + // добавление файлов в Compilation Database + selected.forEach((item) => { + const filePath = item.description; + addFileToCompileCommands(filePath); + }); + + const absolutePaths = selected.map(item => item.description); + const workspaceRoot = getWorkspaceRoot(uri); + + const tsarProjectPath = path.join(workspaceRoot, 'tsar_project_file.json'); + + // создание или преписывание TSAR файла-проекта + fs.writeFileSync(tsarProjectPath, JSON.stringify(absolutePaths, null, 2)); + } + ) + + let addFileToProject = vscode.commands.registerCommand( + 'tsar.addToTsarProject', async (uri:vscode.Uri) => { + addFileToCompileCommands(uri.fsPath); + addFileToTsarProject(uri.fsPath); + + }); + + let start = vscode.commands.registerCommand( + 'tsar.start', async (uri:vscode.Uri) => { + const projectDir = getWorkspaceRoot(uri); + const projectPath = path.join(projectDir, 'tsar_project_file.json'); + + if (!fs.existsSync(projectPath)) { + await vscode.commands.executeCommand('tsar.refactorTsarProject', uri); + } + vscode.workspace.openTextDocument(vscode.Uri.file(projectPath)) + .then((success) => { + return engine.start(success, + server.tools.find(t=>{return t.name === 'project'})); + }) + .then( async project => { + project = engine.runProjectTool(project); + const data = fs.readFileSync(projectPath, 'utf-8'); + const fileList = JSON.parse(data); + const compiledFiles = fileList.toString().replace(',', ' ').replace(/\.(c|cpp)\b/g, '.ll'); + const totalFilePath = path.join(projectDir, 'total.ll'); + + await waitForFiles(fileList.map((file) => file.replace(/\.(c|cpp)\b/g, '.ll'))); + + try { + const cliString = "llvm-link-15 -S " + compiledFiles + " -o " + totalFilePath + child_process.execSync(cliString); + } catch (error) { + const errorMessage = `Linking failed: ${error.stderr.toString()}`; + vscode.window.showErrorMessage(errorMessage); + } + + let projectFileUri = vscode.Uri.file(totalFilePath) + return projectFileUri; + }) + .then((projectUri) => { + return vscode.workspace.openTextDocument(projectUri) + }) + .then((success) => { + return engine.start(success, + server.tools.find(t=>{return t.name === 'tsar'})); + }) + .then( + async project => { + await engine.runTool(project); + project.providerState(FileListProvider.scheme).active = true; + project.send(new msg.FileList); + vscode.commands.executeCommand('tsar.function.list', project.uri); + + let git = simpleGit(path.dirname(project.uri.fsPath)); + git.checkIsRepo() + .then(async (isRepo) => { + if (!isRepo) { + await git.init(); + } + }) + + let GitFilePath = path.join(path.dirname(project.uri.fsPath), '.tsar_git'); + if (!fs.existsSync(GitFilePath)) { + fs.writeFileSync(GitFilePath, '', 'utf-8'); // here can be written information that should be saved for git + } + }, + reason => { onReject(reason, uri) + }) + } + ); t.registerCommands([ { command: 'tsar.transform.propagate', @@ -104,7 +862,7 @@ export function activate(context: vscode.ExtensionContext) { run: '-clang-inline' }, { - command: 'tsar.transform.replace', + command: 'tsar.transformccff.replace', title: 'TSAR Structure Replacement', run: '-clang-struct-replacement' }, @@ -201,5 +959,5 @@ export function activate(context: vscode.ExtensionContext) { project.focus = state; project.send(request); }); - context.subscriptions.push(start, stop, statistic, openProject, showCalleeFunc); + context.subscriptions.push(start, stop, statistic, openProject, showCalleeFunc, gitGraph, refactorProject, addFileToProject); } diff --git a/src/log.ts b/src/log.ts index 5e0cae1..ff8426a 100644 --- a/src/log.ts +++ b/src/log.ts @@ -46,6 +46,7 @@ export class Project { } export class Error { + static gitIgnore = '{0} is in .gitignore'; static serverNotFound = 'cannot find analysis server {0}'; static serverVersion = 'unable to determine server version' static general = 'some errors have been occurred'; diff --git a/src/project.ts b/src/project.ts index edf203f..864d0ec 100644 --- a/src/project.ts +++ b/src/project.ts @@ -44,7 +44,7 @@ export class ProjectEngine { * Build internal identifier for a specified project uri. */ private static _projectID(uri: vscode.Uri): string { - return vscode.Uri.file(uri.path).toString(); + return vscode.Uri.file(uri.path).fsPath.toString(); } /** @@ -177,8 +177,8 @@ export class ProjectEngine { * TODO (kaniandr@gmail.com): currently each project consists of a single * file, update to support projects configured with a help of *.json file. */ - start(doc: vscode.TextDocument, tool:ToolT): Thenable { - return new Promise((resolve, reject) => { + start(doc: vscode.TextDocument, tool:ToolT): Thenable { + return new Promise((resolve, reject) => { let project = this.project(doc.uri); if (project !== undefined) { vscode.window.showWarningMessage( @@ -194,14 +194,14 @@ export class ProjectEngine { state.provider.update(project); return undefined; } - let check = this._checkDocument(doc); - if (check) - return reject(check); + let check = this._checkDocument(doc); + // if (check) + // return reject(check); let uri = doc.uri; let prjDir = this._makeProjectDir(path.dirname(uri.fsPath)); if (typeof prjDir != 'string') return reject(prjDir); - this._startServer(uri, prjDir, tool, this._environment, resolve, reject); + this._startServer(uri, prjDir, tool, this._environment, resolve, reject, doc); return undefined; }) } @@ -277,6 +277,29 @@ export class ProjectEngine { return project; } + // project should be *.json file + runProjectTool(project: Project, query?: string) { + let cl = new msg.CommandLine(log.Extension.displayName); + + this._projects.forEach((val, key) =>{ + cl.Args.push(vscode.Uri.file(key).fsPath); + }) + // cl.Args.push("-emit-llvm"); + cl.Query = "-emit-llvm"; + // cl.Args.push(query); + + project.arguments = cl.Args; + + // if (query) + // cl.Query = query; + cl.Output = path.join(project.dirname, log.Project.output); + cl.Error = path.join(project.dirname, log.Project.error); + + project.send(cl); + + return project; + } + /** * Stop analysis of a specified project. */ @@ -339,7 +362,7 @@ export class ProjectEngine { * execution. */ private _startServer(uri: vscode.Uri, prjDir: string, - tool: ToolT, env: any, resolve: any, reject: any) { + tool: ToolT, env: any, resolve: any, reject: any, doc) { let server: child_process.ChildProcess; let userConfig = vscode.workspace.getConfiguration(log.Extension.id); let pathToServer = which.sync( @@ -365,7 +388,7 @@ export class ProjectEngine { // do not move project inside 'data/message' event listener // it must be shared between all messages evaluation let project: Project; - let onServerData = (raw: string) => { + let onServerData = async (raw: string) => { let client: net.Socket; try { let data = JSON.parse(raw); @@ -389,7 +412,17 @@ export class ProjectEngine { this, this._context.subscriptions); project.register(scheme, provider.state()); } - this._projects.set(ProjectEngine._projectID(uri), project); + if (path.extname(uri.fsPath).toLowerCase() === '.json') { + const data = fs.readFileSync(uri.fsPath, 'utf-8'); + const fileList = JSON.parse(data); + for (const file of fileList) { + this._projects.set(ProjectEngine._projectID(vscode.Uri.file(file)), project); + } + + + } else { + this._projects.set(ProjectEngine._projectID(uri), project); + } client.on('error', (err) => {this._internalError(err)}); client.on('data', (data:string) => { log.Log.logs[0].write(log.Message.server.replace('{0}', data)); diff --git a/src/tools.ts b/src/tools.ts index 508641d..0dc055a 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -97,6 +97,9 @@ export default target: '-no-format' } ] + }, + { + name: "project" } ] } \ No newline at end of file diff --git a/src/transformProvider.ts b/src/transformProvider.ts index add431e..349f625 100644 --- a/src/transformProvider.ts +++ b/src/transformProvider.ts @@ -9,12 +9,15 @@ 'use strict' import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; import * as log from './log'; import * as msg from './messages'; import {DisposableLikeList, onReject} from './functions'; import {Project, ProjectEngine, ProjectContentProvider, ProjectContentProviderState} from './project'; import server from './tools'; +import simpleGit, { SimpleGit } from 'simple-git' /** * Register transformation command. @@ -52,8 +55,35 @@ export function registerCommands( }).then((value) => {vscode.commands.executeCommand('tsar.stop', project.uri)}); state.active = true; project.focus = state; + + let git = simpleGit(path.dirname(project.uri.fsPath)); + let isRepo = await git.checkIsRepo() + if (!isRepo) { + await git.init(); + } + let GitFilePath = path.join(path.dirname(project.uri.fsPath), '.tsar_git'); + if (!fs.existsSync(GitFilePath)) { + fs.writeFileSync(GitFilePath, '', 'utf-8'); // here can be written information that should be saved for git + } + + let isIgnored = await git.checkIgnore(project.uri.fsPath) + if (isIgnored.some((path) => path == project.uri.fsPath)){ + throw new Error(log.Error.gitIgnore.replace('{0}', project.uri.fsPath)); + } else { + await git.add(project.uri.fsPath); + await git.commit(`${path.basename(project.uri.fsPath)} before sapfor transformation ${info.title}`, project.uri.fsPath, {"--author": "Tsar-advisor "}); + } + await engine.runTool(project, info.run) project.send(''); + + isIgnored = await git.checkIgnore(project.uri.fsPath); + if (isIgnored.some((path) => path == project.uri.fsPath)){ + throw new Error(log.Error.gitIgnore.replace('{0}', project.uri.fsPath)); + } else { + await git.add(project.uri.fsPath); + await git.commit(`${path.basename(project.uri.fsPath)} after sapfor transformation ${info.title}`, project.uri.fsPath, {"--author": "Tsar-advisor "}); + } }, reason => { return onReject(reason, uri) }) })) diff --git a/tsconfig.json b/tsconfig.json index 11282c9..ba4c969 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "target": "es6", "outDir": "out", "lib": [ - "es6" + "es6", + "DOM" ], "sourceMap": true, "rootDir": "."