From 8984ac81f66607b29e606eb28462b4937540bbf9 Mon Sep 17 00:00:00 2001
From: Vuong <3168632+vuon9@users.noreply.github.com>
Date: Tue, 8 Mar 2022 19:20:40 +0700
Subject: [PATCH 1/2] WIP: Add codelens for main func (but still implementing
 the cmds)

---
 src/goMain.ts         |  4 +++
 src/goMainCodelens.ts | 81 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+)
 create mode 100644 src/goMainCodelens.ts

diff --git a/src/goMain.ts b/src/goMain.ts
index 585d5f2c44..3b35ad4dde 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -61,6 +61,7 @@ import { GO111MODULE, goModInit, isModSupported } from './goModules';
 import { playgroundCommand } from './goPlayground';
 import { GoReferencesCodeLensProvider } from './goReferencesCodelens';
 import { GoRunTestCodeLensProvider } from './goRunTestCodelens';
+import { GoMainCodeLensProvider } from './goMainCodelens';
 import { disposeGoStatusBar, expandGoStatusBar, outputChannel, updateGoStatusBar } from './goStatus';
 import {
 	debugPrevious,
@@ -210,9 +211,11 @@ If you would like additional configuration for diagnostics from gopls, please se
 		})
 	);
 	const testCodeLensProvider = new GoRunTestCodeLensProvider();
+	const mainCodeLensProvider = new GoMainCodeLensProvider();
 	const referencesCodeLensProvider = new GoReferencesCodeLensProvider();
 
 	ctx.subscriptions.push(vscode.languages.registerCodeLensProvider(GO_MODE, testCodeLensProvider));
+	ctx.subscriptions.push(vscode.languages.registerCodeLensProvider(GO_MODE, mainCodeLensProvider));
 	ctx.subscriptions.push(vscode.languages.registerCodeLensProvider(GO_MODE, referencesCodeLensProvider));
 
 	// debug
@@ -479,6 +482,7 @@ If you would like additional configuration for diagnostics from gopls, please se
 
 			if (updatedGoConfig['enableCodeLens']) {
 				testCodeLensProvider.setEnabled(updatedGoConfig['enableCodeLens']['runtest']);
+				mainCodeLensProvider.setEnabled(updatedGoConfig['enableCodeLens']['runmain']);
 				referencesCodeLensProvider.setEnabled(updatedGoConfig['enableCodeLens']['references']);
 			}
 
