ろうとるがPythonを扱う、、（その４：まとも版コマンドプロンプトもどき）

# Windowsコマンドプロンプトもどきプログラム（まとも版）
[前回（その３）](https://qiita.com/infinite1oop/items/96a153037bd7b6edf093)で予告した、Text Widgetのみでコマンドプロンプトもどきを実現（より近づく）できたので、その記録をここに記す。

## 作成したもの
![最初.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/da228482-8364-4913-3f6d-f65629a04a93.png)
ここでは、「> 」がプロンプトである。
![結果１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/f9d0eecb-9e9e-b8be-f997-e563d23e251d.png)
![結果２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/aca206f2-594d-feba-6b54-0b3f13a98a86.png)
コマンドを入力すると実行された結果が表示される。スクロールもあり。

## ソースコード
```python:
# -*- coding: utf-8 -*-
import subprocess
import sys
import tkinter as tk

def func(event):
    current = result.get('1.0', tk.END)
    lastline = current.rsplit('\n')[-2]
    laststr = lastline.split(' ', 1)[1]  # removal of prompt('> ')
    cmd_arg = laststr.split(' ')         # make it to be list
    cmd = cmd_arg[0].lower()             # take "command"
    if cmd == "exit":                    # exit program
        sys.exit()

    output = subprocess.run(cmd_arg, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout
    output = output.decode('shift_jis')
    result.insert(tk.END, '\n' + output + '\n> ')
    result.see('end')
    return "break"                       # prevent cursor from going to the next line

root = tk.Tk()
root.geometry('600x500')
root.title('Like command prompt 2')

frm = tk.Frame()
frm.place(x = 40, y = 20)

result = tk.Text(frm, font=("", 10), width=70, height=35, wrap="word")
ysc = tk.Scrollbar(frm, orient=tk.VERTICAL, command=result.yview)
ysc.pack(side=tk.RIGHT, fill="y")
result["yscrollcommand"] = ysc.set
result.insert(tk.END, '> ')              # insert prompt('> ')
result.pack()
result.bind('<Return>', func)

root.mainloop() 
```
コマンドを実行するSubprocess、Text Widget、スクロールバーについては、[前回（その３）](https://qiita.com/infinite1oop/items/96a153037bd7b6edf093)を参照。ここでは、それ以外のポイントとなる点を順不同で記載。

```python
result.insert(tk.END, '> ')              # insert prompt('> ')
result.pack()
result.bind('<Return>', func)
```
初期処理として、ボックス（Text Widget）にプロンプト「> 」を入れ、Return(Enter)が押されたときの関数（func）を登録。

```python
def func(event):
    current = result.get('1.0', tk.END)
    lastline = current.rsplit('\n')[-2]
    laststr = lastline.split(' ', 1)[1]  # removal of prompt('> ')
    cmd_arg = laststr.split(' ')         # make it to be list
    cmd = cmd_arg[0].lower()             # take "command"
    if cmd == "exit":                    # exit program
        sys.exit()
```
- 現在のボックス内のデータをすべて「current」に格納
- 最終行にコマンドが含まれているので、最後の行をrsplit()で取得（改行コード'\n'を区切り文字、Indexである"-2"はTry&Errorで見つけた）し、「lastline」に格納
- その行の最初にはプロンプトがあるので、split()でそれを削除し、最後にSubprocessに渡すために、split()でリスト化
- 最初の空白までがコマンド
    - それが「exit」であれば本プログラムを終了

なお、rsplit()やsplit()については、下記サイトを参照した。
- [Pythonで文字列を分割（区切り文字、改行、正規表現、文字数）](https://note.nkmk.me/python-split-rsplit-splitlines-re/)

```python
    output = subprocess.run(cmd_arg, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout
    output = output.decode('shift_jis')
    result.insert(tk.END, '\n' + output + '\n> ')
    result.see('end')
    return "break"                       # prevent cursor from going to the next line
```
- Subprocessによりコマンドを実行し、その結果をボックスに追加
- プロンプトを追加
- 最後の「return "break"」が重要。これがないと、コマンドプロンプトの次の空白が改行されてしまい、ボックス内のカーソルが次の行に移動してしまう。

「return "break"」については調べたが、正直この行の効果はよくわからなかった。これに関して参考としたサイトは次のもの。
- [the tkinter text widget insertion cursor](https://splunktool.com/the-tkinter-text-widget-insertion-cursor)

## おまけ
今回、カーソルの移動を防ぐ「return "break"」を見つけるのに最も苦労した。それにたどり着くまでに有益と思った情報を記す。

#### TextWidget内でカーソルを戻す
```python
result = tk.Text(frm, font=("", 10), width=70, height=35, wrap="word")

......

result.mark_set("insert", "insert-1c") # 1文字戻す
result.mark_set("insert", "insert-2c") # 2文字戻す
```

#### TextWidget内でカーソルを特定の場所に移動
```python
result.mark_set("insert", "%d.%d" % (2, 10)) # 2行目＆10文字目(”0”が最初)
```

#### 最終行番号
```python
int(float(result.index('end')))
```
'end'とtk.ENDとは同じのようだ。

#### HEX dump
```python
# 変数「sss」が「str」型とする
print(''.join([f'{ord(c):02x}' for c in sss]))
```

EOF