Browse Source

Irreparable improvements of console

Alexey Edelev 7 years ago
parent
commit
f9345d20c0

+ 0 - 4
gitcommit.cpp

@@ -82,10 +82,6 @@ QString GitCommit::summary() const
 
 GitDiff* GitCommit::diff()
 {
-    if(isMerge()) {
-        return nullptr;//QString("Commit - merge");
-    }
-
     if(m_diff.isNull()) {
         git_commit *parent = nullptr;
         git_commit_parent(&parent, raw(), 0);

+ 97 - 2
gitconsole.cpp

@@ -2,6 +2,66 @@
 
 #include <QProcess>
 #include <gitrepository.h>
+#include <QTimer>
+
+struct Autocomplete {
+    Autocomplete() {
+        m_gitCommands
+                      << "git add"
+                      << "git mv"
+                      << "git reset"
+                      << "git rm"
+                      << "git grep"
+                      << "git log"
+                      << "git show"
+                      << "git status"
+                      << "git branch"
+                      << "git checkout"
+                      << "git commit"
+                      << "git diff"
+                      << "git rebase"
+                      << "git tag"
+                      << "git fetch"
+                      << "git pull"
+                      << "git clean"
+                      << "git reflog"
+                      << "git push"
+                      << "git submodule"
+                      << "git cherry-pick"
+                      << "git revert"
+                      << "git remote"
+                      << "git config"
+                      << "git clean"
+                      << "git stash";
+        qSort(m_gitCommands);
+    }
+
+    QList<QString> listMatches(const QString& text, QString& commonPart) {
+        QList<QString> matches;
+        foreach(QString command, m_gitCommands) {
+            if(command.startsWith(text)) {
+                matches.append(command);
+            }
+        }
+
+        if(matches.count() > 1) {
+            int i = 0;
+            for(; i < qMax(matches.first().length(), matches.last().length()); i++) {
+                if(matches.first().at(i) != matches.last().at(i)) {
+                    break;
+                }
+            }
+            commonPart = matches.first().mid(0, i);
+        }
+        return matches;
+    }
+
+    QList<QString> m_gitCommands;
+};
+
+namespace  {
+    Autocomplete completer;
+}
 
 GitConsole::GitConsole(QObject *parent) : QObject(parent)
   ,m_process(new QProcess(this))
@@ -24,6 +84,8 @@ void GitConsole::exec(const QString& command)
     m_recentIndex = -1;
     if(!command.startsWith("git ")) {
         emit commandError();
+        emit commandLog(QString("<b>$&nbsp;") + command.toHtmlEscaped() + QString("</b><br/>"), false);
+        emit commandLog(tr("Only <b>git</b> command is accetable").append("<br/>"), true);
         return;
     }
     qDebug() << "Execute:" << command << "in" << m_process->workingDirectory();
@@ -46,13 +108,16 @@ void GitConsole::onFinished(int exitCode)
     if(exitCode != 0 ) {
         emit commandError();
     }
+    QByteArray log = m_process->readAllStandardOutput();
+    log += m_process->readAllStandardError();
+
+    emit commandLog(QString::fromUtf8(log).toHtmlEscaped().replace("\n","<br/>"), true);
     setBusy(false);
 }
 
 void GitConsole::onOutputReady()
 {
-    QByteArray log = m_process->readAll();
-    emit commandLog(QString::fromUtf8(log).toHtmlEscaped().replace("\n","<br/>"), true);
+//    QByteArray log = m_process->readAll();
 }
 
 void GitConsole::recentUp()
@@ -81,3 +146,33 @@ void GitConsole::recentDown()
     }
     emit recentChanged(m_recentContainer.at(--m_recentIndex));
 }
+
+void GitConsole::requestAutocomplete(const QString& current)
+{
+    QString commonPart;
+    QList<QString> matches = completer.listMatches(current.trimmed(), commonPart);
+
+    if(matches.count() == 1) {
+        emit autocomplete(matches.first());
+        return;
+    }
+
+    if(matches.count() <= 0) {
+        return;
+    }
+
+    emit autocomplete(commonPart);
+}
+
+void GitConsole::requestPredictions(QString current)
+{
+    QString commonPart;
+    current = current.trimmed();
+
+    QList<QString> matches = completer.listMatches(current, commonPart);
+    if((!current.isEmpty() && matches.count() > 1) || (matches.count() == 1 && current != matches.first())) {
+        emit predict(completer.listMatches(current, commonPart));
+        return;
+    }
+    emit predict(QList<QString>());
+}

