Skip to content

Commit

Permalink
Merge pull request #8 from renathossain/feat/environment
Browse files Browse the repository at this point in the history
Feat/environment
  • Loading branch information
renathossain authored May 11, 2024
2 parents d1b0d16 + 80994b9 commit d3e2e59
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 95 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## 1.2.0

- Added feature that adds the directory of the markdown file to Python's sys.path, allowing you to import modules from the same directory.
- Extension settings now apply without restarting VS Code.

## 1.1.0

- Enhanced user-friendliness of "Compiler Configuration" settings by integrating them into the VS Code Extension Settings UI. Users can now directly edit, add, or reset configurations for each language without the need to modify a .json file.
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ On Arch Linux, you can install all supported languages with 1 command!
sudo pacman --needed -S php perl r dart groovy go rustup ghc julia lua ruby nodejs npm python bash
```

On other less excellent operating systems, please research the installation process. Consider using the [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install) on Windows.
Please research the language installation process on other operating systems. Consider using the [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install) on Windows.

## Demo

Expand All @@ -35,7 +35,7 @@ Download or copy [DEMO.md](DEMO.md) into VS Code after installing this extension

### Compiler Configuration

- Add or modify the entries representing compiler configurations and restart VS Code afterwards. Each entry consists of an array defining the properties for a specific programming language. The array elements represent: [Language Name, File Extension, Compiler Command/Path, Compiled (true/false)]. You can only add non-compiled languages here.
- Add or modify the entries representing compiler configurations. Each entry consists of an array defining the properties for a specific programming language. The array elements represent: [Language Name, File Extension, Compiler Command/Path, Compiled (true/false)]. You can only add non-compiled languages here.

```json
// Example
Expand All @@ -50,6 +50,10 @@ Download or copy [DEMO.md](DEMO.md) into VS Code after installing this extension
}
```

### Python Path

Enabling this setting adds the directory of the markdown file to Python's sys.path, allowing you to import modules from the same directory.

## Future Development

Please note that the following features are desired for future development, but their implementation is not guaranteed:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Enables the seamless execution of code blocks in any language within Markdown files in VS Code",
"publisher": "renathossain",
"icon": "Icon.png",
"version": "1.1.0",
"version": "1.2.0",
"engines": {
"vscode": "^1.87.0"
},
Expand Down Expand Up @@ -49,6 +49,11 @@
"additionalProperties": {
"type": "string"
}
},
"markdownRunner.pythonPath": {
"type": "boolean",
"description": "Enabling this setting adds the directory of the markdown file to Python's sys.path, allowing you to import modules from the same directory.",
"default": true
}
}
}
Expand Down
12 changes: 5 additions & 7 deletions src/codeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import * as vscode from 'vscode';
import { parseCodeBlocks } from './parser';
import { languageConfigurations } from './extension';
import { getLanguageConfig } from './compilerConfig';

// For each parsed code block, provide a code lens button with the correct title and command:
// - Run the code, if the language is supported
Expand All @@ -29,7 +29,7 @@ export class ButtonCodeLensProvider implements vscode.CodeLensProvider {
// Loop through all parsed code blocks and generate buttons
for (const { language, code, range } of parseCodeBlocks(document)) {
// Check that parsed langauge is valid before creating code lens
if (languageConfigurations.hasOwnProperty(language)) {
if (getLanguageConfig(language, 'name') !== undefined) {
pushCodeLens(codeLenses, language, code, range);
}
// For bash and untyped code blocks, give `run in terminal` (line by line) option
Expand Down Expand Up @@ -60,12 +60,10 @@ function provideTitle(language: string): string {
return 'Copy';
} else if (language === '') {
return 'Run in Terminal';
}
const config = languageConfigurations[language];
if (config.compiled) {
return `Compile and Run ${config.name} File`;
} else if (getLanguageConfig(language, 'compiled')) {
return `Compile and Run ${getLanguageConfig(language, 'name')} File`;
} else {
return `Run ${config.name} File`;
return `Run ${getLanguageConfig(language, 'name')} File`;
}
}

Expand Down
43 changes: 38 additions & 5 deletions src/codeRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as cp from 'child_process';
import { languageConfigurations } from './extension';
import { getLanguageConfig } from './compilerConfig';
import { provideCommand } from './codeLens';

// Stores the paths of the temporary files created for running code
// which are cleaned up at the end
Expand All @@ -36,6 +37,26 @@ export function cleanTempFiles() {
});
}

// Create the commands and assign what they do
export function registerCommand(context: vscode.ExtensionContext, language: string) {
context.subscriptions.push(
vscode.commands.registerCommand(provideCommand(language), async (code: string) => {
if (language === 'copy') {
await vscode.env.clipboard.writeText(code);
vscode.window.showInformationMessage('Code copied to clipboard.');
} else if (language === '') {
await runCommandsInTerminal(code);
} else if (language === 'inline') {
await runCommandsInTerminal(code);
} else if (language === 'java') {
await executeJavaBlock(code);
} else {
await executeCodeBlock(language, code);
}
})
);
}

// For Java code blocks, special handling is needed
// The user is prompted to enter the name of the Java file to match the name of the main class
export function executeJavaBlock(code: string) {
Expand All @@ -46,8 +67,8 @@ export function executeJavaBlock(code: string) {
placeHolder: 'MyJavaFile'
}).then((javaCompiledName) => {
if (javaCompiledName) {
const extension = languageConfigurations['java'].extension;
const compiler = languageConfigurations['java'].compiler;
const extension = getLanguageConfig('java', 'extension');
const compiler = getLanguageConfig('java', 'compiler');
const javaCompiledPath = path.join(os.tmpdir(), javaCompiledName);
const javaSourcePath = `${javaCompiledPath}.${extension}`;

Expand All @@ -70,10 +91,22 @@ export function executeJavaBlock(code: string) {
export function executeCodeBlock(language: string, code: string) {
const compiledName = `temp_${Date.now()}`;
const compiledPath = path.join(os.tmpdir(), compiledName);
const extension = languageConfigurations[language].extension;
const compiler = languageConfigurations[language].compiler;
const extension = getLanguageConfig(language, 'extension');
const compiler = getLanguageConfig(language, 'compiler');
const sourcePath = `${compiledPath}.${extension}`;

// Read and store the Python Path configuration boolean
const config = vscode.workspace.getConfiguration();
const pythonPathEnabled = config.get<boolean>('markdownRunner.pythonPath');

// Inject the markdown file's path into the code
const editor = vscode.window.activeTextEditor;
if (pythonPathEnabled && editor && language === 'python') {
const documentUri = editor.document.uri;
const documentDirectory = path.dirname(documentUri.fsPath);
code = `import sys\nsys.path.insert(0, '${documentDirectory}')\n` + code;
}

// SECURITY: Only Owner Read and Write
fs.writeFileSync(sourcePath, code, { mode: 0o600 });
tempFilePaths.push(sourcePath);
Expand Down
53 changes: 22 additions & 31 deletions src/compilerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,30 @@ import * as vscode from 'vscode';
// Language configurations hold the info necessary for
// executing code blocks and providing codelens buttons
// Instead of hardcoding the language configurations,
// the user can modify the defaults in the `settings.json` file
type LanguageConfiguration = {
[key: string]: {
name: string;
extension: string;
compiler: string;
compiled: boolean;
};
};
// the user can modify the defaults in the extension settings.

// Reads and returns the language configurations from `settings.json` file
export function getLanguageConfigurations(): LanguageConfiguration {
// Return the raw language configuration
export function languageMap() {
const config = vscode.workspace.getConfiguration();
const languageConfigurations = config.get<any>('markdownRunner.compilerConfiguration');

const parsedConfig: LanguageConfiguration = {};

// Check if language configurations exist
if (languageConfigurations) {
// Loop through each language configuration
Object.keys(languageConfigurations).forEach((language: string) => {
const configValue = languageConfigurations[language];
// Parse the string to JSON array, considering the quotes
const configArray = JSON.parse(configValue.replace(/'/g, '"'));
// Append each language configuration to the dictionary
parsedConfig[language] = {
name: configArray[0],
extension: configArray[1],
compiler: configArray[2],
compiled: configArray[3]
};
});
return languageConfigurations || {};
}

// Return the language configuration for a specific language
export function getLanguageConfig(language: string, configuration: string) {
const languageConfig = languageMap();
if (languageConfig.hasOwnProperty(language)) {
const configValue = languageConfig[language];
const configArray = JSON.parse(configValue.replace(/'/g, '"'));
if (configuration === 'name') {
return configArray[0];
} else if (configuration === 'extension') {
return configArray[1];
} else if (configuration === 'compiler') {
return configArray[2];
} else if (configuration === 'compiled') {
return configArray[3];
}
}

return parsedConfig;
return undefined;
}
71 changes: 24 additions & 47 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,71 +16,48 @@

// ******************************ARCHITECTURE******************************
//
// extension.ts
// |
// +------------+-------+-----+-----------------+
// | | | |
// codeLinks.ts codeLens.ts codeRunner.ts compilerConfig.ts
// | |
// +-----+------+
// |
// parser.ts
// extension.ts
// |
// +------------+-------------+
// | | |
// codeLinks.ts codeLens.ts codeRunner.ts
// | | | |
// +-----+----+ +-----+------+
// | |
// parser.ts compilerConfig.ts
//
// - `extension.ts`: Responsible for activating the extension and orchestrating
// the loading of the language configuration using `compilerConfig.ts`. Passes
// the configuration to `codeLens.ts` and `codeRunner.ts`.
// the loading of the codeLinks, codeLens and registering the commands that
// perform actions such as executing or copying code blocks.
//
// - `codeLinks.ts`: Uses `parser.ts` to parse inline code snippets, then turn them
// into links that the user can 'Ctrl + Left Click' to run them.
//
// - `codeLens.ts`: Uses `parser.ts` to parse code blocks and determine
// their language and content, then generate appropriate code lens buttons for
// each code block in the editor. It only generates the buttons for languages
// specified in the language configuration.
//
// - `codeLinks.ts`: Uses `parser.ts` to parse inline code snippets, then turn them
// into links that the user can 'Ctrl + Left Click' to run them.
// specified in the language configuration provided by `compilerConfig.ts`.
//
// - `codeRunner.ts`: Uses the language configuration to provide the correct
// file extension and compiler to execute code blocks.
// - `codeRunner.ts`: Uses `compilerConfig.ts` to provide the correct file
// extension and compiler to execute code blocks.
//
// - `compilerConfig.ts`: Provides language configurations used by `codeLens.ts`
// and `codeRunner.ts`.
//
// - `parser.ts`: Responsible for parsing code blocks to determine their language
// and content, assisting `codeLens.ts` in generating code lens buttons.
// and content used by `codeLinks.ts` and `codeLens.ts`.
//
// - Data Flow:
// `compilerConfig.ts` ---> `extension.ts` +-+-> `codeLens.ts` +-+-> codeRunner.ts
// `parser.ts` +-+-> `codeLinks.ts` +-+
// `compilerConfig.ts` +-+-> `codeLinks.ts` +-+-> `extension.ts` +-+-> codeRunner.ts
// `parser.ts` +-+-> `codeLens.ts` +-+
//
// ************************************************************************

import * as vscode from 'vscode';
import { getLanguageConfigurations } from './compilerConfig';
import { ButtonCodeLensProvider, provideCommand } from './codeLens';
import { CodeSnippetLinkProvider } from './codeLinks';
import { cleanTempFiles, runCommandsInTerminal, executeJavaBlock, executeCodeBlock } from './codeRunner';

// Read and store the language configurations as a global variable
export const languageConfigurations = getLanguageConfigurations();

// Create the commands and assign what they do
function registerCommand(context: vscode.ExtensionContext, language: string) {
context.subscriptions.push(
vscode.commands.registerCommand(provideCommand(language), async (code: string) => {
if (language === 'copy') {
await vscode.env.clipboard.writeText(code);
vscode.window.showInformationMessage('Code copied to clipboard.');
} else if (language === '') {
await runCommandsInTerminal(code);
} else if (language === 'inline') {
await runCommandsInTerminal(code);
} else if (language === 'java') {
await executeJavaBlock(code);
} else {
await executeCodeBlock(language, code);
}
})
);
}
import { ButtonCodeLensProvider } from './codeLens';
import { cleanTempFiles, registerCommand } from './codeRunner';
import { languageMap } from './compilerConfig';

// Main function that runs when the extension is activated
export function activate(context: vscode.ExtensionContext) {
Expand All @@ -99,7 +76,7 @@ export function activate(context: vscode.ExtensionContext) {
);

// Register the commands used by the "subscriptions" above
for (const language of Object.keys(languageConfigurations)) {
for (const language of Object.keys(languageMap())) {
registerCommand(context, language);
}
registerCommand(context, '');
Expand Down

0 comments on commit d3e2e59

Please sign in to comment.