Skip to content
Open
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
20 changes: 20 additions & 0 deletions examples/code_blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Currently supported languages:
* `swift`
* `dart`
* `v`
* `nu`
<!-- * `secret` -->

---
Expand Down Expand Up @@ -192,3 +193,22 @@ object Main extends App {
println("Hello")
}
```

---

## Nushell

```nu
# Basic string output
echo "Hello from Nushell!"
```

```nu
# Data manipulation with pipelines
[1, 2, 3, 4, 5] | where $it > 2 | math sum
```

```nu
# Working with records
{name: "Alice", age: 30, city: "New York"} | get name
```
20 changes: 20 additions & 0 deletions internal/code/execute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ func main() {
ExitCode: 127,
},
},
{
block: code.Block{
Code: `echo "Hello, nushell!"`,
Language: "nu",
},
expected: code.Result{
Out: "Hello, nushell!\n",
ExitCode: 0,
},
},
{
block: code.Block{
Code: `{message: "Hello, World!"} | get message`,
Language: "nu",
},
expected: code.Result{
Out: "Hello, World!\n",
ExitCode: 0,
},
},
{
block: code.Block{
Code: `Invalid Code`,
Expand Down
5 changes: 5 additions & 0 deletions internal/code/languages.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
V = "v"
Scala = "scala"
Haskell = "haskell"
Nushell = "nu"
)

// Languages is a map of supported languages with their extensions and commands
Expand Down Expand Up @@ -129,4 +130,8 @@ var Languages = map[string]Language{
Extension: "hs",
Commands: cmds{{"runghc", "<file>"}},
},
Nushell: {
Extension: "nu",
Commands: cmds{{"nu", "--no-config-file", "<file>"}},
},
}
2 changes: 2 additions & 0 deletions internal/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/charmbracelet/glamour"
"github.com/maaslalani/slides/internal/code"
"github.com/maaslalani/slides/internal/meta"
"github.com/maaslalani/slides/internal/syntax"
"github.com/maaslalani/slides/styles"
)

Expand Down Expand Up @@ -207,6 +208,7 @@ func (m Model) View() string {
r, _ := glamour.NewTermRenderer(m.Theme, glamour.WithWordWrap(m.viewport.Width))
slide := m.Slides[m.Page]
slide = code.HideComments(slide)
slide = syntax.PreprocessNushellMarkdown(slide)
slide, err := r.Render(slide)
slide = strings.ReplaceAll(slide, "\t", tabSpaces)
slide += m.VirtualText
Expand Down
140 changes: 140 additions & 0 deletions internal/syntax/nushell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package syntax

import (
"regexp"
"strings"
)

// NushellHighlighter provides basic syntax highlighting for Nushell code
// by converting it to a format that works better with existing shell lexers
type NushellHighlighter struct{}

// NewNushellHighlighter creates a new nushell highlighter
func NewNushellHighlighter() *NushellHighlighter {
return &NushellHighlighter{}
}

// PreprocessForShellHighlighting converts nushell syntax to be more compatible
// with shell syntax highlighters like bash
func (n *NushellHighlighter) PreprocessForShellHighlighting(code string) string {
// Apply various transformations to make nushell code more shell-like
// for better syntax highlighting

processed := code

// Convert nushell pipes to regular pipes (already compatible)
// No change needed for basic pipes

// Convert nushell comments (# already compatible with shell)
// No change needed

// Convert nushell variables ($var -> $var, already compatible)
// No change needed for basic variables

// Convert nushell record syntax {key: value} to something more shell-like
// This is a basic approximation for highlighting purposes
recordRegex := regexp.MustCompile(`\{([^}]+)\}`)
processed = recordRegex.ReplaceAllStringFunc(processed, func(match string) string {
// Keep the braces but make it look more like shell arrays
return strings.ReplaceAll(match, ":", "=")
})

// Convert nushell list syntax [item1, item2] to shell-like arrays
listRegex := regexp.MustCompile(`\[([^\]]+)\]`)
processed = listRegex.ReplaceAllString(processed, "($1)")

// Convert nushell where clauses to look more like shell conditionals
whereRegex := regexp.MustCompile(`\|\s*where\s+`)
processed = whereRegex.ReplaceAllString(processed, "| grep ")

// Convert nushell $it references to shell-like variables
processed = strings.ReplaceAll(processed, "$it", "$_")

return processed
}

// GetFallbackLanguage returns the best fallback language for nushell highlighting
func (n *NushellHighlighter) GetFallbackLanguage() string {
return "bash"
}

// ShouldUsePreprocessing determines if the code should be preprocessed
// based on common nushell patterns
func (n *NushellHighlighter) ShouldUsePreprocessing(code string) bool {
nushellPatterns := []string{
"{", // Records
"where $it", // Nushell where clauses
"| get ", // Nushell get command
"| select ", // Nushell select command
"| each ", // Nushell each command
"| from ", // Nushell from command
"| to ", // Nushell to command
"| str ", // Nushell str command
"| math ", // Nushell math command
"| length", // Nushell length command
"| first", // Nushell first command
"| last", // Nushell last command
"| sort-by", // Nushell sort-by command
"| group-by", // Nushell group-by command
"| where ", // Nushell where command
"| into ", // Nushell into command
"| append", // Nushell append command
"| prepend", // Nushell prepend command
"open ", // Nushell open command
"save ", // Nushell save command
"sys", // Nushell sys command
"date now", // Nushell date command
"http get", // Nushell http command
"format date", // Nushell format command
}

for _, pattern := range nushellPatterns {
if strings.Contains(code, pattern) {
return true
}
}

return false
}

// LanguageMapping defines fallback mappings for nushell identifiers
var LanguageMapping = map[string]string{
"nu": "bash",
}

// GetMappedLanguage returns the mapped language for highlighting,
// or the original if no mapping exists
func GetMappedLanguage(lang string) string {
if mapped, exists := LanguageMapping[lang]; exists {
return mapped
}
return lang
}

// ProcessNushellCode applies preprocessing if needed and returns the language to use
func ProcessNushellCode(code, language string) (processedCode, highlightLanguage string) {
if language == "nu" {
highlighter := NewNushellHighlighter()

if highlighter.ShouldUsePreprocessing(code) {
return highlighter.PreprocessForShellHighlighting(code), highlighter.GetFallbackLanguage()
}

return code, highlighter.GetFallbackLanguage()
}

return code, language
}

// PreprocessNushellMarkdown preprocesses markdown to replace nushell language identifiers
// with bash for better syntax highlighting in code blocks
func PreprocessNushellMarkdown(markdown string) string {
// Regular expression to match code blocks with nushell language identifiers
// Matches both ```nu and ```nushell
nushellCodeBlockRegex := regexp.MustCompile(`(?m)^` + "```(?:nu)" + `$`)

// Replace nushell identifiers with bash for better syntax highlighting
processed := nushellCodeBlockRegex.ReplaceAllString(markdown, "```bash")

return processed
}
Loading