+ 6 - 0
gitconsole.h

@@ -14,6 +14,7 @@ class GitConsole : public QObject
     Q_OBJECT
     Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
     Q_PROPERTY(QString recent READ recent NOTIFY recentChanged)
+
 public:
     GitConsole(QObject* parent = 0);
     ~GitConsole();
@@ -49,12 +50,17 @@ public slots:
 
     Q_INVOKABLE void recentUp();
     Q_INVOKABLE void recentDown();
+    Q_INVOKABLE void requestAutocomplete(const QString& current);
+    Q_INVOKABLE void requestPredictions(QString current);
 
 signals:
     void busyChanged(bool busy);
     void commandLog(const QString& data, bool prepend);
+    void resetLog();
     void commandError();
     void recentChanged(QString recent);
+    void autocomplete(const QString& value);
+    void predict(const QList<QString>& predictions);
 
 private:
     QProcess* m_process;

+ 1 - 1
githandler.cpp

@@ -163,7 +163,7 @@ void GitHandler::updateModels()
     m_tagList->reset(m_activeRepo->tags().values());
 }
 
-void GitHandler::copySha1(const QString& sha1)
+void GitHandler::copy(const QString& sha1)
 {
     QGuiApplication::clipboard()->setText(sha1);
 }

+ 1 - 1
githandler.h

@@ -34,7 +34,7 @@ public:
 
     Q_INVOKABLE GitDiff* diff(GitCommit* a, GitCommit* b);
 
