5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RustでTUIファイルマネージャーを一緒に作る人を集めるためにコードの説明をする

Last updated at Posted at 2022-06-12

この記事に憧れてTUIのファイルマネージャーを作っている最中

私は個人で研究用のコードを書くぐらいでほぼ個人でしか開発をしたことがない

加えて,研究用のコードはデータの前処理やデータの加工しかなく,いわゆるアプリに近いものを全く作ったことが無かった

Github上にある無数のOSSに飛び込むのはなんだかんだ難しい

Good First Isuueを見つけても普通に難しい

そこで他の人と一緒にアプリ開発の練習をしてみたいと思った

プログラミング初心者の方(私も)や学生などで複数人で開発したことがなくRust言語に興味があるならこのプロジェクトを見てもらいたい

私も初心者なので上手いコードが書けるわけでもなく,良いアドバイスなどができるわけではない

しかし,GitHubで一緒に開発の練習としてやってみたい人は,それこそIssueやPRの練習をしてみるのをお勧めしたい

現状はまだないが,GitHub以外にもGitterやSlack,Notion,Matrix,Discordなど何かしらコミュニケーションツールは導入しようと思っている

simple-tui-file-managerのリポジトリ

プロトタイプ

とりあえず現状(6/15時点)での状況は以下の通り

main_window (2).png

カラーテーマなどいくつか改善したい点があるが,とりあえずはこんな感じのが既にできている

コマンド一覧

  • 移動操作

    • j(またはup),k(またはdown)の上下移動
    • tabでディレクトリタブの移動(バッファの移動みたいな感じ)
    • qで終了
    • h(またはLeft),l(またはRight)で階層移動
  • ユーザーコンフィグ(ronファイル)

    • テーマの設定
  • 現在実装中の機能

    • モード機能(Normal, Input, Select)

    • キーバンドの設定(難しい)

目標としてはfelixxplrの2つの段階にしようとしている

まずは,基本機能を備えたfelixのようなのを目標としてる

続いて,何も意見がなければxplrのように,より複雑な機能を考えている

これらを作りながら学んでいけたらいいなあと考えている

もしもある程度使えるようになったら,もちろんこれを成果物としてポートフォリオに載せても良い

完全にパクって自己流に改造しても良い

コードの解説

コードの書き始めはtui.rsのexamplesのlist.rstable.rstabs.rsの3つを流用して書いている

なのでmain.rsとキーバインドの仕方や仮想(代替?)ターミナルの初期化の話などは省略する

application.rs

基本的な画面を管理している

一番上のタブのウィンドウとディレクトリのウィンドウ,コマンド(押されたボタン)のウィンドウの3つを管理するためのstructがある

#[derive(Debug)]
struct App {
    directory_tabs: Vec<String>,
    tab_index: usize,
    dir_map: HashMap<String, StatefulDirectory>,
    command_hist: Vec<String>,
    (mode: Mode), // 実装中
}

directory_tab[tab_index]がキーでdir_mapStatefulDirが取り出されるようになっている

タブの移動やタブ移動によってstatefulDirが変わるようにAppのメソッド(関連関数)が書かれている

これらメソッドをハードコーディングされたキーバインドから呼び出すようになっている

vimのようにモードがあれば面白いかなということで,簡易的なモードを作成することを考えている

state.rs

ここではtui-rsのstateを持つwidgetの管理とその中身に当たるディレクトリとファイルアイテムの2つを保持している

画像の真ん中の四角の部分にあたる

#[derive(Debug, Clone)]
pub struct StatefulDirectory {
    directory: Directory,
    file_items: Vec<FileItem>,
    length: usize,
    state: TableState,
}

今ディレクトリ内のファイルアイテム上をカーソルが移動するようになっている

そのために必要な移動用のメソッドと現在選択しているファイルアイテムの情報を得るためのメソッドなどがある

file_item_list.rs(file_items.rsとdirectory_item.rsのmodファイル)

正直このファイルを分ける意味があったのかは定かではないが,モジュールに分ける練習として割り切って使っている

他のファイルも同様に階層化されたモジュール構造にするかを考えている(ui.rsなど)

#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Kinds {
    File(bool),
    Directory(bool),
}

ここでは実際のディレクトリ内にあるファイルがファイルなのかディレクトリなのか,ドットファイルなのかをenumで分類できるようにしている

また,下にはディレクトリ用のstruct,ファイル用のstructがそれぞれ存在しおり,ファイル名やパス名など保持できるようになっている

ui.rs

名前が説明的ではないので変更する可能性が大きい

tui.rsを使って今まで保持してきた情報を取り出して,TUIの画面として出力するところ

前半はひたすら色の指定やファイルアイコンの指定などを宣言しているだけ

    let size = f.size();
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
        .split(size);

ここでApp用のタブのウィンドウとディックトリのウィンドウの2つにターミナルを分割して四角形を作成する

constraintsの動作が独特で制約で画面のサイズが埋まるように画面を作成する

lengthが固定値でそれ以外は割合で変わる


タブの表示とタブの遷移

    let tab_titles = tabs
        .iter()
        .map(|t| {
            let (first, rest) = t.split_at(1);
            Spans::from(vec![
                Span::styled(first, Style::default().fg(ayu_orange)),
                Span::styled(rest, Style::default().fg(ayu_darkgray)),
            ])
        })
        .collect();
    let tabs = Tabs::new(tab_titles)
        .block(Block::default().borders(Borders::ALL).title("Tabs"))
        .select(index)
        .style(tab_style)
        .highlight_style(tab_highlight_style);

    f.render_widget(tabs, chunks[0]);

これはexampleにあるui関数をそのまま流用している

