Skip to content

Commit

Permalink
feat: newline as part of block
Browse files Browse the repository at this point in the history
this deprecates the "newline" block and favours using the newline
property on the Block component. For backwards compatibility we'll
keep recognizing the newline block for the time being.

resolves #607
  • Loading branch information
JanDeDobbeleer committed Apr 18, 2021
1 parent 1f25cd4 commit a8efe6f
Show file tree
Hide file tree
Showing 31 changed files with 362 additions and 338 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Expand Up @@ -7,7 +7,7 @@
"request": "launch",
"mode": "debug",
"program": "${workspaceRoot}/src",
"args": ["--config=/Users/jan/.jandedobbeleer.omp.json"]
"args": ["--config=${workspaceRoot}/themes/jandedobbeleer.omp.json"]
},
{
"name": "Launch tests",
Expand Down
15 changes: 8 additions & 7 deletions docs/docs/configuration.md
Expand Up @@ -105,7 +105,8 @@ the current working directory is `/usr/home/omp` and the shell is `zsh`.

Let's take a closer look at what defines a block.

- type: `prompt` | `rprompt` | `newline`
- type: `prompt` | `rprompt`
- newline: `boolean`
- alignment: `left` | `right`
- vertical_offset: `int`
- horizontal_offset: `int`
Expand All @@ -117,9 +118,11 @@ Tells the engine what to do with the block. There are three options:

- `prompt` renders one or more segments
- `rprompt` renders one or more segments aligned to the right of the cursor. Only one `rprompt` block is permitted.
Supported on [ZSH][rprompt] and Powershell.
- `newline` inserts a new line to start the next block on a new line. `newline` blocks require no additional
configuration other than the `type`.
Supported on [ZSH][rprompt], Bash and Powershell.

### Newline

Start the block on a new line. Defaults to `false`.

### Alignment

Expand Down Expand Up @@ -402,12 +405,10 @@ has to be enabled at the segment level. Hyperlink generation is disabled by defa
}
]
},
{
"type": "newline"
},
{
"type": "prompt",
"alignment": "left",
"newline": true,
"segments": [
{
"type": "session",
Expand Down
192 changes: 192 additions & 0 deletions src/block.go
@@ -0,0 +1,192 @@
package main

import (
"fmt"
"sync"
"time"
)

// BlockType type of block
type BlockType string

// BlockAlignment aligment of a Block
type BlockAlignment string

const (
// Prompt writes one or more Segments
Prompt BlockType = "prompt"
// LineBreak creates a line break in the prompt
LineBreak BlockType = "newline"
// RPrompt a right aligned prompt in ZSH and Powershell
RPrompt BlockType = "rprompt"
// Left aligns left
Left BlockAlignment = "left"
// Right aligns right
Right BlockAlignment = "right"
)

// Block defines a part of the prompt with optional segments
type Block struct {
Type BlockType `config:"type"`
Alignment BlockAlignment `config:"alignment"`
HorizontalOffset int `config:"horizontal_offset"`
VerticalOffset int `config:"vertical_offset"`
Segments []*Segment `config:"segments"`
Newline bool `config:"newline"`

env environmentInfo
color *AnsiColor
activeSegment *Segment
previousActiveSegment *Segment
}

func (b *Block) init(env environmentInfo, color *AnsiColor) {
b.env = env
b.color = color
}

func (b *Block) enabled() bool {
if b.Type == LineBreak {
return true
}
for _, segment := range b.Segments {
if segment.active {
return true
}
}
return false
}

func (b *Block) setStringValues() {
wg := sync.WaitGroup{}
wg.Add(len(b.Segments))
defer wg.Wait()
cwd := b.env.getcwd()
for _, segment := range b.Segments {
go func(s *Segment) {
defer wg.Done()
s.setStringValue(b.env, cwd)
}(segment)
}
}

func (b *Block) renderSegments() string {
for _, segment := range b.Segments {
if !segment.active {
continue
}
b.activeSegment = segment
b.endPowerline()
b.renderSegmentText(segment.stringValue)
}
if b.previousActiveSegment != nil && b.previousActiveSegment.Style == Powerline {
b.writePowerLineSeparator(Transparent, b.previousActiveSegment.background(), true)
}
return b.color.string()
}

func (b *Block) endPowerline() {
if b.activeSegment != nil &&
b.activeSegment.Style != Powerline &&
b.previousActiveSegment != nil &&
b.previousActiveSegment.Style == Powerline {
b.writePowerLineSeparator(b.getPowerlineColor(false), b.previousActiveSegment.background(), true)
}
}

func (b *Block) writePowerLineSeparator(background, foreground string, end bool) {
symbol := b.activeSegment.PowerlineSymbol
if end {
symbol = b.previousActiveSegment.PowerlineSymbol
}
if b.activeSegment.InvertPowerline {
b.color.write(foreground, background, symbol)
return
}
b.color.write(background, foreground, symbol)
}

func (b *Block) getPowerlineColor(foreground bool) string {
if b.previousActiveSegment == nil {
return Transparent
}
if !foreground && b.activeSegment.Style != Powerline {
return Transparent
}
if foreground && b.previousActiveSegment.Style != Powerline {
return Transparent
}
return b.previousActiveSegment.background()
}

func (b *Block) renderSegmentText(text string) {
switch b.activeSegment.Style {
case Plain:
b.renderPlainSegment(text)
case Diamond:
b.renderDiamondSegment(text)
case Powerline:
b.renderPowerLineSegment(text)
}
b.previousActiveSegment = b.activeSegment
}

func (b *Block) renderPowerLineSegment(text string) {
b.writePowerLineSeparator(b.activeSegment.background(), b.getPowerlineColor(true), false)
b.renderText(text)
}

func (b *Block) renderPlainSegment(text string) {
b.renderText(text)
}

func (b *Block) renderDiamondSegment(text string) {
b.color.write(Transparent, b.activeSegment.background(), b.activeSegment.LeadingDiamond)
b.renderText(text)
b.color.write(Transparent, b.activeSegment.background(), b.activeSegment.TrailingDiamond)
}

func (b *Block) renderText(text string) {
text = b.color.formats.generateHyperlink(text)
defaultValue := " "
prefix := b.activeSegment.getValue(Prefix, defaultValue)
postfix := b.activeSegment.getValue(Postfix, defaultValue)
b.color.write(b.activeSegment.background(), b.activeSegment.foreground(), fmt.Sprintf("%s%s%s", prefix, text, postfix))
}

func (b *Block) debug() (int, []*SegmentTiming) {
var segmentTimings []*SegmentTiming
largestSegmentNameLength := 0
for _, segment := range b.Segments {
err := segment.mapSegmentWithWriter(b.env)
if err != nil || !segment.shouldIncludeFolder(b.env.getcwd()) {
continue
}
var segmentTiming SegmentTiming
segmentTiming.name = string(segment.Type)
segmentTiming.nameLength = len(segmentTiming.name)
if segmentTiming.nameLength > largestSegmentNameLength {
largestSegmentNameLength = segmentTiming.nameLength
}
// enabled() timing
start := time.Now()
segmentTiming.enabled = segment.enabled()
segmentTiming.enabledDuration = time.Since(start)
// string() timing
if segmentTiming.enabled {
start = time.Now()
segmentTiming.stringValue = segment.string()
segmentTiming.stringDuration = time.Since(start)
b.previousActiveSegment = nil
b.activeSegment = segment
b.renderSegmentText(segmentTiming.stringValue)
if b.activeSegment.Style == Powerline {
b.writePowerLineSeparator(Transparent, b.activeSegment.background(), true)
}
segmentTiming.stringValue = b.color.string()
b.color.builder.Reset()
}
segmentTimings = append(segmentTimings, &segmentTiming)
}
return largestSegmentNameLength, segmentTimings
}
29 changes: 29 additions & 0 deletions src/block_test.go
@@ -0,0 +1,29 @@
package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestBlockEnabled(t *testing.T) {
cases := []struct {
Case string
Expected bool
Segments []*Segment
Type BlockType
}{
{Case: "line break block", Expected: true, Type: LineBreak},
{Case: "prompt enabled", Expected: true, Type: Prompt, Segments: []*Segment{{active: true}}},
{Case: "prompt disabled", Expected: false, Type: Prompt, Segments: []*Segment{{active: false}}},
{Case: "prompt enabled multiple", Expected: true, Type: Prompt, Segments: []*Segment{{active: false}, {active: true}}},
{Case: "rprompt enabled multiple", Expected: true, Type: RPrompt, Segments: []*Segment{{active: false}, {active: true}}},
}
for _, tc := range cases {
block := &Block{
Type: tc.Type,
Segments: tc.Segments,
}
assert.Equal(t, tc.Expected, block.enabled(), tc.Case)
}
}
25 changes: 0 additions & 25 deletions src/config.go
Expand Up @@ -28,36 +28,11 @@ type Config struct {
Blocks []*Block `config:"blocks"`
}

// BlockType type of block
type BlockType string

// BlockAlignment aligment of a Block
type BlockAlignment string

const (
// Prompt writes one or more Segments
Prompt BlockType = "prompt"
// LineBreak creates a line break in the prompt
LineBreak BlockType = "newline"
// RPrompt a right aligned prompt in ZSH and Powershell
RPrompt BlockType = "rprompt"
// Left aligns left
Left BlockAlignment = "left"
// Right aligns right
Right BlockAlignment = "right"
// EnableHyperlink enable hyperlink
EnableHyperlink Property = "enable_hyperlink"
)

// Block defines a part of the prompt with optional segments
type Block struct {
Type BlockType `config:"type"`
Alignment BlockAlignment `config:"alignment"`
HorizontalOffset int `config:"horizontal_offset"`
VerticalOffset int `config:"vertical_offset"`
Segments []*Segment `config:"segments"`
}

// GetConfig returns the default configuration including possible user overrides
func GetConfig(env environmentInfo) *Config {
cfg, err := loadConfig(env)
Expand Down

0 comments on commit a8efe6f

Please sign in to comment.