-    Q_INVOKABLE void copySha1(const QString& sha1);
+    Q_INVOKABLE void copy(const QString& sha1);
 
     RepositoryModel* repositories() const
     {

BIN
images/erase-24.png


BIN
images/erase-24_active.png


BIN
images/flow-merge-16.png


BIN
images/flow-merge.png


BIN
images/gear-2-24.png


BIN
images/gear-2-24_active.png


+ 13 - 0
qml/CommitInfo.qml

@@ -45,6 +45,19 @@ Item {
             field: qsTr("Email")
             value: commit ? commit.email : ""
         }
+
+        CommitInfoLine {
+            field: qsTr("Summary")
+            value: commit ? commit.summary : ""
+        }
+    }
+    Image {
+        id: merge
+        visible: commit.isMerge
+        source: "qrc:///images/flow-merge.png"
+        anchors.right: shortInfo.right
+        anchors.rightMargin: 5
+        anchors.top: shortInfo.top
     }
     Column {
         anchors.top: shortInfo.bottom

+ 11 - 3
qml/CommitList.qml

@@ -14,6 +14,7 @@ FlickPager {
         property int graphWidth: 0
         property int fullMessageWidth: 600
         property int annotationWidth: 200
+        property int summaryAdditionalSpace: 95 //Applicable for commitsOnly state
     }
 
     width: 120
@@ -31,7 +32,7 @@ FlickPager {
             name: "commitsOnly"
             PropertyChanges {
                 target: root
-                width: d.commitsWidth + d.graphWidth + d.annotationWidth
+                width: d.commitsWidth + d.graphWidth + d.annotationWidth + d.summaryAdditionalSpace
             }
         },
         State {
@@ -78,11 +79,18 @@ FlickPager {
                         width: root.width - graph.width - graphAnnotation.width
                         height: sha1.height
                         anchors.right: parent.right
+
+                        Image {
+                            id: marker
+                            visible: model.isMerge
+                            source: "qrc:///images/flow-merge-16.png"
+                        }
+
                         Text {
                             id: sha1
                             font.family: "Inconsolata"
                             font.pointSize: 12
-                            anchors.left: parent.left
+                            anchors.left: marker.right
                             text: "[" + model.shortSha1 + "]"
                             Component.onCompleted: {
                                 d.commitsWidth = sha1.width  + 10
@@ -135,7 +143,7 @@ FlickPager {
                         MenuItem {
                             text: "Copy sha-id"
                             onTriggered: {
-                                _handler.copySha1(model.sha1)
+                                _handler.copy(model.sha1)
                             }
                         }
                         MenuItem {

+ 5 - 1
qml/CommitPlane.qml

@@ -1,5 +1,6 @@
 import QtQuick 2.0
 import QtQuick.Controls 1.4
+import org.semlanik.nicegit 1.0
 
 Item {
     id: root
@@ -86,13 +87,16 @@ Item {
             id: innerItem
             width: fileDiff.contentWidth
             height: fileDiff.contentHeight + 20
-            Text {
+            TextEdit {
                 id: fileDiff
                 text: d.diffModel ? d.diffModel.data : ""
                 anchors.top: parent.top
                 anchors.topMargin: 10
+                textFormat: TextEdit.RichText
                 font.family: "Inconsolata"
                 font.pointSize: 12
+                selectByMouse: true
+                readOnly: true
             }
         }
     }

+ 160 - 27
qml/ConsoleControl.qml

@@ -1,8 +1,9 @@
 import QtQuick 2.0
 import QtQuick.Controls 1.4
-
+import QtQuick.Controls.Styles 1.4
 Item {
     id: root
+    property bool logEmpty: true
     Rectangle {
         anchors.fill: parent
         color: "#839496"
@@ -21,17 +22,25 @@ Item {
                 target: consoleInput
                 focus: true
             }
+            PropertyChanges {
+                target: controlBar
+                opacity: 1.0
+            }
         },
         State {
             name: "closed"
             PropertyChanges {
                 target: root
-                height: consoleInput.height
+                height: consoleInput.height + consoleInput.anchors.bottomMargin*2
             }
             PropertyChanges {
                 target: consoleInput
                 focus: false
             }
+            PropertyChanges {
+                target: controlBar
+                opacity: 0.0
+            }
         }
     ]
 
@@ -51,8 +60,11 @@ Item {
             id: consoleLog
             width: root.width - 10
             height: contentHeight
-            textFormat: Text.RichText
+            textFormat: TextEdit.RichText
+            font.family: "Inconsolata"
+            font.pointSize: 11
             color: "#002b36"
+            onTextChanged: root.logEmpty = consoleLog.text.length <= 0
             Connections {
                 target: _handler.console
                 onCommandLog: {
@@ -64,53 +76,56 @@ Item {
                 onCommandError: {
                     fadeIn.start()
                 }
+                onResetLog: {
+                    consoleLog.text = ""
+                }
             }
         }
     }
 
     Rectangle {
         id: errorRect
-        anchors.left: parent.left
-        anchors.right: parent.right
-        anchors.top: consoleInput.top
-        anchors.bottom: consoleInput.bottom
-        color: "#ff0000"
-        opacity: 0.0
-        visible: opacity > 0
+        anchors.fill: consoleInput
+        anchors.rightMargin: -2
+        anchors.leftMargin: -2
+        opacity: 0.6
+        border.width: 1
+        radius: 2
 
-        NumberAnimation {
+        ColorAnimation on color {
             id: fadeIn
-            target: errorRect
-            property: "opacity"
+            from: "#ffffff"
+            to: "#ffaeae"
+            running: false
             duration: 350
-            from: 0.0
-            to: 1.0
             onStopped: {
                 fadeOut.start()
             }
         }
 
-        NumberAnimation {
+        ColorAnimation on color {
             id: fadeOut
-            target: errorRect
-            property: "opacity"
+            from: "#ffaeae"
+            to: "#ffffff"
+            running: false
             duration: 350
-            from: 1.0
-            to: 0.0
         }
     }
 
     TextInput {
         id: consoleInput
         anchors.bottom: parent.bottom
-        height: 30
+        height: 20
         font.weight: Font.Bold
         color: "#002b36"
         anchors.left: parent.left
         anchors.right: parent.right
-        anchors.rightMargin: 5
-        anchors.leftMargin: 5
+        anchors.rightMargin: 3
+        anchors.leftMargin: 3
+        anchors.bottomMargin: 2
         enabled: !_handler.console.busy
+        font.family: "Inconsolata"
+        font.pointSize: 11
         onAccepted: {
             _handler.console.exec(consoleInput.text);
             consoleInput.text = ""
@@ -119,12 +134,123 @@ Item {
             switch(event.key) {
             case Qt.Key_Up:
                 event.accepted = true
-                _handler.console.recentUp();
-                break;
+                _handler.console.recentUp()
+                break
             case Qt.Key_Down:
                 event.accepted = true
-                _handler.console.recentDown();
-                break;
+                _handler.console.recentDown()
+                break
+            case Qt.Key_Tab:
+                event.accepted = true
+                _handler.console.requestAutocomplete(consoleInput.text)
+                break
+            case Qt.Key_C:
+                if(event.modifiers === Qt.ControlModifier) {
+                    event.accepted = true
+                    consoleInput.text = ""
+                    if(flick.flickable.contentHeight > flick.flickable.height) {
+                        flick.flickable.contentY = flick.flickable.contentHeight - flick.flickable.height
+                    }
+                }
+                break
+            case Qt.Key_Escape:
+                event.accepted = true
+                consoleInput.text = ""
+                break
+                //TODO: choose from prediction menu
+                //            case Qt.Key_Right:
+                //                if(event.modifiers === Qt.ControlModifier) {
+                //                    event.accepted = true
+                //                    predictionsRepeater.activeIndex++
+                //                    if(predictionsRepeater.activeIndex >= predictionsRepeater.model) {
+                //                        predictionsRepeater.activeIndex = 0;
+                //                    }
+                //                }
+                //                break
+                //            case Qt.Key_Left:
+                //                if(event.modifiers === Qt.ControlModifier) {
+                //                    event.accepted = true
+                //                    predictionsRepeater.activeIndex--
+                //                    if(predictionsRepeater.activeIndex < 0) {
+                //                        predictionsRepeater.activeIndex = predictionsRepeater.model - 1;
+                //                    }
+                //                }
+                //                break
+            }
+        }
+
+        onTextChanged: {
+            predictionsRepeater.activeIndex = -1
+            _handler.console.requestPredictions(consoleInput.text);
+        }
+
+        Row {
+            anchors.bottom: parent.top
+            spacing: 3
+            Repeater {
+                id: predictionsRepeater
+                property var values: null
+                property int activeIndex: -1
+                model: values ? values.length : 0
+                Rectangle {
+                    opacity: 0.6
+                    width: prediction.width + 10
+                    height: prediction.height + 10
+                    border.width: 1
+                    radius: 2
+                    color: predictionsRepeater.activeIndex === model.index ? "#e5f1f7" : "#ffffff"
+                    Text {
+                        id: prediction
+                        anchors.centerIn: parent
+                        text: predictionsRepeater.values[model.index]
+                    }
+                }
+            }
+        }
+    }
+
+    Row {
+        id: controlBar
+        anchors.right: parent.right
+        anchors.rightMargin: 2
+        anchors.top: parent.top
+        anchors.topMargin: 2
+        spacing: 10
+        visible: opacity > 0
+
+        Behavior on opacity {
+            NumberAnimation {
+                duration: 200
+                from: 0.0
+            }
+        }
+
+        Button {
+            visible: !root.logEmpty
+            style: ButtonStyle {
+                background: Image {
+                    source: control.pressed ? "qrc:///images/erase-24_active.png" : "qrc:///images/erase-24.png"
+                }
+            }
+            onClicked: _handler.console.resetLog()
+        }
+        Button {
+            style: ButtonStyle {
+                background: Image {
+                    source: control.pressed ? "qrc:///images/gear-2-24_active.png" : "qrc:///images/gear-2-24.png"
+                }
+            }
+            onClicked: consoleSettingsMenu.popup()
+        }
+    }
+
+    Menu {
+        id: consoleSettingsMenu
+        MenuItem {
+            checkable: true
+            text: "Show predictions"
+            onTriggered: {
+                //TODO: Once settigns are implemented need to make console more or less configurable
             }
         }
     }
@@ -134,5 +260,12 @@ Item {
         onRecentChanged: {
             consoleInput.text = _handler.console.recent
         }
+        onAutocomplete: {
+            consoleInput.text = value
+            consoleInput.cursorPosition = consoleInput.length
+        }
+        onPredict: {
+            predictionsRepeater.values = predictions
+        }
     }
 }

+ 30 - 7
qml/DiffFiles.qml

@@ -14,12 +14,12 @@ ListView {
         property QtObject diffModel: root.diff.model(modelData)
         width: commitBodyText.width + action.width + 10
         height: commitBodyText.height + 10
-//        Rectangle {
-//            color: "red"
-//            anchors.right: action.left
-//            height: parent.height
-//            width: parent.width*diffModel.similarity/100
-//        }
+        //        Rectangle {
+        //            color: "red"
+        //            anchors.right: action.left
+        //            height: parent.height
+        //            width: parent.width*diffModel.similarity/100
+        //        }
         Text {
             id: commitBodyText
             anchors.bottom: parent.bottom
@@ -33,8 +33,19 @@ ListView {
                 id: control
                 anchors.fill: parent
                 hoverEnabled: true
+                acceptedButtons: Qt.RightButton|Qt.LeftButton
                 onClicked: {
-                    root.openDiff(modelData)
+                    if(mouse.button === Qt.RightButton) {
+                        var coord = commitBodyText.mapToItem(TooltipViewModel.viewport, 0, 0)
+                        TooltipViewModel.x = coord.x
+                        TooltipViewModel.y = coord.y
+                        TooltipViewModel.text = qsTr("Filename copied ") + modelData
+                        TooltipViewModel.visible = true
+                        _handler.copy(modelData)
+                        tttimer.restart()
+                    } else {
+                        root.openDiff(modelData)
+                    }
                 }
             }
         }
@@ -60,4 +71,16 @@ ListView {
             }
         }
     }
+
+    Timer {
+        id: tttimer
+        interval: 1000
+        repeat: false
+        onTriggered: {
+            TooltipViewModel.visible = false
+        }
+        Component.onDestruction: {
+            TooltipViewModel.visible = false
+        }
+    }
 }

+ 10 - 0
qml/MainView.qml

@@ -88,6 +88,11 @@ FocusScope {
         case Qt.Key_F4:
             consoleContol.state = consoleContol.state === "closed" ? "opened" : "closed"
             break
+        case Qt.Key_G://Found the G point. Let's open console using this action.
+            //TODO: Later has to be part of Console C++ class to read settings
+            consoleContol.state = "opened"
+            _handler.console.requestAutocomplete("g");
+            break
         default:
             event.accepted = false
         }
@@ -100,6 +105,11 @@ FocusScope {
         }
     }
 
+    onActiveFocusChanged: {
+        root.controlActive = false
+        console.log("control released");
+    }
+
     Tooltip {
     }
 }

+ 10 - 9
qml/TopBar.qml

@@ -1,13 +1,14 @@
 import QtQuick 2.0
 import QtQuick.Dialogs 1.2
 import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
 Item {
     id: root
     anchors.right: parent.right
     anchors.left: parent.left
     height: childrenRect.height
     signal closeClicked()
-    property alias closeVisible: closeButton.visible
+    property alias closeVisible: closeControl.visible
     Row {
         spacing: 10
         height: 50
@@ -36,20 +37,20 @@ Item {
 
     }
 
-    Image {
-        id: closeButton
+    Button {
+        id: closeControl
         anchors.right: parent.right
         anchors.top: parent.top
         anchors.topMargin: 10
         anchors.rightMargin: 10
-        source: closeControl.pressed ? "qrc:///images/x-mark-3-24_active.png" : "qrc:///images/x-mark-3-24.png"
-        MouseArea {
-            id: closeControl
-            anchors.fill: parent
-            onClicked: {
-                root.closeClicked()
+        style: ButtonStyle {
+            background: Image {
+                source: control.pressed ? "qrc:///images/x-mark-3-24_active.png" : "qrc:///images/x-mark-3-24.png"
             }
         }
+        onClicked: {
+            root.closeClicked()
+        }
     }
 }
 

+ 6 - 0
resources.qrc

@@ -46,5 +46,11 @@
         <file>images/diff-modified.png</file>
         <file>images/diff-removed.png</file>
         <file>images/diff-renamed.png</file>
+        <file>images/erase-24.png</file>
+        <file>images/gear-2-24.png</file>
+        <file>images/gear-2-24_active.png</file>
+        <file>images/erase-24_active.png</file>
+        <file>images/flow-merge.png</file>
+        <file>images/flow-merge-16.png</file>
     </qresource>
 </RCC>