diff --git a/src/goMainCodelens.ts b/src/goMainCodelens.ts
new file mode 100644
index 0000000000..294a6b4378
--- /dev/null
+++ b/src/goMainCodelens.ts
@@ -0,0 +1,81 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/*---------------------------------------------------------
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+'use strict';
+
+import vscode = require('vscode');
+import { CancellationToken, CodeLens, TextDocument } from 'vscode';
+import { getGoConfig } from './config';
+import { GoBaseCodeLensProvider } from './goBaseCodelens';
+import { GoDocumentSymbolProvider } from './goOutline';
+import { getBenchmarkFunctions, getTestFunctions } from './testUtils';
+
+const mainFuncRegx = /^main$/u;
+
+export class GoMainCodeLensProvider extends GoBaseCodeLensProvider {
+	private readonly mainRegex = /^main.+/;
+
+	public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
+		if (!this.enabled) {
+			return [];
+		}
+		const config = getGoConfig(document.uri);
+		const codeLensConfig = config.get<{ [key: string]: any }>('enableCodeLens');
+		const codelensEnabled = codeLensConfig ? codeLensConfig['runmain'] : false;
+		if (!codelensEnabled || !document.fileName.match('main.go')) {
+			return [];
+		}
+
+		const codelenses = await Promise.all([
+			this.getCodeLensForMainFunc(document, token)
+		]);
+		return ([] as CodeLens[]).concat(...codelenses);
+	}
+
+	// Return the first main function
+	private async getMainFunc(
+		doc: vscode.TextDocument,
+		token: vscode.CancellationToken
+	): Promise<vscode.DocumentSymbol | undefined> {
+		const documentSymbolProvider = new GoDocumentSymbolProvider(true);
+		const symbols = await documentSymbolProvider.provideDocumentSymbols(doc, token);
+		if (!symbols || symbols.length === 0) {
+			return;
+		}
+		const symbol = symbols[0];
+		if (!symbol) {
+			return;
+		}
+		const children = symbol.children;
+
+		return children.find(sym => sym.kind === vscode.SymbolKind.Function && mainFuncRegx.test(sym.name));
+	}
+
+	private async getCodeLensForMainFunc(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
+		const mainPromise = async (): Promise<CodeLens[]> => {
+			const mainFunc = await this.getMainFunc(document, token);
+			if (!mainFunc) {
+				return [];
+			}
+
+			return [
+				new CodeLens(mainFunc.range, {
+					title: 'run',
+					command: 'go.main.run',
+					arguments: [{ functionName: mainFunc.name }]
+				}),
+				new CodeLens(mainFunc.range, {
+					title: 'package run',
+					command: 'go.main.package',
+					arguments: [{ functionName: mainFunc.name }]
+				})
+			];
+		};
+
+		return await mainPromise();
+	}
+}

From fa778fe1202cc3cf8935d58b53fa3639b7ff0694 Mon Sep 17 00:00:00 2001
From: Vuong <3168632+vuon9@users.noreply.github.com>
Date: Wed, 16 Mar 2022 00:04:26 +0700
Subject: [PATCH 2/2] feat: add running main func code lens

---
 docs/settings.md      |  4 ++-
 package.json          | 13 ++++++++-
 src/goMain.ts         |  8 +++++-
 src/goMainCodelens.ts | 64 +++++++++++++++++++++++++++++++++++--------
 4 files changed, 74 insertions(+), 15 deletions(-)

diff --git a/docs/settings.md b/docs/settings.md
index d45bb62467..16319068c8 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -210,12 +210,14 @@ Feature level setting to enable/disable code lens for references and run/debug t
 | --- | --- |
 | `references` | If true, enables the references code lens. Uses guru. Recalculates when there is change to the document followed by scrolling. Unnecessary when using the language server; use the call graph feature instead. <br/> Default: `false` |
 | `runtest` | If true, enables code lens for running and debugging tests <br/> Default: `true` |
+| `runmain` | If true, enables code lens for running main func <br/> Default: `true` |
 
 Default:
 ```
 {
 	"references" :	false,
 	"runtest" :	true,
+	"runmain" :	true,
 }
 ```
 ### `go.formatFlags`
@@ -492,7 +494,7 @@ Allowed Options: `package`, `workspace`, `off`
 Default: `"package"`
 ### `gopls`
 
-Customize `gopls` behavior by specifying the gopls' settings in this section. For example, 
+Customize `gopls` behavior by specifying the gopls' settings in this section. For example,
 ```
 "gopls" : {
 	"build.directoryFilters": ["-node_modules"]
diff --git a/package.json b/package.json
index e8ee7741e1..e7c811125b 100644
--- a/package.json
+++ b/package.json
@@ -481,6 +481,11 @@
         "command": "go.global.resetState",
         "title": "Go: Reset Global State",
         "description": "Reset keys in global state to undefined."
+      },
+      {
+        "command": "go.runMain",
+        "title": "Go: Run main() func on file",
+        "description": "Run main() func on file"
       }
     ],
     "breakpoints": [
@@ -1579,12 +1584,18 @@
               "type": "boolean",
               "default": true,
               "description": "If true, enables code lens for running and debugging tests"
+            },
+            "runmain": {
+              "type": "boolean",
+              "default": true,
+              "description": "If true, enables code lens for running main func"
             }
           },
           "additionalProperties": false,
           "default": {
             "references": false,
-            "runtest": true
+            "runtest": true,
+            "runmain": true
           },
           "description": "Feature level setting to enable/disable code lens for references and run/debug tests",
           "scope": "resource"
diff --git a/src/goMain.ts b/src/goMain.ts
index 3b35ad4dde..aa623775c3 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -61,7 +61,7 @@ import { GO111MODULE, goModInit, isModSupported } from './goModules';
 import { playgroundCommand } from './goPlayground';
 import { GoReferencesCodeLensProvider } from './goReferencesCodelens';
 import { GoRunTestCodeLensProvider } from './goRunTestCodelens';
-import { GoMainCodeLensProvider } from './goMainCodelens';
+import { GoMainCodeLensProvider, runMainFunc } from './goMainCodelens';
 import { disposeGoStatusBar, expandGoStatusBar, outputChannel, updateGoStatusBar } from './goStatus';
 import {
 	debugPrevious,
@@ -566,6 +566,12 @@ If you would like additional configuration for diagnostics from gopls, please se
 		})
 	);
 
+	ctx.subscriptions.push(
+		vscode.commands.registerCommand('go.runMain', (args) => {
+			runMainFunc()
+		})
+	)
+
 	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.show.commands', () => {
 			const extCommands = getExtensionCommands();
diff --git a/src/goMainCodelens.ts b/src/goMainCodelens.ts
index 294a6b4378..ce375e15d2 100644
--- a/src/goMainCodelens.ts
+++ b/src/goMainCodelens.ts
@@ -8,16 +8,18 @@
 'use strict';
 
 import vscode = require('vscode');
+import cp = require('child_process');
+
 import { CancellationToken, CodeLens, TextDocument } from 'vscode';
 import { getGoConfig } from './config';
 import { GoBaseCodeLensProvider } from './goBaseCodelens';
 import { GoDocumentSymbolProvider } from './goOutline';
-import { getBenchmarkFunctions, getTestFunctions } from './testUtils';
-
-const mainFuncRegx = /^main$/u;
+import { getBinPath } from './util';
+import { envPath, getCurrentGoRoot } from './utils/pathUtils';
+import { reject } from 'lodash';
 
 export class GoMainCodeLensProvider extends GoBaseCodeLensProvider {
-	private readonly mainRegex = /^main.+/;
+	private readonly mainRegex = /^main$/;
 
 	public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
 		if (!this.enabled) {
@@ -52,7 +54,7 @@ export class GoMainCodeLensProvider extends GoBaseCodeLensProvider {
 		}
 		const children = symbol.children;
 
-		return children.find(sym => sym.kind === vscode.SymbolKind.Function && mainFuncRegx.test(sym.name));
+		return children.find(sym => sym.kind === vscode.SymbolKind.Function && this.mainRegex.test(sym.name));
 	}
 
 	private async getCodeLensForMainFunc(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
@@ -64,13 +66,8 @@ export class GoMainCodeLensProvider extends GoBaseCodeLensProvider {
 
 			return [
 				new CodeLens(mainFunc.range, {
-					title: 'run',
-					command: 'go.main.run',
-					arguments: [{ functionName: mainFunc.name }]
-				}),
-				new CodeLens(mainFunc.range, {
-					title: 'package run',
-					command: 'go.main.package',
+					title: 'run main',
+					command: 'go.runMain',
 					arguments: [{ functionName: mainFunc.name }]
 				})
 			];
@@ -79,3 +76,46 @@ export class GoMainCodeLensProvider extends GoBaseCodeLensProvider {
 		return await mainPromise();
 	}
 }
+
+const mainFuncOutputChannel = vscode.window.createOutputChannel('Go Main');
+
+export async function runMainFunc() {
+	let outputChannel = mainFuncOutputChannel
+	const goRuntimePath = getBinPath('go');
+	if (!goRuntimePath) {
+		vscode.window.showErrorMessage(
+			`Failed to run "go run ." as the "go" binary cannot be found in either GOROOT(${getCurrentGoRoot}) or PATH(${envPath})`
+		);
+		return Promise.resolve(false);
+	}
+
+	const editor = vscode.window.activeTextEditor;
+	const documentUri = editor ? editor.document.uri : null;
+	const args = ['run', documentUri.path];
+
+	outputChannel.clear()
+	outputChannel.show(true)
+	outputChannel.appendLine(["Running main func: ", goRuntimePath, ...args].join(' '))
+
+	cp.execFile(
+		goRuntimePath,
+		args,
+		{ },
+		(err, stdout, stderr) => {
+			try {
+				if (err) {
+					outputChannel.appendLine(err.message);
+					return;
+				}
+				if (stdout) {
+					outputChannel.append(stdout);
+				}
+				if (stderr) {
+					outputChannel.append(stderr);
+				}
+			} catch (e) {
+				reject(e);
+			}
+		}
+	)
+}
\ No newline at end of file