最近比较喜欢去GitHub上猎奇,这不又看到一个有意思的工具,它可以让你在终端中,演示你的幻灯片,而且幻灯片只需要使用Markdown标记语言即可书写,这对我们来说太方便了。

想象一下,你不再需要繁琐的PowerPoint,只需要一个文本编辑工具,使用Markdown标记语言即可写一个幻灯片,然后在终端里演示,这是非常酷的一件事情,现在让我们开始行动吧。

安装

首先,你需要安装 https://github.com/maaslalani/slides 这个工具,它完全使用Go语言编写,安装也比较简单。

如果你是Go语言开发者,那么你只需要使用如下命令即可安装它:

1
go install github.com/maaslalani/slides

如果你是MacOS用户,使用brew即可安装它。

1
brew install slides

当然,你也可以从这 https://github.com/maaslalani/slides/releases 下载,Mac、Linux的都有,甚至你可以从源代码安装,都随你,都很简单。

查看帮助

安装成功后,打开你的终端,输入slides -h就可以看到一些帮助提示了。

1
2
3
4
5
6
7
8
9
➜ slides -h
Slides is a terminal based presentation tool

Usage:
  slides <file.md> [flags]

Flags:
  -h, --help   help for slides

从帮助中,你可以看到,它的确非常简单,只需要一个md文档即可使用。

快速入门

现在,我们使用一个官方的例子,看下演示的效果。

首先你需要下载这个md文档,它的地址是 https://github.com/maaslalani/slides/blob/main/examples/slides.md

比如我把它下载到我的桌面上,然后我运行如下命令,就可以看到幻灯片的效果了。

1
➜  Desktop slides slides.md

演示效果

如上图所示,这就是一个被slides渲染出来的幻灯片的效果,你可以从右下角看到,它一共有7页,当前是第1页。

左下角显示的是我的计算机的用户名称和时间,上面中间这部分,就是当前页的内容。

这时候你可以按上下键翻页,也可以一直敲回车键下一页,进行演示,效果非常棒。

演示效果

从网络读取MD文件

slides不仅可以从本地读取md文件,也可以读取网上的md文件,比如刚刚这个md文件,我们可以使用curl读取,然后作为输入流给slides即可。

1
curl https://github.com/maaslalani/slides/blob/main/examples/slides.md | slides

以上这个命令的效果,和我们上面演示的从桌面读取的,是一样的。

这个总结就是说,slides可以从一个输入流中读取md内容,解析成幻灯片。

分页实现原理

md文件的内容就是我们常用的、普通的markdown标记语言,那么slides是如何解析成一页页内容的呢?

这个是slides中的约定,它使用—来分页,也就是markdown里面的分割线。遇到一个—,slides就认为要分页了。我们来看下它的分页代码实现:

1
2
3
4
5
6
7
const (
  delimiter    = "\n---\n"
  altDelimiter = "\n~~~\n"
)

content = strings.ReplaceAll(content, altDelimiter, delimiter)
slides := strings.Split(content, delimiter)

其实就是基于分隔符,使用strings.Split分成一个切片,这个切片就是需要演示的一页页内容。

从源代码中,我们也可以看到,slides其实也支持~~~这个分隔符分页。

如何显示在终端

现在已经分成了一页页内容,那么每一页的md内容,是如何显示到终端里的呢?

相信你自己也能想出来,思路就是要解析MD内容,然后显示到终端中。要自己做这个功能,会比较繁琐,还要考虑样式美观等。

索性有开源,有现成的库可以帮我们做这个事情,它就是 https://github.com/charmbracelet/glamour ,这个是一个可以把md内容渲染到终端的库,并且还自带样式。

Stylesheet-based markdown rendering for your CLI apps.

