Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/img/templateVariables.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 33 additions & 4 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,42 @@ Several options are available to customize the extension. Open VS Code settings
- [Python] Command used to run python files. For eg. py, python3, pypy3, etc.

## Default Language Templates
- The path of the template that will be loaded when a new file of the default language is created by Competitive Companion
- For Java Users, the template shall be in the format where class name is `CLASS_NAME` as the file name so that CLASS_NAME in the code gets auto replaced.
\

- The path of the template that will be loaded when a new file of the default language is created by Competitive Companion.

### Available template variables

Place these strings in your template file; they will be replaced (if present)
when a new file is created.

| Placeholder | Description |
| -------------------------- | -------------------------------------------------------------------------------- |
| `CURSOR_PLACEHOLDER` | Placeholder for the initial cursor position in the new file |
| `CLASS_NAME` | Replaced with the class name (i.e, file name without extension); useful for Java |
| `PROBLEM_NAME` | The name of the problem |
| `PROBLEM_FILE_NAME` | The file name of the problem |
| `PROBLEM_URL` | The URL of the problem |
| `PROBLEM_GROUP` | The group of the problem |
| `CURRENT_YEAR` | The current year |
| `CURRENT_YEAR_SHORT` | The current year's last two digits |
| `CURRENT_MONTH` | The month as two digits (e.g. `02`) |
| `CURRENT_MONTH_NAME` | The full name of the month (e.g. `July`) |
| `CURRENT_MONTH_NAME_SHORT` | The short name of the month (e.g. `Jul`) |
| `CURRENT_DATE` | The day of the month as two digits (e.g. `08`) |
| `CURRENT_DAY_NAME` | The name of the day (e.g. `Monday`) |
| `CURRENT_DAY_NAME_SHORT` | The short name of the day (e.g. `Mon`) |
| `CURRENT_HOUR_24` | The current hour in 24-hour clock format |
| `CURRENT_HOUR_12` | The current hour in 12-hour clock format |
| `CURRENT_HOUR_AM_PM` | AM or PM (e.g. `AM`, `PM`) |
| `CURRENT_MINUTE` | The current minute as two digits |
| `CURRENT_SECOND` | The current second as two digits |
| `CURRENT_SECONDS_UNIX` | The number of seconds since the Unix epoch |
| `CURRENT_TIMEZONE_OFFSET` | The current UTC time zone offset (e.g. `-07:00`) |

![Templates](img/templateVariables.png)
![Templates](img/templateSettings.png)
![Templates](img/javaTemplate.png)


## Getting help

If you have trouble using the extension, find any bugs, or want to request a new
Expand Down
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
module.exports = {
silent: true,
roots: ['<rootDir>/out'],
testMatch: ['**/*.test.js'],
moduleNameMapper: {
'^vscode$': '<rootDir>/src/tests/__mocks__/vscode.js',
},
};
11 changes: 11 additions & 0 deletions package-lock.json

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

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@
"cph.general.defaultLanguageTemplateFileLocation": {
"type": "string",
"default": "",
"description": "The path to the template file that will be used when creating a new file for the default language via Competitive Companion. For Java templates, use 'CLASS_NAME' as a placeholder for the class name like 'class CLASS_NAME{...}'"
"description": "The path to the template file used when creating a new file for the default language via Competitive Companion. Placeholders such as CLASS_NAME, PROBLEM_NAME, PROBLEM_URL, CURRENT_YEAR, CURRENT_DATE, and others are replaced automatically. Cursor is put at the CURSOR_PLACEHOLDER position if provided. See the user guide for the full list."
},
"cph.general.autoShowJudge": {
"type": "boolean",
Expand Down Expand Up @@ -439,14 +439,15 @@
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@vscode/extension-telemetry": "^0.9.0",
"date-fns": "^4.1.0",
"python-shell": "^5.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-textarea-autosize": "^8.5.3",
"@vscode/extension-telemetry": "^0.9.0"
"react-textarea-autosize": "^8.5.3"
},
"repository": {
"type": "git",
"url": "https://github.com/agrawal-d/competitive-programming-helper/"
}
}
}
53 changes: 24 additions & 29 deletions src/companion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import {
useShortLuoguName,
useShortAtCoderName,
getMenuChoices,
getDefaultLanguageTemplateFileLocation,
} from './preferences';
import { getProblemName } from './submit';
import { spawn } from 'child_process';
import { getJudgeViewProvider } from './extension';
import { words_in_text } from './utilsPure';
import telmetry from './telmetry';
import os from 'os';
import { writeTemplateContents } from './templateEngine';