タブとしてはディレクトリ名を表示してある

タブの移動で表示されるディレクトリも変更される

タブの移動などはすべてwidgetのTabs::new()の箇所で指定してる

ここでタブがindexで変更されることやハイライト表示などを指定している


ここでディレクトリの内部にあるファイルアイテムの表示している

画像の真ん中のファイル名やサイズなどを表示しているところ

Table widgetを利用しており,vec![]に書かれている内容を列として文字通りテーブルを作成することができる

より細かく一つだけの色などを変えたい場合はCell::new()を使えばできる

    let file_item_iter = dir.take_file_items();
    let file_items_list = file_item_iter.iter().map(|file_item| {
        let name = file_item.name();
        let perm = if file_item.get_permission() {
            format!("{:>4}", "r")
        } else {
            format!("{:>4}", "rx")
        };
        let size = file_item.get_file_item_size();
        let date = file_item.get_created_date_and_time();
        let lines = if file_item.kinds() == Kinds::Directory(true) {
            vec![
                Span::raw(dir_symbol),
                Span::styled(name, dir_style),
                Span::raw(perm),
                Span::raw(size),
                Span::raw(date),
            ]
        } else {
            vec![
                Span::raw(file_symbol),
                Span::styled(name, file_style),
                Span::raw(perm),
                Span::raw(size),
                Span::raw(date),
            ]
        };
        Row::new(lines)
    }

アイテムシンボル,ファイルの名前,なんちゃってパーミッション,データのサイズ,日付を一列(Row::new())にしている

また,一番初めの要素がファイルやディレクトリのシンボルを変更しているのでnerd fontなどを使うとファイルアイコンなどを表示できる

加えてファイルとディレクトリのみの分類なので,ファイルのenumで拡張子をvariantで内部に持つことができればさらに拡張子でmatchを使って分類することもできる


先ほどの列のイテレータを今度はTable::new()でTableWidgetにしている

これにより選択することができるようになっている

    let items = Table::new(file_items_list)
        .header(header_cells)
        .block(
            Block::default()
                .borders(Borders::ALL)
                .title(current_dir_path),
        )
        .highlight_style(selecting_style)
        .highlight_symbol(selecting_symbol)
        .widths(&header_constraints);

    f.render_stateful_widget(items, chunks[1], &mut dir.take_state_dir());

最後のrenderがrender_statuful_widgetとなっておりrender_widgetとは異なる

このrenderはlistStateとtableStateの二つのみしかない

これで状態を保持することができるが,discussionにあるように別の方法でこれを使わなくても良い方法がある


基本的にはこのような構造と処理になっている

コードからわかるように私はコードを書くのがあまりよろしくない

ただ,このプロジェクトを通じて自身のスキルアップにつなげたいと考えている

以降は感想や目標について書くのでここでブラウザバックしてかまわない

なぜファイルマネージャー

記事に憧れたからでそこまで深い理由はない

ファイルマネージャなら初心者でも作れそうだと思ったから

ファイル(データなど)をよく操作するが,それをVSCodeで操作するのはめんどくさいから

GUIではなくTUIなのは,RustでGUIはなかなかにハードだから

eguiやiced,druidなどの練習として今作っているファイルマネージャーをGUI化しても良い

もしも,他に人が集まって作りたいアプリなどの案があればそちらへ移行しても良いと思っている

アプリランチャー,簡易ゲーム,gitビューワー,エディタなどなど

作りたい機能

早めにほしい機能(felix的な目標)

  • ファイルとディレクトリのコピーとペースト(cp)

  • ファイルとディレクトリの移動と削除(mv)

  • ディレクトリ作成(mkdir)

  • ファイル作成(touch)

  • ファイルをエディタで直接開く(vim, nvim, emacs, code)

  • tomlファイルの読み込み(ユーザー設定用)

    • 練習なのでronやyaml,jsonなどにも対応できるようにするのもあり
  • ファイル検索(find)

    • FuzzyFinder的なものを作ってみるのも面白いかも

将来的にあればうれしい機能(xplr的な目標)

  • ファイル内検索(grep)

    • grepやripgrepなどを使った検索

    • grepを自作でも面白い

  • ファイル内プレビュー(cat)

    • プレビューは外部ツールやアプリ(cat, batやphoto viewerなど)を使えたらいい

    • これも単純に中身だけ見れるようになるだけでも面白い

  • プラグインもどき

    • 現状は第二目標の中でも優先順位は低い

改善したいところ多数

特に名前が良くないというぼんやりしたことはわかってるがどのように変えれば良いかがわからないので,それを教えてほしい

また,githubのcommitの内容も割と適当なのでそういった指摘も欲しい

私自身もPRなど来たらこれらを注意してみたいとは思っている

  • 関数名や変数名などの変更

    • わかりやすい名前

    • 適切な命名

  • 使い方やコントリビュートなどのドキュメント化

    • docコメント

    • mdread

  • 効率的なデータ構造

    • HashMapやVecを利用しているが,BTreeとかの方が良いかどうかなど

    • 新しいデータ構造を一から作ってみるなど()

  • 効率的なアルゴリズムの選定

    • ファイル検索で二分木探索や深さ優先探索
  • コードをモジュール化

    • モジュールとしてファイル毎に分割

ここで学べることのまとめ

私が教えられるのはこれくらいしかない

  • Githubでの開発の基礎

  • Rustの基本文法

  • TUIの作り方の基礎

  • 基本的なデータ構造

  • Markdownの基本

私はコーディングが下手で適切で効率的なアルゴリズムやデータ構造を知らない

ただし,学ぶ気力はあるので頑張るつもりである

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?