我们看下,在slides中是如何使用它的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func (m Model) View() string {
  r, _ := glamour.NewTermRenderer(m.Theme, glamour.WithWordWrap(0))
  slide, err := r.Render(m.Slides[m.Page])
  if err != nil {
    slide = fmt.Sprintf("Error: Could not render markdown! (%v)", err)
  }
  slide = styles.Slide.Render(slide)

  left := styles.Author.Render(m.Author) + styles.Date.Render(m.Date)
  right := styles.Page.Render(fmt.Sprintf("Slide %d / %d", m.Page+1, len(m.Slides)))
  status := styles.Status.Render(styles.JoinHorizontal(left, right, m.viewport.Width))
  return styles.JoinVertical(slide, status, m.viewport.Height)
}

从以上代码可以看到,new一个Render,然后渲染就可以了。

同时这里也可以看到,渲染出的幻灯片,要和作者、时间以及幻灯片状态,合在一起,组成一个终端的界面,也就是这一页幻灯片。

这里可以看到很多styles.Slide,styles.Author等,这些其实就是用于控制终端布局的,它使用的是lipgloss这个库控制的。

Style definitions for nice terminal layouts. Built with TUIs in mind.

这个库的使用这里暂不讲,有兴趣的朋友可以研究下,这是个很不错的东西,可以让你写出来更好看的CLI Apps。

bubbletea框架

以上讲到的代码,都是一个Model接口的实现,通过它我们才能使用bubbletea这个终端UI框架,把幻灯片的内容、作者、时间等信息,显示在终端中,也就是我上面的截图看到的这些。

1
2
3
4
5
6
7
8
9
    p := tea.NewProgram(model.Model{
      Slides: slides,
      Page:   0,
      Author: user.Name,
      Date:   time.Now().Format("2006-01-02"),
      Theme:  styles.SelectTheme(m.Theme),
    }, tea.WithAltScreen())

    err = p.Start()

以上是部分代码,用于把幻灯片展示到终端。完整的Model接口实现代码如下所示。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
type Model struct {
  Slides   []string
  Page     int
  Author   string
  Date     string
  Theme    glamour.TermRendererOption
  viewport viewport.Model
}

func (m Model) Init() tea.Cmd {
  return nil
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  switch msg := msg.(type) {
  case tea.WindowSizeMsg:
    m.viewport.Width = msg.Width
    m.viewport.Height = msg.Height
    return m, nil

  case tea.KeyMsg:
    switch msg.String() {
    case "ctrl+c", "q":
      return m, tea.Quit
    case " ", "down", "k", "right", "l", "enter", "n":
      if m.Page < len(m.Slides)-1 {
        m.Page++
      }
    case "up", "j", "left", "h", "p":
      if m.Page > 0 {
        m.Page--
      }
    }
  }
  return m, nil
}

func (m Model) View() string {
  r, _ := glamour.NewTermRenderer(m.Theme, glamour.WithWordWrap(0))
  slide, err := r.Render(m.Slides[m.Page])
  if err != nil {
    slide = fmt.Sprintf("Error: Could not render markdown! (%v)", err)
  }
  slide = styles.Slide.Render(slide)

  left := styles.Author.Render(m.Author) + styles.Date.Render(m.Date)
  right := styles.Page.Render(fmt.Sprintf("Slide %d / %d", m.Page+1, len(m.Slides)))
  status := styles.Status.Render(styles.JoinHorizontal(left, right, m.viewport.Width))
  return styles.JoinVertical(slide, status, m.viewport.Height)
}

小结

关于bubbletea这个精美的终端UI框架,我这里不做过多介绍,你可以在GitHub上搜索看下。

经常关注GitHub是一件非常有意思的事情,他可以让你知道哪些Repo正在流行,是否可以学习它,甚至引入到团队、公司,提升整体的实力和效率。

同时,通过研究这些Repo,你可以看到它的实现,引用了哪些第三方的库,然后通过这些Repo,又可以学到更多,更有趣的第三方库的使用,这是一件非常有意思的事情,驱动着你对Go语言越来越了解。

本文为原创文章,转载注明出处,欢迎扫码关注公众号flysnow_org或者网站asf http://www.flysnow.org/ ,第一时间看后续精彩文章。觉得好的话,请猛击文章右下角「好看」,感谢支持。

扫码关注