const emptyResponse: CphEmptyResponse = { empty: true };
let savedResponse: CphEmptyResponse | CphSubmitResponse = emptyResponse;
Expand Down Expand Up @@ -233,39 +233,34 @@ const handleNewProblem = async (problem: Problem) => {

if (!existsSync(srcPath)) {
writeFileSync(srcPath, '');

if (defaultLanguage) {
const templateLocation = getDefaultLanguageTemplateFileLocation();
if (templateLocation !== null) {
const templateExists = existsSync(templateLocation);
if (!templateExists) {
vscode.window.showErrorMessage(
`Template file does not exist: ${templateLocation}`,
);
} else {
let templateContents =
readFileSync(templateLocation).toString();

if (extn == 'java') {
const className = path.basename(
problemFileName,
'.java',
);
templateContents = templateContents.replace(
'CLASS_NAME',
className,
);
}
writeFileSync(srcPath, templateContents);
}
}
}
}

// Write the template contents to the problem file
writeTemplateContents(problem, extn);

saveProblem(srcPath, problem);
const doc = await vscode.workspace.openTextDocument(srcPath);

await vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
const editor = await vscode.window.showTextDocument(
doc,
vscode.ViewColumn.One,
);

// Move cursor to the first occurence of placeholder "CURSOR_PLACEHOLDER" and remove it
const cursorPlaceholder = config.templateVariables.CURSOR_PLACEHOLDER;
const text = doc.getText();
const index = text.indexOf(cursorPlaceholder);

// If the cursor placeholder is found, move the cursor to the placeholder and remove it
if (index !== -1) {
const start = doc.positionAt(index);
const end = doc.positionAt(index + cursorPlaceholder.length);
editor.selection = new vscode.Selection(start, start);
await editor.edit((editBuilder) => {
editBuilder.delete(new vscode.Range(start, end));
});
}

getJudgeViewProvider().extensionToJudgeViewMessage({
command: 'new-problem',
problem,
Expand Down
32 changes: 32 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,36 @@ export default {
'cj',
],
skipCompile: ['py', 'js', 'rb'],
templateVariables: {
// Idea from: https://code.visualstudio.com/docs/editing/userdefinedsnippets

// Special variable to place the starting cursor in the file
CURSOR_PLACEHOLDER: 'CURSOR_PLACEHOLDER',

// For inserting the class name:
CLASS_NAME: 'CLASS_NAME', // CLASS_NAME

// Problem metadata:
PROBLEM_NAME: 'PROBLEM_NAME', // The name of the problem
PROBLEM_FILE_NAME: 'PROBLEM_FILE_NAME', // The file name of the problem
PROBLEM_URL: 'PROBLEM_URL', // The URL of the problem
PROBLEM_GROUP: 'PROBLEM_GROUP', // The group of the problem

// For inserting the current date and time:
CURRENT_YEAR: 'CURRENT_YEAR', // The current year
CURRENT_YEAR_SHORT: 'CURRENT_YEAR_SHORT', // The current year's last two digits
CURRENT_MONTH: 'CURRENT_MONTH', // The month as two digits (e.g. '02')
CURRENT_MONTH_NAME: 'CURRENT_MONTH_NAME', // The full name of the month (e.g. 'July')
CURRENT_MONTH_NAME_SHORT: 'CURRENT_MONTH_NAME_SHORT', // The short name of the month (e.g. 'Jul')
CURRENT_DATE: 'CURRENT_DATE', // The day of the month as two digits (e.g. '08')
CURRENT_DAY_NAME: 'CURRENT_DAY_NAME', // The name of day (e.g. 'Monday')
CURRENT_DAY_NAME_SHORT: 'CURRENT_DAY_NAME_SHORT', // The short name of the day (e.g. 'Mon')
CURRENT_HOUR_24: 'CURRENT_HOUR_24', // The current hour in 24-hour clock format
CURRENT_HOUR_12: 'CURRENT_HOUR_12', // The current hour in 12-hour clock format
CURRENT_HOUR_AM_PM: 'CURRENT_HOUR_AM_PM', // The current hour in 12-hour clock format either "AM" or "PM"
CURRENT_MINUTE: 'CURRENT_MINUTE', // The current minute as two digits
CURRENT_SECOND: 'CURRENT_SECOND', // The current second as two digits
CURRENT_SECONDS_UNIX: 'CURRENT_SECONDS_UNIX', // The number of seconds since the Unix epoch
CURRENT_TIMEZONE_OFFSET: 'CURRENT_TIMEZONE_OFFSET', // The current UTC time zone offset as +HH:MM or -HH:MM (e.g. -07:00)
},
};
164 changes: 164 additions & 0 deletions src/templateEngine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { readFileSync } from 'fs';
import {
getDefaultLangPref,
getDefaultLanguageTemplateFileLocation,
} from './preferences';
import * as vscode from 'vscode';
import path from 'path';
import { existsSync, writeFileSync } from 'fs';
import { Problem } from './types';
import config from './config';
import { format } from 'date-fns';

/**
* Writes the template contents to the problem file
* @param problem - The problem object
* @param extn - The extension of the file
*/
export const writeTemplateContents = (problem: Problem, extn: string) => {
const defaultLanguage = getDefaultLangPref();

// Only write template if the extension of the file matches the default language extension
if (extn !== defaultLanguage) {
return;
}

const templateLocation = getDefaultLanguageTemplateFileLocation();

// If the user has not set a default language template file location, do nothing
if (!templateLocation) {
return;
}

const templateExists = existsSync(templateLocation);
if (!templateExists) {
vscode.window.showErrorMessage(
`Template file does not exist: ${templateLocation}`,
);
return;
}

const templateContents = readFileSync(templateLocation).toString();
const newTemplateContents = getTemplateContents(
templateContents,
extn,
problem,
);

writeFileSync(problem.srcPath, newTemplateContents);
};

/**
* Gets the contents of the template file with the placeholders replaced
* @param templateContents - The contents of the template file
* @param extn - The extension of the file
* @param problem - The problem object
* @returns The contents of the template file with the placeholders replaced
*/
export const getTemplateContents = (
templateContents: string,
extn: string,
problem: Problem,
) => {
const srcPath = problem.srcPath;
const problemFileName = path.basename(srcPath);

// Replace longest placeholders first so e.g. CURRENT_SECONDS_UNIX is not partially replaced by CURRENT_SECOND
const entriesByLength = Object.entries(config.templateVariables).sort(
(a, b) => b[1].length - a[1].length,
);
entriesByLength.forEach(([key, value]) => {
let templateKeyValue: string = '';
let shouldReplace: boolean = true;

const now = new Date();

switch (key) {
// class name
case config.templateVariables.CLASS_NAME:
templateKeyValue = path.basename(problemFileName, '.' + extn);
break;

// Problem metadata
case config.templateVariables.PROBLEM_FILE_NAME:
templateKeyValue = problemFileName;
break;
case config.templateVariables.PROBLEM_NAME:
templateKeyValue = problem.name;
break;
case config.templateVariables.PROBLEM_URL:
templateKeyValue = problem.url;
break;
case config.templateVariables.PROBLEM_GROUP:
templateKeyValue = problem.group;
break;

// Year
case config.templateVariables.CURRENT_YEAR:
templateKeyValue = format(now, 'yyyy');
break;
case config.templateVariables.CURRENT_YEAR_SHORT:
templateKeyValue = format(now, 'yy');
break;

// Month
case config.templateVariables.CURRENT_MONTH:
templateKeyValue = format(now, 'MM'); // '02'
break;
case config.templateVariables.CURRENT_MONTH_NAME:
templateKeyValue = format(now, 'MMMM'); // 'February'
break;
case config.templateVariables.CURRENT_MONTH_NAME_SHORT:
templateKeyValue = format(now, 'MMM'); // 'Feb'
break;

// Date/Day
case config.templateVariables.CURRENT_DATE:
templateKeyValue = format(now, 'dd');
break;
case config.templateVariables.CURRENT_DAY_NAME:
templateKeyValue = format(now, 'EEEE'); // 'Sunday'
break;
case config.templateVariables.CURRENT_DAY_NAME_SHORT:
templateKeyValue = format(now, 'EEE'); // 'Sun'
break;

// Time
case config.templateVariables.CURRENT_HOUR_24:
templateKeyValue = format(now, 'HH');
break;
case config.templateVariables.CURRENT_HOUR_12:
templateKeyValue = format(now, 'hh');
break;
case config.templateVariables.CURRENT_HOUR_AM_PM:
templateKeyValue = format(now, 'aa'); // 'AM' or 'PM'
break;
case config.templateVariables.CURRENT_MINUTE:
templateKeyValue = format(now, 'mm');
break;
case config.templateVariables.CURRENT_SECOND:
templateKeyValue = format(now, 'ss');
break;

// Unix & Timezone
case config.templateVariables.CURRENT_SECONDS_UNIX:
templateKeyValue = format(now, 't'); // Unix timestamp in seconds
break;
case config.templateVariables.CURRENT_TIMEZONE_OFFSET:
templateKeyValue = format(now, 'xxx'); // '+05:30'
break;
default:
shouldReplace = false;
break;
}

if (shouldReplace) {
templateContents = templateContents.replace(
value,
templateKeyValue,
);
}
});

return templateContents;
};
Loading