feat: export image of the current theme
10
.github/workflows/docs.yml
vendored
|
@ -13,10 +13,20 @@ jobs:
|
|||
uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
- name: Build
|
||||
run: |
|
||||
cd src
|
||||
go build -o docs/oh-my-posh
|
||||
cd ..
|
||||
- name: Install and Build 🔧
|
||||
run: |
|
||||
cd docs
|
||||
npm install
|
||||
npm run themes
|
||||
npm run build
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@3.6.2
|
||||
|
|
3
.gitignore
vendored
|
@ -155,3 +155,6 @@ bin/
|
|||
Output/
|
||||
*.sha256
|
||||
*.7z
|
||||
|
||||
# images
|
||||
*.png
|
||||
|
|
25
.vscode/launch.json
vendored
|
@ -1,7 +1,4 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
|
@ -42,6 +39,28 @@
|
|||
"--shell=pwsh",
|
||||
"--config=${workspaceRoot}/themes/jandedobbeleer.omp.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Export PNG",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceRoot}/src",
|
||||
"args": [
|
||||
"--export-png",
|
||||
"--shell=shell",
|
||||
"--rprompt-offset=40",
|
||||
"--cursor-padding=15",
|
||||
"--config=${workspaceRoot}/themes/jandedobbeleer.omp.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Theme export",
|
||||
"cwd": "${workspaceFolder}/docs",
|
||||
"program": "${workspaceRoot}/docs/export_themes.js",
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
54
docs/docs/share.mdx
Normal file
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
id: share
|
||||
title: Share
|
||||
sidebar_label: 📸 Share
|
||||
---
|
||||
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
You can export your prompt to an image which you can share online. You have the ability to align it correctly and add your name for credits too.
|
||||
|
||||
:::warning
|
||||
Some glyphs aren't rendered correctly, that's not you but the limitations of the renderer. Depending on your config, you might have to tweak
|
||||
the output a little bit.
|
||||
:::
|
||||
|
||||
<Tabs
|
||||
defaultValue="powershell"
|
||||
values={[
|
||||
{ label: 'powershell', value: 'powershell', },
|
||||
{ label: 'others', value: 'others', },
|
||||
]
|
||||
}>
|
||||
<TabItem value="powershell">
|
||||
|
||||
You can make use of the `Export-PoshImage` function to export your current theme configuration.
|
||||
|
||||
```powershell
|
||||
Export-PoshImage -CursorPadding 50
|
||||
```
|
||||
|
||||
There are a couple of parameters you can use to tweak the image rendering:
|
||||
|
||||
- `CursorPadding`: spaces to add after the cursor indication (`_`)
|
||||
- `RPromptOffset`: spaces to add **before** a block that's right aligned
|
||||
- `Author`: the name of the creator, added after `https://ohmyposh.dev`
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="others">
|
||||
|
||||
The oh-my-posh executable has the `--export-png` switch to export your current theme configuration.
|
||||
|
||||
```powershell
|
||||
oh-my-posh --config ~/.mytheme.omp.json --export-png --cursor-padding 50
|
||||
```
|
||||
|
||||
There are a couple of additional switches you can use to tweak the image rendering:
|
||||
|
||||
- `--cursor-padding`: spaces to add after the cursor indication (`_`)
|
||||
- `--rprompt-offset`: spaces to add **before** a block that's right aligned
|
||||
- `--author`: the name of the creator, added after `https://ohmyposh.dev`
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
|
@ -16,102 +16,6 @@ Once you're ready to swap to a theme, follow the steps described in [🚀Install
|
|||
|
||||
Themes with `minimal` in their names do not require a Nerd Font. Read about [🆎Fonts][fonts] for more information.
|
||||
|
||||
### [Agnoster]
|
||||
|
||||
[![Agnoster](/img/themes/agnoster.png)][Agnoster]
|
||||
|
||||
### [AgnosterPlus]
|
||||
|
||||
[![AgnosterPlus](/img/themes/agnosterplus.png)][AgnosterPlus]
|
||||
|
||||
### [Aliens]
|
||||
|
||||
[![Aliens](/img/themes/aliens.png)][Aliens]
|
||||
|
||||
### [Avit]
|
||||
|
||||
[![Avit](/img/themes/avit.png)][Avit]
|
||||
|
||||
### [DarkBlood]
|
||||
|
||||
[![DarkBlood](/img/themes/darkblood.png)][DarkBlood]
|
||||
|
||||
### [Emodipt]
|
||||
|
||||
[![Emodipt](/img/themes/emodipt.png)][Emodipt]
|
||||
|
||||
### [Fish]
|
||||
|
||||
[![Fish](/img/themes/fish.png)][Fish]
|
||||
|
||||
### [Honukai]
|
||||
|
||||
[![Honukai](/img/themes/honukai.png)][Honukai]
|
||||
|
||||
### [JanDeDobbeleer]
|
||||
|
||||
[![JanDeDobbeleer](/img/themes/jandedobbeleer.png)][JanDeDobbeleer]
|
||||
|
||||
### [Lambda]
|
||||
|
||||
[![Lambda](/img/themes/lambda.png)][Lambda]
|
||||
|
||||
### [Material]
|
||||
|
||||
[![Material](/img/themes/material.png)][Material]
|
||||
|
||||
### [ParaRussel]
|
||||
|
||||
[![ParaRussel](/img/themes/pararussel.png)][ParaRussel]
|
||||
|
||||
### [Powerlevel10k_Classic]
|
||||
|
||||
[![Powerlevel10k_Classic](/img/themes/powerlevel10k_classic.png)][Powerlevel10k_Classic]
|
||||
|
||||
### [Powerlevel10k_Lean]
|
||||
|
||||
[![Powerlevel10k_Lean](/img/themes/powerlevel10k_lean.png)][Powerlevel10k_Lean]
|
||||
|
||||
### [PowerLine]
|
||||
|
||||
[![PowerLine](/img/themes/powerline.png)][PowerLine]
|
||||
|
||||
### [RobbyRussel]
|
||||
|
||||
[![RobbyRussel](/img/themes/robbyrussel.png)][RobbyRussel]
|
||||
|
||||
### [Sorin]
|
||||
|
||||
[![Sorin](/img/themes/sorin.png)][Sorin]
|
||||
|
||||
### [Star]
|
||||
|
||||
[![Star](/img/themes/star.png)][Star]
|
||||
|
||||
### [Zash]
|
||||
|
||||
[![Zash](/img/themes/zash.png)][Zash]
|
||||
|
||||
[themes]: https://github.com/JanDeDobbeleer/oh-my-posh/tree/main/themes
|
||||
[fonts]: /docs/fonts
|
||||
[replace-you-existing-prompt]: /docs/installation#3-replace-your-existing-prompt
|
||||
|
||||
[Agnoster]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/agnoster.omp.json 'Agnoster'
|
||||
[AgnosterPlus]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/agnosterplus.omp.json 'AgnosterPlus'
|
||||
[Aliens]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/aliens.omp.json 'Aliens'
|
||||
[Avit]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/avit.omp.json 'Avit'
|
||||
[DarkBlood]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/darkblood.omp.json 'DarkBlood'
|
||||
[Emodipt]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/emodipt.omp.json 'Emodipt'
|
||||
[Fish]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/fish.omp.json 'Fish'
|
||||
[Honukai]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/honukai.omp.json 'Honukai'
|
||||
[JanDeDobbeleer]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/jandedobbeleer.omp.json 'JanDeDobbeleer'
|
||||
[Lambda]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/lambda.omp.json 'Lambda'
|
||||
[Material]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/material.omp.json 'Material'
|
||||
[ParaRussel]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/ParaRussel.omp.json 'ParaRussel'
|
||||
[Powerlevel10k_Classic]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/powerlevel10k_classic.omp.json 'Powerlevel10k_Classic'
|
||||
[Powerlevel10k_Lean]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/powerlevel10k_lean.omp.json 'Powerlevel10k_Lean'
|
||||
[PowerLine]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/powerline.omp.json 'PowerLine'
|
||||
[RobbyRussel]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/robbyrussel.omp.json 'RobbyRussel'
|
||||
[Sorin]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/sorin.omp.json 'Sorin'
|
||||
[Star]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/star.omp.json 'Star'
|
||||
[Zash]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/zash.omp.json 'Zash'
|
||||
|
|
102
docs/export_themes.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
//jshint esversion:8
|
||||
//jshint node:true
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
const exec = util.promisify(require('child_process').exec);
|
||||
|
||||
const themesConfigDir = "./../themes";
|
||||
const themesStaticDir = "./static/img/themes";
|
||||
|
||||
function newThemeConfig(rpromptOffset = 40, cursorPadding = 30, author = "") {
|
||||
var config = {
|
||||
rpromptOffset: rpromptOffset,
|
||||
cursorPadding: cursorPadding,
|
||||
author: author
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
||||
let themeConfigOverrrides = new Map();
|
||||
themeConfigOverrrides.set('agnoster.omp.json', newThemeConfig(40, 40));
|
||||
themeConfigOverrrides.set('agnosterplus.omp.json', newThemeConfig(80));
|
||||
themeConfigOverrrides.set('avit.omp.json', newThemeConfig(40, 80));
|
||||
themeConfigOverrrides.set('blueish.omp.json', newThemeConfig(40, 100));
|
||||
themeConfigOverrrides.set('cert.omp.json', newThemeConfig(40, 50));
|
||||
themeConfigOverrrides.set('cinnamon.omp.json', newThemeConfig(40, 80));
|
||||
themeConfigOverrrides.set('darkblood.omp.json', newThemeConfig(40, 40));
|
||||
themeConfigOverrrides.set('honukai.omp.json', newThemeConfig(20));
|
||||
themeConfigOverrrides.set('hotstick.minimal.omp.json', newThemeConfig(40, 10));
|
||||
themeConfigOverrrides.set('huvix.omp.json', newThemeConfig(40, 70));
|
||||
themeConfigOverrrides.set('jandedobbeleer.omp.json', newThemeConfig(40, 15));
|
||||
themeConfigOverrrides.set('lambda.omp.json', newThemeConfig(40, 40));
|
||||
themeConfigOverrrides.set('material.omp.json', newThemeConfig(40, 40));
|
||||
themeConfigOverrrides.set('microverse-power.omp.json', newThemeConfig(40, 100));
|
||||
themeConfigOverrrides.set('negligible.omp.json', newThemeConfig(10));
|
||||
themeConfigOverrrides.set('paradox.omp.json', newThemeConfig(40, 100));
|
||||
themeConfigOverrrides.set('powerlevel10k_classic.omp.json', newThemeConfig(10));
|
||||
themeConfigOverrrides.set('powerlevel10k_lean.omp.json', newThemeConfig(80));
|
||||
themeConfigOverrrides.set('powerline.omp.json', newThemeConfig(40, 40));
|
||||
themeConfigOverrrides.set('pure.omp.json', newThemeConfig(40, 80));
|
||||
themeConfigOverrrides.set('remk.omp.json', newThemeConfig(40, 40));
|
||||
themeConfigOverrrides.set('robbyrussel.omp.json', newThemeConfig(40, 40));
|
||||
themeConfigOverrrides.set('slim.omp.json', newThemeConfig(10, 80));
|
||||
themeConfigOverrrides.set('slimfat.omp.json', newThemeConfig(10, 93));
|
||||
themeConfigOverrrides.set('space.omp.json', newThemeConfig(40, 40));
|
||||
themeConfigOverrrides.set('spaceship.omp.json', newThemeConfig(40, 40));
|
||||
themeConfigOverrrides.set('star.omp.json', newThemeConfig(40, 70));
|
||||
themeConfigOverrrides.set('stelbent.minimal.omp.json', newThemeConfig(70));
|
||||
themeConfigOverrrides.set('ys.omp.json', newThemeConfig(40, 100));
|
||||
themeConfigOverrrides.set('zash.omp.json', newThemeConfig(40, 40));
|
||||
|
||||
(async () => {
|
||||
const themes = await fs.promises.readdir(themesConfigDir);
|
||||
let links = new Array();
|
||||
|
||||
for (const theme of themes) {
|
||||
if (!theme.endsWith('.omp.json')) {
|
||||
continue;
|
||||
}
|
||||
const configPath = path.join(themesConfigDir, theme);
|
||||
|
||||
let config = newThemeConfig();
|
||||
if (themeConfigOverrrides.has(theme)) {
|
||||
config = themeConfigOverrrides.get(theme);
|
||||
}
|
||||
|
||||
let poshCommand = `oh-my-posh --config=${configPath} --shell shell --export-png`;
|
||||
poshCommand += ` --rprompt-offset=${config.rpromptOffset}`;
|
||||
poshCommand += ` --cursor-padding=${config.cursorPadding}`;
|
||||
if (config.author !== '') {
|
||||
poshCommand += ` --author=${config.author}`;
|
||||
}
|
||||
|
||||
const { _, stderr } = await exec(poshCommand);
|
||||
|
||||
if (stderr !== '') {
|
||||
console.error(`Unable to create image for ${theme}, please try manually`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const image = theme.replace('.omp.json', '.png');
|
||||
const toPath = path.join(themesStaticDir, image);
|
||||
|
||||
await fs.promises.rename(image, toPath);
|
||||
|
||||
const themeName = theme.replace('.omp.json', '');
|
||||
|
||||
const themeData = `
|
||||
### [${themeName}]
|
||||
|
||||
[![${themeName}](/img/themes/${themeName}.png)][${themeName}]
|
||||
`;
|
||||
|
||||
await fs.promises.appendFile('./docs/themes.md', themeData);
|
||||
|
||||
links.push(`[${themeName}]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/themes/${theme} '${themeName}'\n`);
|
||||
}
|
||||
|
||||
for (const link of links) {
|
||||
await fs.promises.appendFile('./docs/themes.md', link);
|
||||
}
|
||||
})();
|
|
@ -5,7 +5,7 @@
|
|||
"scripts": {
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle"
|
||||
"themes": "node export_themes.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.0.0-alpha.72",
|
||||
|
|
|
@ -3,7 +3,7 @@ module.exports = {
|
|||
{
|
||||
type: "category",
|
||||
label: "Getting Started",
|
||||
items: ["introduction", "upgrading", "installation", "configure", "themes", "fonts"],
|
||||
items: ["introduction", "upgrading", "installation", "configure", "themes", "share", "fonts"],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
|
|
0
docs/static/img/themes/.keep
vendored
Normal file
BIN
docs/static/img/themes/agnoster.png
vendored
Before Width: | Height: | Size: 80 KiB |
BIN
docs/static/img/themes/agnosterplus.png
vendored
Before Width: | Height: | Size: 83 KiB |
BIN
docs/static/img/themes/aliens.png
vendored
Before Width: | Height: | Size: 79 KiB |
BIN
docs/static/img/themes/avit.png
vendored
Before Width: | Height: | Size: 67 KiB |
BIN
docs/static/img/themes/darkblood.png
vendored
Before Width: | Height: | Size: 71 KiB |
BIN
docs/static/img/themes/emodipt.png
vendored
Before Width: | Height: | Size: 94 KiB |
BIN
docs/static/img/themes/fish.png
vendored
Before Width: | Height: | Size: 77 KiB |
BIN
docs/static/img/themes/honukai.png
vendored
Before Width: | Height: | Size: 83 KiB |
BIN
docs/static/img/themes/jandedobbeleer.png
vendored
Before Width: | Height: | Size: 94 KiB |
BIN
docs/static/img/themes/lambda.png
vendored
Before Width: | Height: | Size: 66 KiB |
BIN
docs/static/img/themes/material.png
vendored
Before Width: | Height: | Size: 69 KiB |
BIN
docs/static/img/themes/paradox.png
vendored
Before Width: | Height: | Size: 82 KiB |
BIN
docs/static/img/themes/pararussel.png
vendored
Before Width: | Height: | Size: 73 KiB |
BIN
docs/static/img/themes/powerlevel10k_classic.png
vendored
Before Width: | Height: | Size: 86 KiB |
BIN
docs/static/img/themes/powerlevel10k_lean.png
vendored
Before Width: | Height: | Size: 70 KiB |
BIN
docs/static/img/themes/powerline.png
vendored
Before Width: | Height: | Size: 78 KiB |
BIN
docs/static/img/themes/robbyrussel.png
vendored
Before Width: | Height: | Size: 65 KiB |
BIN
docs/static/img/themes/sorin.png
vendored
Before Width: | Height: | Size: 79 KiB |
BIN
docs/static/img/themes/star.png
vendored
Before Width: | Height: | Size: 67 KiB |
BIN
docs/static/img/themes/zash.png
vendored
Before Width: | Height: | Size: 67 KiB |
|
@ -3,8 +3,10 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
"golang.org/x/text/unicode/norm"
|
||||
const (
|
||||
ANSIRegex = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
|
||||
)
|
||||
|
||||
type ansiFormats struct {
|
||||
|
@ -44,7 +46,7 @@ func (a *ansiFormats) init(shell string) {
|
|||
a.title = "%%{\x1b]0;%s\007%%}"
|
||||
a.colorSingle = "%%{\x1b[%sm%%}%s%%{\x1b[0m%%}"
|
||||
a.colorFull = "%%{\x1b[%sm\x1b[%sm%%}%s%%{\x1b[0m%%}"
|
||||
a.colorTransparent = "%%{\x1b[%s;49m\x1b[7m%%}%s%%{\x1b[m\x1b[0m%%}"
|
||||
a.colorTransparent = "%%{\x1b[%s;49m\x1b[7m%%}%s%%{\x1b[0m%%}"
|
||||
a.escapeLeft = "%{"
|
||||
a.escapeRight = "%}"
|
||||
a.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}"
|
||||
|
@ -64,7 +66,7 @@ func (a *ansiFormats) init(shell string) {
|
|||
a.title = "\\[\x1b]0;%s\007\\]"
|
||||
a.colorSingle = "\\[\x1b[%sm\\]%s\\[\x1b[0m\\]"
|
||||
a.colorFull = "\\[\x1b[%sm\x1b[%sm\\]%s\\[\x1b[0m\\]"
|
||||
a.colorTransparent = "\\[\x1b[%s;49m\x1b[7m\\]%s\\[\x1b[m\x1b[0m\\]"
|
||||
a.colorTransparent = "\\[\x1b[%s;49m\x1b[7m\\]%s\\[\x1b[0m\\]"
|
||||
a.escapeLeft = "\\["
|
||||
a.escapeRight = "\\]"
|
||||
a.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]"
|
||||
|
@ -84,7 +86,7 @@ func (a *ansiFormats) init(shell string) {
|
|||
a.title = "\x1b]0;%s\007"
|
||||
a.colorSingle = "\x1b[%sm%s\x1b[0m"
|
||||
a.colorFull = "\x1b[%sm\x1b[%sm%s\x1b[0m"
|
||||
a.colorTransparent = "\x1b[%s;49m\x1b[7m%s\x1b[m\x1b[0m"
|
||||
a.colorTransparent = "\x1b[%s;49m\x1b[7m%s\x1b[0m"
|
||||
a.escapeLeft = ""
|
||||
a.escapeRight = ""
|
||||
a.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\"
|
||||
|
@ -93,23 +95,28 @@ func (a *ansiFormats) init(shell string) {
|
|||
a.italic = "\x1b[3m%s\x1b[23m"
|
||||
a.underline = "\x1b[4m%s\x1b[24m"
|
||||
a.strikethrough = "\x1b[9m%s\x1b[29m"
|
||||
a.strikethrough = "\x1b[9m%s\x1b[29m"
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ansiFormats) lenWithoutANSI(text string) int {
|
||||
rANSI := "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
|
||||
stripped := replaceAllString(rANSI, text, "")
|
||||
if len(text) == 0 {
|
||||
return 0
|
||||
}
|
||||
// replace hyperlinks
|
||||
matches := findAllNamedRegexMatch(`(?P<STR>\x1b]8;;file:\/\/(.+)\x1b\\(?P<URL>.+)\x1b]8;;\x1b\\)`, text)
|
||||
for _, match := range matches {
|
||||
text = strings.ReplaceAll(text, match[STR], match[URL])
|
||||
}
|
||||
// replace console title
|
||||
matches = findAllNamedRegexMatch(`(?P<STR>\x1b\]0;(.+)\007)`, text)
|
||||
for _, match := range matches {
|
||||
text = strings.ReplaceAll(text, match[STR], "")
|
||||
}
|
||||
stripped := replaceAllString(ANSIRegex, text, "")
|
||||
stripped = strings.ReplaceAll(stripped, a.escapeLeft, "")
|
||||
stripped = strings.ReplaceAll(stripped, a.escapeRight, "")
|
||||
var i norm.Iter
|
||||
i.InitString(norm.NFD, stripped)
|
||||
var count int
|
||||
for !i.Done() {
|
||||
i.Next()
|
||||
count++
|
||||
}
|
||||
return count
|
||||
runeText := []rune(stripped)
|
||||
return len(runeText)
|
||||
}
|
||||
|
||||
func (a *ansiFormats) generateHyperlink(text string) string {
|
||||
|
|
|
@ -129,7 +129,7 @@ func (e *engine) setStringValues(segments []*Segment) {
|
|||
}
|
||||
}
|
||||
|
||||
func (e *engine) render() {
|
||||
func (e *engine) render() string {
|
||||
for _, block := range e.config.Blocks {
|
||||
// if line break, append a line break
|
||||
switch block.Type {
|
||||
|
@ -161,19 +161,18 @@ func (e *engine) render() {
|
|||
}
|
||||
|
||||
if !e.config.OSC99 {
|
||||
e.print()
|
||||
return
|
||||
return e.print()
|
||||
}
|
||||
cwd := e.env.getcwd()
|
||||
if e.env.isWsl() {
|
||||
cwd, _ = e.env.runCommand("wslpath", "-m", cwd)
|
||||
}
|
||||
e.renderer.osc99(cwd)
|
||||
e.print()
|
||||
return e.print()
|
||||
}
|
||||
|
||||
// debug will loop through your config file and output the timings for each segments
|
||||
func (e *engine) debug() {
|
||||
func (e *engine) debug() string {
|
||||
var segmentTimings []SegmentTiming
|
||||
largestSegmentNameLength := 0
|
||||
e.renderer.write("\n\x1b[1mHere are the timings of segments in your prompt:\x1b[0m\n\n")
|
||||
|
@ -236,19 +235,19 @@ func (e *engine) debug() {
|
|||
segmentName := fmt.Sprintf("%s(%t)", segment.name, segment.enabled)
|
||||
e.renderer.write(fmt.Sprintf("%-*s - %3d ms - %s\n", largestSegmentNameLength, segmentName, duration, segment.stringValue))
|
||||
}
|
||||
fmt.Print(e.renderer.string())
|
||||
return e.renderer.string()
|
||||
}
|
||||
|
||||
func (e *engine) print() {
|
||||
func (e *engine) print() string {
|
||||
switch e.env.getShellName() {
|
||||
case zsh:
|
||||
if *e.env.getArgs().Eval {
|
||||
// escape double quotes contained in the prompt
|
||||
fmt.Printf("PS1=\"%s\"", strings.ReplaceAll(e.renderer.string(), "\"", "\"\""))
|
||||
fmt.Printf("\nRPROMPT=\"%s\"", e.rprompt)
|
||||
return
|
||||
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.renderer.string(), "\"", "\"\""))
|
||||
prompt += fmt.Sprintf("\nRPROMPT=\"%s\"", e.rprompt)
|
||||
return prompt
|
||||
}
|
||||
case pwsh, powershell5, bash:
|
||||
case pwsh, powershell5, bash, shelly:
|
||||
if e.rprompt != "" {
|
||||
e.renderer.saveCursorPosition()
|
||||
e.renderer.carriageForward()
|
||||
|
@ -257,7 +256,7 @@ func (e *engine) print() {
|
|||
e.renderer.restoreCursorPosition()
|
||||
}
|
||||
}
|
||||
fmt.Print(e.renderer.string())
|
||||
return e.renderer.string()
|
||||
}
|
||||
|
||||
func (e *engine) resetBlock() {
|
||||
|
|
BIN
src/font/VictorMono-Bold.ttf
Normal file
BIN
src/font/VictorMono-Italic.ttf
Normal file
BIN
src/font/VictorMono-Regular.ttf
Normal file
|
@ -11,7 +11,10 @@ require (
|
|||
github.com/alecthomas/colour v0.1.0 // indirect
|
||||
github.com/alecthomas/repr v0.0.0-20210301060118-828286944d6a // indirect
|
||||
github.com/distatus/battery v0.10.0
|
||||
github.com/esimov/stackblur-go v1.0.0
|
||||
github.com/fogleman/gg v1.3.0
|
||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/uuid v1.2.0 // indirect
|
||||
github.com/gookit/color v1.3.8
|
||||
github.com/gookit/config/v2 v2.0.22
|
||||
|
@ -24,6 +27,7 @@ require (
|
|||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tklauser/go-sysconf v0.3.5 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492
|
||||
golang.org/x/text v0.3.5
|
||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06 // indirect
|
||||
|
|
|
@ -23,9 +23,15 @@ github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJE
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/esimov/stackblur-go v1.0.0 h1:PuXWrQ16VIh6Di+tn3CpKAyzkIIECo9DPGVecrzyDGc=
|
||||
github.com/esimov/stackblur-go v1.0.0/go.mod h1:a3zzeKuJKUpCcReHmEsuPaEnq42D2b/bHoCI8UjIuMY=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
@ -101,6 +107,8 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
|
|
444
src/image.go
Normal file
|
@ -0,0 +1,444 @@
|
|||
// Copyright © 2020 The Homeport Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// https://github.com/homeport/termshot
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/esimov/stackblur-go"
|
||||
"github.com/fogleman/gg"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"golang.org/x/image/font"
|
||||
)
|
||||
|
||||
const (
|
||||
red = "#ED655A"
|
||||
yellow = "#E1C04C"
|
||||
green = "#71BD47"
|
||||
|
||||
// known ansi sequences
|
||||
FG = "FG"
|
||||
BG = "BG"
|
||||
STR = "STR"
|
||||
URL = "URL"
|
||||
invertedColor = "inverted"
|
||||
invertedColorSingle = "invertedsingle"
|
||||
fullColor = "full"
|
||||
foreground = "foreground"
|
||||
reset = "reset"
|
||||
bold = "bold"
|
||||
boldReset = "boldr"
|
||||
italic = "italic"
|
||||
italicReset = "italicr"
|
||||
underline = "underline"
|
||||
underlineReset = "underliner"
|
||||
strikethrough = "strikethrough"
|
||||
strikethroughReset = "strikethroughr"
|
||||
color16 = "color16"
|
||||
left = "left"
|
||||
osc99 = "osc99"
|
||||
lineChange = "linechange"
|
||||
title = "title"
|
||||
link = "link"
|
||||
)
|
||||
|
||||
//go:embed font/VictorMono-Bold.ttf
|
||||
var victorMonoBold []byte
|
||||
|
||||
//go:embed font/VictorMono-Regular.ttf
|
||||
var victorMonoRegular []byte
|
||||
|
||||
//go:embed font/VictorMono-Italic.ttf
|
||||
var victorMonoItalic []byte
|
||||
|
||||
type RGB struct {
|
||||
r int
|
||||
g int
|
||||
b int
|
||||
}
|
||||
|
||||
func NewRGBColor(ansiColor string) *RGB {
|
||||
colors := strings.Split(ansiColor, ";")
|
||||
r, _ := strconv.Atoi(colors[0])
|
||||
g, _ := strconv.Atoi(colors[1])
|
||||
b, _ := strconv.Atoi(colors[2])
|
||||
return &RGB{
|
||||
r: r,
|
||||
g: g,
|
||||
b: b,
|
||||
}
|
||||
}
|
||||
|
||||
type ImageRenderer struct {
|
||||
ansiString string
|
||||
author string
|
||||
formats *ansiFormats
|
||||
|
||||
factor float64
|
||||
|
||||
columns int
|
||||
rows int
|
||||
|
||||
defaultForegroundColor *RGB
|
||||
defaultBackgroundColor *RGB
|
||||
|
||||
shadowBaseColor string
|
||||
shadowRadius uint8
|
||||
shadowOffsetX float64
|
||||
shadowOffsetY float64
|
||||
|
||||
padding float64
|
||||
margin float64
|
||||
|
||||
regular font.Face
|
||||
bold font.Face
|
||||
italic font.Face
|
||||
lineSpacing float64
|
||||
|
||||
// canvas switches
|
||||
style string
|
||||
backgroundColor *RGB
|
||||
foregroundColor *RGB
|
||||
ansiSequenceRegexMap map[string]string
|
||||
rPromptOffset int
|
||||
cursorPadding int
|
||||
}
|
||||
|
||||
func (ir *ImageRenderer) init() {
|
||||
f := 2.0
|
||||
|
||||
ir.cleanContent()
|
||||
|
||||
fontRegular, _ := truetype.Parse(victorMonoRegular)
|
||||
fontBold, _ := truetype.Parse(victorMonoBold)
|
||||
fontItalic, _ := truetype.Parse(victorMonoItalic)
|
||||
fontFaceOptions := &truetype.Options{Size: f * 12, DPI: 144}
|
||||
|
||||
ir.defaultForegroundColor = &RGB{255, 255, 255}
|
||||
ir.defaultBackgroundColor = &RGB{21, 21, 21}
|
||||
|
||||
ir.factor = f
|
||||
|
||||
ir.columns = 80
|
||||
ir.rows = 25
|
||||
|
||||
ir.margin = f * 48
|
||||
ir.padding = f * 24
|
||||
|
||||
ir.shadowBaseColor = "#10101066"
|
||||
ir.shadowRadius = uint8(math.Min(f*16, 255))
|
||||
ir.shadowOffsetX = f * 16
|
||||
ir.shadowOffsetY = f * 16
|
||||
|
||||
ir.regular = truetype.NewFace(fontRegular, fontFaceOptions)
|
||||
ir.bold = truetype.NewFace(fontBold, fontFaceOptions)
|
||||
ir.italic = truetype.NewFace(fontItalic, fontFaceOptions)
|
||||
ir.lineSpacing = 1.2
|
||||
|
||||
ir.ansiSequenceRegexMap = map[string]string{
|
||||
invertedColor: `^(?P<STR>(\x1b\[38;2;(?P<BG>(\d+;?){3});49m){1}(\x1b\[7m))`,
|
||||
invertedColorSingle: `^(?P<STR>\x1b\[(?P<BG>\d{2,3});49m\x1b\[7m)`,
|
||||
fullColor: `^(?P<STR>(\x1b\[48;2;(?P<BG>(\d+;?){3})m)(\x1b\[38;2;(?P<FG>(\d+;?){3})m))`,
|
||||
foreground: `^(?P<STR>(\x1b\[38;2;(?P<FG>(\d+;?){3})m))`,
|
||||
reset: `^(?P<STR>\x1b\[0m)`,
|
||||
bold: `^(?P<STR>\x1b\[1m)`,
|
||||
boldReset: `^(?P<STR>\x1b\[22m)`,
|
||||
italic: `^(?P<STR>\x1b\[3m)`,
|
||||
italicReset: `^(?P<STR>\x1b\[23m)`,
|
||||
underline: `^(?P<STR>\x1b\[4m)`,
|
||||
underlineReset: `^(?P<STR>\x1b\[24m)`,
|
||||
strikethrough: `^(?P<STR>\x1b\[9m)`,
|
||||
strikethroughReset: `^(?P<STR>\x1b\[29m)`,
|
||||
color16: `^(?P<STR>\x1b\[(?P<FG>\d{2,3})m)`,
|
||||
left: `^(?P<STR>\x1b\[(\d{1,3})D)`,
|
||||
osc99: `^(?P<STR>\x1b\]9;9;(.+)\x1b\\)`,
|
||||
lineChange: `^(?P<STR>\x1b\[(\d)[FB])`,
|
||||
title: `^(?P<STR>\x1b\]0;(.+)\007)`,
|
||||
link: `^(?P<STR>\x1b]8;;file:\/\/(.+)\x1b\\(?P<URL>.+)\x1b]8;;\x1b\\)`,
|
||||
}
|
||||
}
|
||||
|
||||
func (ir *ImageRenderer) fontHeight() float64 {
|
||||
return float64(ir.regular.Metrics().Height >> 6)
|
||||
}
|
||||
|
||||
func (ir *ImageRenderer) calculateWidth() int {
|
||||
longest := 0
|
||||
for _, line := range strings.Split(ir.ansiString, "\n") {
|
||||
length := ir.formats.lenWithoutANSI(line)
|
||||
if length > longest {
|
||||
longest = length
|
||||
}
|
||||
}
|
||||
return longest
|
||||
}
|
||||
|
||||
func (ir *ImageRenderer) cleanContent() {
|
||||
rPromptAnsi := "\x1b7\x1b[1000C"
|
||||
hasRPrompt := strings.Contains(ir.ansiString, rPromptAnsi)
|
||||
// clean abundance of empty lines
|
||||
ir.ansiString = strings.Trim(ir.ansiString, "\n")
|
||||
ir.ansiString = "\n" + ir.ansiString
|
||||
// clean string before render
|
||||
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[m", "\x1b[0m")
|
||||
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[K", "")
|
||||
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[1F", "")
|
||||
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b8", "")
|
||||
// replace rprompt with adding and mark right aligned blocks with a pointer
|
||||
ir.ansiString = strings.ReplaceAll(ir.ansiString, rPromptAnsi, fmt.Sprintf("_%s", strings.Repeat(" ", ir.cursorPadding)))
|
||||
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[1000C", strings.Repeat(" ", ir.rPromptOffset))
|
||||
if !hasRPrompt {
|
||||
ir.ansiString += fmt.Sprintf("_%s", strings.Repeat(" ", ir.cursorPadding))
|
||||
}
|
||||
// add watermarks
|
||||
ir.ansiString += "\n\n\x1b[1mhttps://ohmyposh.dev\x1b[22m"
|
||||
if len(ir.author) > 0 {
|
||||
createdBy := fmt.Sprintf(" by \x1b[1m%s\x1b[22m", ir.author)
|
||||
ir.ansiString += createdBy
|
||||
}
|
||||
}
|
||||
|
||||
func (ir *ImageRenderer) measureContent() (width, height float64) {
|
||||
// get the longest line
|
||||
linewidth := ir.calculateWidth()
|
||||
// width, taken from the longest line
|
||||
tmpDrawer := &font.Drawer{Face: ir.regular}
|
||||
advance := tmpDrawer.MeasureString(strings.Repeat(" ", linewidth))
|
||||
width = float64(advance >> 6)
|
||||
// height, lines times font height and line spacing
|
||||
height = float64(len(strings.Split(ir.ansiString, "\n"))) * ir.fontHeight() * ir.lineSpacing
|
||||
return width, height
|
||||
}
|
||||
|
||||
func (ir *ImageRenderer) SavePNG(path string) error {
|
||||
var f = func(value float64) float64 { return ir.factor * value }
|
||||
|
||||
var (
|
||||
corner = f(6)
|
||||
radius = f(9)
|
||||
distance = f(25)
|
||||
)
|
||||
|
||||
contentWidth, contentHeight := ir.measureContent()
|
||||
|
||||
// Make sure the output window is big enough in case no content or very few
|
||||
// content will be rendered
|
||||
contentWidth = math.Max(contentWidth, 3*distance+3*radius)
|
||||
|
||||
marginX, marginY := ir.margin, ir.margin
|
||||
paddingX, paddingY := ir.padding, ir.padding
|
||||
|
||||
xOffset := marginX
|
||||
yOffset := marginY
|
||||
titleOffset := f(40)
|
||||
|
||||
width := contentWidth + 2*marginX + 2*paddingX
|
||||
height := contentHeight + 2*marginY + 2*paddingY + titleOffset
|
||||
|
||||
dc := gg.NewContext(int(width), int(height))
|
||||
|
||||
xOffset -= ir.shadowOffsetX / 2
|
||||
yOffset -= ir.shadowOffsetY / 2
|
||||
|
||||
bc := gg.NewContext(int(width), int(height))
|
||||
bc.DrawRoundedRectangle(xOffset+ir.shadowOffsetX, yOffset+ir.shadowOffsetY, width-2*marginX, height-2*marginY, corner)
|
||||
bc.SetHexColor(ir.shadowBaseColor)
|
||||
bc.Fill()
|
||||
|
||||
var done = make(chan struct{}, ir.shadowRadius)
|
||||
shadow := stackblur.Process(
|
||||
bc.Image(),
|
||||
uint32(width),
|
||||
uint32(height),
|
||||
uint32(ir.shadowRadius),
|
||||
done,
|
||||
)
|
||||
|
||||
<-done
|
||||
dc.DrawImage(shadow, 0, 0)
|
||||
|
||||
// Draw rounded rectangle with outline and three button to produce the
|
||||
// impression of a window with controls and a content area
|
||||
dc.DrawRoundedRectangle(xOffset, yOffset, width-2*marginX, height-2*marginY, corner)
|
||||
dc.SetHexColor("#151515")
|
||||
dc.Fill()
|
||||
|
||||
dc.DrawRoundedRectangle(xOffset, yOffset, width-2*marginX, height-2*marginY, corner)
|
||||
dc.SetHexColor("#404040")
|
||||
dc.SetLineWidth(f(1))
|
||||
dc.Stroke()
|
||||
|
||||
for i, color := range []string{red, yellow, green} {
|
||||
dc.DrawCircle(xOffset+paddingX+float64(i)*distance+f(4), yOffset+paddingY+f(4), radius)
|
||||
dc.SetHexColor(color)
|
||||
dc.Fill()
|
||||
}
|
||||
|
||||
// Apply the actual text into the prepared content area of the window
|
||||
var x, y float64 = xOffset + paddingX, yOffset + paddingY + titleOffset + ir.fontHeight()
|
||||
|
||||
for len(ir.ansiString) != 0 {
|
||||
if !ir.shouldPrint() {
|
||||
continue
|
||||
}
|
||||
runes := []rune(ir.ansiString)
|
||||
str := string(runes[0:1])
|
||||
ir.ansiString = string(runes[1:])
|
||||
switch ir.style {
|
||||
case bold:
|
||||
dc.SetFontFace(ir.bold)
|
||||
case italic:
|
||||
dc.SetFontFace(ir.italic)
|
||||
default:
|
||||
dc.SetFontFace(ir.regular)
|
||||
}
|
||||
|
||||
w, h := dc.MeasureString(str)
|
||||
if ir.backgroundColor != nil {
|
||||
dc.SetRGB255(ir.backgroundColor.r, ir.backgroundColor.g, ir.backgroundColor.b)
|
||||
dc.DrawRectangle(x, y-h, w, h+12)
|
||||
dc.Fill()
|
||||
}
|
||||
if ir.foregroundColor != nil {
|
||||
dc.SetRGB255(ir.foregroundColor.r, ir.foregroundColor.g, ir.foregroundColor.b)
|
||||
} else {
|
||||
dc.SetRGB255(ir.defaultForegroundColor.r, ir.defaultForegroundColor.g, ir.defaultForegroundColor.b)
|
||||
}
|
||||
|
||||
if str == "\n" {
|
||||
x = xOffset + paddingX
|
||||
y += h * ir.lineSpacing
|
||||
continue
|
||||
}
|
||||
|
||||
dc.DrawString(str, x, y)
|
||||
|
||||
if ir.style == underline {
|
||||
dc.DrawLine(x, y+f(4), x+w, y+f(4))
|
||||
dc.SetLineWidth(f(1))
|
||||
dc.Stroke()
|
||||
}
|
||||
|
||||
x += w
|
||||
}
|
||||
|
||||
return dc.SavePNG(path)
|
||||
}
|
||||
|
||||
func (ir *ImageRenderer) shouldPrint() bool {
|
||||
for sequence, regex := range ir.ansiSequenceRegexMap {
|
||||
match := findNamedRegexMatch(regex, ir.ansiString)
|
||||
if len(match) == 0 {
|
||||
continue
|
||||
}
|
||||
ir.ansiString = strings.TrimPrefix(ir.ansiString, match[STR])
|
||||
switch sequence {
|
||||
case invertedColor:
|
||||
ir.foregroundColor = ir.defaultBackgroundColor
|
||||
ir.backgroundColor = NewRGBColor(match[BG])
|
||||
return false
|
||||
case invertedColorSingle:
|
||||
ir.foregroundColor = ir.defaultBackgroundColor
|
||||
color, _ := strconv.Atoi(match[BG])
|
||||
color += 10
|
||||
ir.setBase16Color(fmt.Sprint(color))
|
||||
return false
|
||||
case fullColor:
|
||||
ir.foregroundColor = NewRGBColor(match[FG])
|
||||
ir.backgroundColor = NewRGBColor(match[BG])
|
||||
return false
|
||||
case foreground:
|
||||
ir.foregroundColor = NewRGBColor(match[FG])
|
||||
return false
|
||||
case reset:
|
||||
ir.foregroundColor = ir.defaultForegroundColor
|
||||
ir.backgroundColor = nil
|
||||
return false
|
||||
case bold, italic, underline:
|
||||
ir.style = sequence
|
||||
return false
|
||||
case boldReset, italicReset, underlineReset:
|
||||
ir.style = ""
|
||||
return false
|
||||
case strikethrough, strikethroughReset, left, osc99, lineChange, title:
|
||||
return false
|
||||
case color16:
|
||||
ir.setBase16Color(match[FG])
|
||||
return false
|
||||
case link:
|
||||
ir.ansiString = match[URL] + ir.ansiString
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ir *ImageRenderer) setBase16Color(colorStr string) {
|
||||
color := ir.defaultForegroundColor
|
||||
colorInt, err := strconv.Atoi(colorStr)
|
||||
if err != nil {
|
||||
ir.foregroundColor = color
|
||||
}
|
||||
switch colorInt {
|
||||
case 30, 40: // Black
|
||||
color = &RGB{1, 1, 1}
|
||||
case 31, 41: // Red
|
||||
color = &RGB{222, 56, 43}
|
||||
case 32, 42: // Green
|
||||
color = &RGB{57, 181, 74}
|
||||
case 33, 43: // Yellow
|
||||
color = &RGB{255, 199, 6}
|
||||
case 34, 44: // Blue
|
||||
color = &RGB{0, 111, 184}
|
||||
case 35, 45: // Magenta
|
||||
color = &RGB{118, 38, 113}
|
||||
case 36, 46: // Cyan
|
||||
color = &RGB{44, 181, 233}
|
||||
case 37, 47: // White
|
||||
color = &RGB{204, 204, 204}
|
||||
case 90, 100: // Bright Black (Gray)
|
||||
color = &RGB{128, 128, 128}
|
||||
case 91, 101: // Bright Red
|
||||
color = &RGB{255, 0, 0}
|
||||
case 92, 102: // Bright Green
|
||||
color = &RGB{0, 255, 0}
|
||||
case 93, 103: // Bright Yellow
|
||||
color = &RGB{255, 255, 0}
|
||||
case 94, 104: // Bright Blue
|
||||
color = &RGB{0, 0, 255}
|
||||
case 95, 105: // Bright Magenta
|
||||
color = &RGB{255, 0, 255}
|
||||
case 96, 106: // Bright Cyan
|
||||
color = &RGB{101, 194, 205}
|
||||
case 97, 107: // Bright White
|
||||
color = &RGB{255, 255, 255}
|
||||
}
|
||||
if colorInt < 40 || (colorInt >= 90 && colorInt < 100) {
|
||||
ir.foregroundColor = color
|
||||
return
|
||||
}
|
||||
ir.backgroundColor = color
|
||||
}
|
40
src/image_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func runImageTest(content string) error {
|
||||
poshImagePath := "ohmyposh.png"
|
||||
file, err := ioutil.TempFile("", poshImagePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
formats := &ansiFormats{}
|
||||
formats.init(shelly)
|
||||
image := &ImageRenderer{
|
||||
ansiString: content,
|
||||
formats: formats,
|
||||
}
|
||||
image.init()
|
||||
err = image.SavePNG(poshImagePath)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestStringImageFileWithText(t *testing.T) {
|
||||
err := runImageTest("foobar")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestStringImageFileWithANSI(t *testing.T) {
|
||||
prompt := `[38;2;0;55;218;49m[7m[m[0m[48;2;0;55;218m[38;2;255;255;255m oh-my-posh
|
||||
[0m[48;2;193;156;0m[38;2;0;55;218m[0m[48;2;193;156;0m[38;2;17;17;17m main ≡ ~4 -8 ?7 [0m[38;2;193;156;0m[0m
|
||||
[37m [0m[0m`
|
||||
err := runImageTest(prompt)
|
||||
assert.NoError(t, err)
|
||||
}
|
|
@ -115,3 +115,28 @@ function global:Export-PoshTheme {
|
|||
$configString = @(&$omp --config="$config" --config-format="$Format" --print-config 2>&1)
|
||||
[IO.File]::WriteAllLines($FilePath, $configString)
|
||||
}
|
||||
|
||||
function global:Export-PoshImage {
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[int]
|
||||
$RPromptOffset = 40,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[int]
|
||||
$CursorPadding = 30,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]
|
||||
$Author
|
||||
)
|
||||
|
||||
if ($Author) {
|
||||
$Author = "--author=$Author"
|
||||
}
|
||||
|
||||
$omp = "::OMP::"
|
||||
$config = $global:PoshSettings.Theme
|
||||
$cleanPWD = $PWD.ProviderPath.TrimEnd("\")
|
||||
$cleanPSWD = $PWD.ToString().TrimEnd("\")
|
||||
$standardOut = @(&$omp --config="$config" --pwd="$cleanPWD" --pswd="$cleanPSWD" --export-png --rprompt-offset="$RPromptOffset" --cursor-padding="$CursorPadding" $Author 2>&1)
|
||||
$standardOut -join "`n"
|
||||
}
|
||||
|
|
42
src/main.go
|
@ -33,6 +33,7 @@ const (
|
|||
pwsh = "pwsh"
|
||||
fish = "fish"
|
||||
powershell5 = "powershell"
|
||||
shelly = "shell"
|
||||
)
|
||||
|
||||
type args struct {
|
||||
|
@ -51,6 +52,10 @@ type args struct {
|
|||
Eval *bool
|
||||
Init *bool
|
||||
PrintInit *bool
|
||||
ExportPNG *bool
|
||||
Author *string
|
||||
CursorPadding *int
|
||||
RPromptOffset *int
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -115,6 +120,22 @@ func main() {
|
|||
"print-init",
|
||||
false,
|
||||
"Print the shell initialization script"),
|
||||
ExportPNG: flag.Bool(
|
||||
"export-png",
|
||||
false,
|
||||
"Create an image based on the current configuration"),
|
||||
Author: flag.String(
|
||||
"author",
|
||||
"",
|
||||
"Add the author to the exported image using --export-img"),
|
||||
CursorPadding: flag.Int(
|
||||
"cursor-padding",
|
||||
30,
|
||||
"Pad the cursor with x when using --export-img"),
|
||||
RPromptOffset: flag.Int(
|
||||
"rprompt-offset",
|
||||
40,
|
||||
"Offset the right prompt with x when using --export-img"),
|
||||
}
|
||||
flag.Parse()
|
||||
env := &environment{}
|
||||
|
@ -170,10 +191,27 @@ func main() {
|
|||
}
|
||||
|
||||
if *args.Debug {
|
||||
engine.debug()
|
||||
fmt.Print(engine.debug())
|
||||
return
|
||||
}
|
||||
engine.render()
|
||||
prompt := engine.render()
|
||||
if !*args.ExportPNG {
|
||||
fmt.Print(prompt)
|
||||
return
|
||||
}
|
||||
imageCreator := &ImageRenderer{
|
||||
ansiString: prompt,
|
||||
author: *args.Author,
|
||||
cursorPadding: *args.CursorPadding,
|
||||
rPromptOffset: *args.RPromptOffset,
|
||||
formats: formats,
|
||||
}
|
||||
imageCreator.init()
|
||||
match := findNamedRegexMatch(`.*(\/|\\)(?P<STR>.+).omp.(json|yaml|toml)`, *args.Config)
|
||||
err := imageCreator.SavePNG(fmt.Sprintf("%s.png", match[STR]))
|
||||
if err != nil {
|
||||
fmt.Print(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func initShell(shell, configFile string) string {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"user_info_separator": "",
|
||||
"display_host": false,
|
||||
"prefix": "",
|
||||
"postfix": ""
|
||||
"postfix": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -42,8 +42,8 @@
|
|||
"properties": {
|
||||
"branch_icon": "",
|
||||
"display_status": false,
|
||||
"prefix": "git(",
|
||||
"postfix": ")"
|
||||
"prefix": " git(",
|
||||
"postfix": ") "
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -55,8 +55,8 @@
|
|||
"background": "lightMagenta",
|
||||
"properties": {
|
||||
"time_format": "15:04",
|
||||
"prefix": "",
|
||||
"postfix": ""
|
||||
"prefix": " ",
|
||||
"postfix": " "
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json",
|
||||
"final_space":true,
|
||||
"osc99":true,
|
||||
"console_title":true,
|
||||
"console_title_style":"template",
|
||||
"console_title_template":"{{.Folder}}{{if .Root}} :: root{{end}} :: {{.Shell}}",
|
||||
"blocks":[
|
||||
"final_space": true,
|
||||
"osc99": true,
|
||||
"console_title": true,
|
||||
"console_title_style": "template",
|
||||
"console_title_template": "{{.Folder}}{{if .Root}} :: root{{end}} :: {{.Shell}}",
|
||||
"blocks": [
|
||||
{
|
||||
"type":"prompt",
|
||||
"alignment":"left",
|
||||
"horizontal_offset":0,
|
||||
"vertical_offset":1,
|
||||
"segments":[
|
||||
"type": "prompt",
|
||||
"alignment": "left",
|
||||
"segments": [
|
||||
{
|
||||
"type": "root",
|
||||
"style": "plain",
|
||||
|
@ -21,41 +19,32 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"type":"path",
|
||||
"style":"diamond",
|
||||
"powerline_symbol":"",
|
||||
"invert_powerline":false,
|
||||
"foreground":"black",
|
||||
"foreground_templates":null,
|
||||
"background":"lightBlue",
|
||||
"background_templates":null,
|
||||
"leading_diamond":"",
|
||||
"trailing_diamond":"",
|
||||
"properties":{
|
||||
"style":"mixed"
|
||||
"type": "path",
|
||||
"style": "diamond",
|
||||
"foreground": "black",
|
||||
"background": "lightBlue",
|
||||
"leading_diamond": "",
|
||||
"trailing_diamond": "",
|
||||
"properties": {
|
||||
"style": "mixed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type":"git",
|
||||
"style":"powerline",
|
||||
"powerline_symbol":"",
|
||||
"invert_powerline":false,
|
||||
"foreground":"black",
|
||||
"foreground_templates":null,
|
||||
"background":"green",
|
||||
"background_templates":null,
|
||||
"leading_diamond":"",
|
||||
"trailing_diamond":"",
|
||||
"properties":{
|
||||
"type": "git",
|
||||
"style": "powerline",
|
||||
"powerline_symbol": "",
|
||||
"foreground": "black",
|
||||
"background": "green",
|
||||
"properties": {
|
||||
"display_status_detail": true,
|
||||
"branch_icon":" ",
|
||||
"branch_identical_icon":"≡",
|
||||
"branch_ahead_icon":"↑",
|
||||
"branch_behind_icon":"↓",
|
||||
"branch_gone":"≢",
|
||||
"local_working_icon":"",
|
||||
"local_staged_icon":"",
|
||||
"stash_count_icon":"",
|
||||
"branch_icon": " ",
|
||||
"branch_identical_icon": "≡",
|
||||
"branch_ahead_icon": "↑",
|
||||
"branch_behind_icon": "↓",
|
||||
"branch_gone": "≢",
|
||||
"local_working_icon": "",
|
||||
"local_staged_icon": "",
|
||||
"stash_count_icon": "",
|
||||
"commit_icon": "▷ ",
|
||||
"tag_icon": "▶ ",
|
||||
"rebase_icon": "Ɫ ",
|
||||
|
|
|
@ -32,6 +32,5 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"final_space": true
|
||||
]
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"foreground": "#ffffff",
|
||||
"properties": {
|
||||
"prefix": "",
|
||||
"postfix": "",
|
||||
"text": "<#C591E8>\u276F</><#69FF94>\u276F</>"
|
||||
}
|
||||
},
|
||||
|
@ -52,6 +53,5 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"final_space": true
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
{
|
||||
"type": "os",
|
||||
"style": "plain",
|
||||
"foreground": "#3A86FF"
|
||||
"foreground": "#3A86FF",
|
||||
"properties": {
|
||||
"prefix": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "session",
|
||||
|
@ -101,6 +104,5 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"final_space": true
|
||||
]
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"style": "plain",
|
||||
"foreground": "#56B6C2",
|
||||
"properties": {
|
||||
"prefix": "",
|
||||
"style": "folder"
|
||||
}
|
||||
},
|
||||
|
@ -77,6 +78,5 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"final_space": true
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
"console_title_template": "{{if .Root}}(Admin){{end}} {{.Path}}",
|
||||
"blocks": [
|
||||
{
|
||||
"type":"newline"
|
||||
"type": "newline"
|
||||
},
|
||||
{
|
||||
"type":"prompt",
|
||||
"type": "prompt",
|
||||
"alignment": "left",
|
||||
"segments": [
|
||||
{
|
||||
"type":"root",
|
||||
"style":"plain",
|
||||
"type": "root",
|
||||
"style": "plain",
|
||||
"foreground": "#BF616A",
|
||||
"properties": {
|
||||
"prefix": "",
|
||||
|
@ -21,8 +21,8 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"type":"path",
|
||||
"style":"plain",
|
||||
"type": "path",
|
||||
"style": "plain",
|
||||
"foreground": "#81A1C1",
|
||||
"properties": {
|
||||
"prefix": "",
|
||||
|
@ -32,14 +32,15 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type":"prompt",
|
||||
"type": "prompt",
|
||||
"alignment": "left",
|
||||
"segments": [
|
||||
{
|
||||
"type":"git",
|
||||
"style":"plain",
|
||||
"type": "git",
|
||||
"style": "plain",
|
||||
"foreground": "#6C6C6C",
|
||||
"properties": {
|
||||
"prefix": "",
|
||||
"display_stash_count": true,
|
||||
"display_status": true,
|
||||
"display_status_detail": true,
|
||||
|
@ -54,12 +55,12 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type":"prompt",
|
||||
"type": "prompt",
|
||||
"alignment": "left",
|
||||
"segments": [
|
||||
{
|
||||
"type":"executiontime",
|
||||
"style":"plain",
|
||||
"type": "executiontime",
|
||||
"style": "plain",
|
||||
"foreground": "#A3BE8C",
|
||||
"properties": {
|
||||
"style": "austin"
|
||||
|
@ -68,15 +69,15 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type":"newline"
|
||||
"type": "newline"
|
||||
},
|
||||
{
|
||||
"alignment": "left",
|
||||
"type":"prompt",
|
||||
"type": "prompt",
|
||||
"segments": [
|
||||
{
|
||||
"type":"exit",
|
||||
"style":"plain",
|
||||
"type": "exit",
|
||||
"style": "plain",
|
||||
"foreground": "#B48EAD",
|
||||
"properties": {
|
||||
"prefix": "\u276f",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"leading_diamond": "\uE0B6",
|
||||
"trailing_diamond": "",
|
||||
"properties": {
|
||||
"prefix": "",
|
||||
"display_host":false
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json",
|
||||
"blocks": [
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"wsl": "\ue712",
|
||||
"wsl_separator": " on ",
|
||||
"windows": "\ue70f",
|
||||
"postfix": "<#7a7a7a> \ue0b1</>"
|
||||
"postfix": "<#7a7a7a> \uE0b1</>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -177,7 +177,9 @@
|
|||
"style": "plain",
|
||||
"foreground": "#7a7a7a",
|
||||
"properties": {
|
||||
"text": "~#@\u276F"
|
||||
"text": "~#@\u276F",
|
||||
"prefix": "",
|
||||
"postfix": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"style": "plain",
|
||||
"foreground": "#0973C0",
|
||||
"properties": {
|
||||
"style": "full"
|
||||
"style": "full",
|
||||
"prefix": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|