edbee-lib icon indicating copy to clipboard operation
edbee-lib copied to clipboard

Multiple Selection behaviour like SublimeText

Open brupelo opened this issue 5 years ago • 9 comments

I'd like to have in edbee-lib the same nice behaviour when making multiple selections than SublimeText. Compare the behaviour between SublimeText and edbee-lib (using the same sequence of mouse/keyboard actions)

showcase

Is this already implemented? If it's so, how do I enable it?

Ps. Using windows7 + 5.11.3 over here Ps2. On windows & ST, to create new selection/region on the view you just need to hold ctrl while creating the new selections with the mouse.

brupelo avatar Feb 20 '19 15:02 brupelo

This is not yet available, but would be very nice to have!

gamecreature avatar Feb 22 '19 12:02 gamecreature

This is not yet completely fixed. At the moment it allows you to add carets from the top to the bottom. (You cannot add ranges before the last one)

To do this, I need to remember the index of the caret has been added with the Ctrl+Click. This can be tricky with overlapping ranges which get merged... So that still is a WIP

gamecreature avatar Mar 05 '19 08:03 gamecreature

Sounds awesome, when this is totally implemented I'll give it a shot again to the project, thing is, over the last weeks I've been testing a lot of projects to find a good replacemente for QScintilla widgets used on my python projects (pyqt&pyside2) and at this point I haven't found anything, here's a little list of the ones I've tried:

  • https://github.com/edbee/edbee-lib/tree/master/edbee-lib : Yours, with this one I've made 2 experiments:

    a) The first one has been creating a large file containing almost all python bindings for almost all existing data structures b) After finishing a I've got quite excited so I thought maybe rewriting the whole thing in python could be fun and cool, what ended in https://github.com/brupelo/pylime, I thought magically some pythonists would start contributing to it... but obviously people just like things that work out of the box, so since I've posted that project nobody joined and I lost a little bit of "excitement" about it :)

  • https://github.com/trishume/syntect : This is a great rust library and I thought maybe I could just try to improve my QScintilla widgets with some of the features of Sublime, so another experiment I did was creating python bindings from this library in order to implement my favourite Sublime features into Scintilla... unfortunately, right now I'm stuck trying to figure out how functions such as extract_scope works... so that way i could be able to implement a proper toggle_comment on my scintilla widgets :/

  • https://github.com/ajaxorg/ace : At certain point I thought porting this nice embeddable widget to python could be a good choice but then I reevaluated and I thought 2 months of working on this wouldn't be worth... so I didn't give it a shot

  • https://github.com/xi-editor/xi-editor : Yesterday I've started to write a frontend about this one https://github.com/brupelo/pyxiqt to see how nice that could be... I don't have high expectations that the outcome will be very similar to SublimeText though... but that's what I'm working on right now :)

  • https://github.com/prymatex/prymatex : This is a nice python textmate clone with lots of nice interesting bits of code... some parts are not good (highly coupled and without proper levelization) but other ones contain a lot of interesting code. With this one I've checked how fast the scope parser would be and it seemed to be quite slow... parsing a simple python file of few lines of coude would take ~1s!!! That's crazy :/

Over the last 2 weeks I've been researching a lot to find alternative that allowed me to replace my QScitilla widgets for something that would look like SublimeText and this one is the closest alternative (and probably the fastest in terms of performance) but there is one thing that throws me off quite a lot to contribute more actively:

  • There are no many contributors in the project
  • You prefer ruby over python
  • The code is very good in many places but in other ones is overly complex with excesive layers of unnecesary abstractions and sometimes highly coupled, so unit testing all diferent subcomponents is not trivial in many cases. Not saying is bad code! It's just that needs quite a bit of refactoring to make it better

So... after all this research I haven't found any ideal solution for my projects and not sure what's the best direction to take :/ . I thought getting a SublimeText-like standalone widget for Qt apps would be much easier than what it's proving to be... or maybe it's just me who's too picky and I should stay with the old creepy Scintillas... hehe.

Ps. Sorry for the long comment but I just wanted to clarify a bit and giving you a bit more of context about why I didn't comment earlier on this issue. Which btw, that's another reason why I started considering alternatives, as this is one of my favourites features when coding in SublimeText, can't live without it to be really productive ;)

brupelo avatar Mar 13 '19 09:03 brupelo

I think the best direction, still, would be to improve upon the existing C++ widget which is used by several projects (one of them very large).

vadi2 avatar Mar 13 '19 11:03 vadi2

I also agree that c++ is the way to go. Because it can be integrated in other project that way... @brupelo I agree some items are complexer (and more abstracted) that you think that is needed.

I've written edbee for a project I was working which required a very open en flexible editor. I needed to have undo buffers over multiple editor windows, sychronized caret movements. Dynamic lines adding / Removing. Side comparing contents in multiple editors... (I could compare 3-4 editors side by side and sync them)

This is the reason it is abstracted on many fronts...

Unfortunately my current time for the project is very limited. (Very busy with customer projects .. ) This doesn't mean the project is dead. I will fix and implement things when needed... But real big changes, will take to much time for me alone to do it ... So feel free to contribute, send pull requests etc.. I actively monitor this project!

gamecreature avatar Mar 14 '19 10:03 gamecreature

Yeah, that's another reason why joining the project looks "risky", this is a project you can't work full time. I can't neither... actually an emdedable proper text editor widget isnt the higher priority to me but I'd definitely wanted to get it done so the quality of my projects using text editor widgets would take the next level in terms of quality and usability.

I think when i'm back home I'll upload all the stuff i did providing python people a python module ready to use edbee-lib in pyqt. Then I'll put some links to redirect traffic here to the upstream. Hopefully, that will help to the project to gain more traction and contributors, even if 1 or 2 :). Problem is my github account isnt very popular as i dont post almost any interesting content there, just throw away code or experiments basically

In the meantime I think I'll be back to scintilla land where everything is creepier but at least is mature stuff. Btw, when i talked before about coupling and levelization didn't intend to criticize your code, so pls don't get me wrong. It was more in line of applying all the good lessons from jhon famous c++ lakos book. I think there are videos on youtube from lakos as explaining those concepts as well... When im back home Ill post the links, right now im texting from phone, which is really tidious ;)

brupelo avatar Mar 14 '19 11:03 brupelo

In Mudlet we had to ditch QScintilla early on because it was crashy. edbee's C++ widget is certainly better than QScintilla, which is what you found when you discovered the project - not sure why would it be any worse a few weeks later... I recommend you stick it out with this!

vadi2 avatar Mar 14 '19 12:03 vadi2

I think I've finally decided the direction I'm gonna follow... I've just created a new python project, https://github.com/brupelo/pyblime trying to improve Scintilla... once the widget becomes usable maybe I'll decouple it from Scintilla.

That said, posting here my last attempt of wrapping edbee into python as I won't need it anymore:

%Module(name=pyedbee_lib)
%Import QtCore/QtCoremod.sip
%Import QtGui/QtGuimod.sip
%Import QtWidgets/QtWidgetsmod.sip

namespace edbee {

class LineEnding;
class RegExp;
class TextAutoCompleteProviderList;
class TextBuffer;
class TextCodec;
class TextDocumentFilter;
class TextDocumentScopes;
class TextEditorConfig;
class TextEditorController;
class TextLexer;
class TextLineData;
class TextLineDataManager;
class TextRangeSet;
class TextUndoStack;

// ----------------------------edbee/models/change.h----------------------------
class Change {
    
%TypeHeaderCode
#include "edbee/models/change.h"
%End

public:
    virtual ~Change();
    virtual void execute(TextDocument *document) = 0;
    virtual void revert(TextDocument *);
    virtual bool giveAndMerge(TextDocument *document, edbee::Change *textChange);
    virtual bool canUndo();
    virtual bool isPersistenceRequired();
    virtual TextEditorController *controllerContext();
    bool isDocumentChange();
    virtual bool isGroup();
    virtual QString toString() = 0;
};

class DocumentChange : public edbee::Change {
    
%TypeHeaderCode
#include "edbee/models/change.h"
%End

};

class EmptyDocumentChange : public edbee::Change {
    
%TypeHeaderCode
#include "edbee/models/change.h"
%End

public:
    virtual bool isPersistenceRequired();
    virtual void execute(TextDocument *);
    virtual void revert(TextDocument *);
    virtual QString toString();
};

class ControllerChange : public edbee::Change {
    
%TypeHeaderCode
#include "edbee/models/change.h"
%End

public:
    ControllerChange(TextEditorController *controller);
    virtual TextEditorController *controllerContext();
    virtual TextEditorController *controller();
};

class ChangeGroup : public edbee::ControllerChange {
    
%TypeHeaderCode
#include "edbee/models/change.h"
%End

public:
    ChangeGroup(TextEditorController *controller);
    virtual ~ChangeGroup();
    virtual bool isGroup();
    virtual bool isDiscardable();
    virtual void groupClosed();
    virtual void execute(TextDocument *document);
    virtual void revert(TextDocument *document);
    virtual bool giveAndMerge(TextDocument *document, edbee::Change *textChange);
    virtual void flatten();
    virtual void giveChange(TextDocument *doc, edbee::Change *change);
    virtual Change *at(int idx);
    virtual Change *take(int idx);
    virtual int size();
    virtual void clear(bool performDelete = true);
    Change *last();
    Change *takeLast();
    int recursiveSize();
    virtual TextEditorController *controllerContext();
    virtual QString toString();
};

// -----------------------------------------------------------------------------


// --------------------------edbee/models/textgrammar.h-------------------------
class TextGrammarRule {
    
%TypeHeaderCode
#include "edbee/models/textgrammar.h"
%End

public:
    enum Instruction { MainRule = 0, RuleList = 1, SingleLineRegExp = 2, MultiLineRegExp = 3, IncludeCall = 4, Parser = 5 };
    TextGrammarRule(edbee::TextGrammar *grammar, Instruction instruction);
    ~TextGrammarRule();
    static TextGrammarRule *createMainRule(edbee::TextGrammar *grammar, const QString &scopeName);
    static TextGrammarRule *createRuleList(edbee::TextGrammar *grammar);
    static TextGrammarRule *createSingleLineRegExp(edbee::TextGrammar *grammar, const QString &scopeName, const QString &regExp);
    static TextGrammarRule *createMultiLineRegExp(edbee::TextGrammar *grammar, const QString &scopeName, const QString &contentScopeName, const QString &beginRegExp, const QString &endRegExp);
    static TextGrammarRule *createIncludeRule(edbee::TextGrammar *grammar, const QString &includeName);
    bool isMainRule();
    bool isRuleList();
    bool isMultiLineRegExp();
    bool isSingleLineRegExp();
    bool isIncludeCall();
    int ruleCount() const;
    TextGrammarRule *rule(int idx) const;
    void giveRule(edbee::TextGrammarRule *rule);
    void giveMatchRegExp(RegExp *regExp);
    void setEndRegExpString(const QString &str);
    Instruction instruction() const;
    void setInstruction(Instruction ins);
    QString scopeName() const;
    void setScopeName(const QString &scopeName);
    RegExp *matchRegExp() const;
    QString endRegExpString() const;
    const QMap<int, QString> &matchCaptures();
    const QMap<int, QString> &endCaptures();
    QString contentScopeName() const;
    void setContentScopeName(const QString &name);
    QString includeName();
    void setIncludeName(const QString &name);
    void setCapture(int idx, const QString &name);
    void setEndCapture(int idx, const QString &name);
    QString toString(bool includePatterns = true);
    TextGrammar *grammar();

private:
    static RegExp *createRegExp(const QString &regexp);
};

class TextGrammar {
    
%TypeHeaderCode
#include "edbee/models/textgrammar.h"
%End

public:
    TextGrammar(const QString &name, const QString &displayName);
    ~TextGrammar();
    void giveMainRule(edbee::TextGrammarRule *mainRule);
    QString name() const;
    QString displayName() const;
    TextGrammarRule *mainRule() const;
    QStringList fileExtensions() const;
    void giveToRepos(const QString &name, edbee::TextGrammarRule *rule);
    TextGrammarRule *findFromRepos(const QString &name, edbee::TextGrammarRule *defValue = 0);
    void addFileExtension(const QString &ext);
};

class TextGrammarManager {
    
%TypeHeaderCode
#include "edbee/models/textgrammar.h"
%End

public:
    TextGrammar *readGrammarFile(const QString &file);
    void readAllGrammarFilesInPath(const QString &path);
    TextGrammar *get(const QString &name);
    void giveGrammar(edbee::TextGrammar *grammar);
    TextGrammar *defaultGrammar();
    TextGrammar *detectGrammarWithFilename(const QString &fileName);
    QString lastErrorMessage() const;

protected:
    TextGrammarManager();
    virtual ~TextGrammarManager();
};

// -----------------------------------------------------------------------------


// ---------------------------edbee/views/texttheme.h---------------------------
class TextTheme : public QObject {
    
%TypeHeaderCode
#include "edbee/views/texttheme.h"
%End

public:
    TextTheme();
    virtual ~TextTheme();
    QString name();
    void setName(const QString &name);
    QString uuid();
    void setUuid(const QString &uuid);
    QColor backgroundColor();
    void setBackgroundColor(const QColor &color);
    QColor caretColor();
    void setCaretColor(const QColor &color);
    QColor foregroundColor();
    void setForegroundColor(const QColor &color);
    QColor invisiblesColor();
    void setInvisiblesColor(const QColor &color);
    QColor lineHighlightColor();
    void setLineHighlightColor(const QColor &color);
    QColor selectionColor();
    void setSelectionColor(const QColor &color);
    QColor findHighlightBackgroundColor();
    void setFindHighlightBackgroundColor(const QColor &color);
    QColor findHighlightForegroundColor();
    void setFindHighlightForegroundColor(const QColor &color);
    QColor selectionBorderColor();
    void setSelectionBorderColor(const QColor &color);
    QColor activeGuideColor();
    void setActiveGuideColor(const QColor &color);
    QColor bracketForegroundColor();
    void setBracketForegroundColor(const QColor &color);
    QString bracketOptions();
    void setBracketOptions(const QString &str);
    QColor bracketContentsForegroundColor();
    void setBracketContentsForegroundColor(const QColor &color);
    QString bracketContentsOptions();
    void setBracketContentsOptions(const QString &str);
    QString tagsOptions();
    void setTagsOptions(const QString &str);
};

class TextThemeManager : public QObject {
    
%TypeHeaderCode
#include "edbee/views/texttheme.h"
%End

public:
    void clear();
    TextTheme *readThemeFile(const QString &fileName, const QString &name = QString());
    void listAllThemes(const QString &themePath = QString());
    int themeCount();
    QString themeName(int idx);
    TextTheme *theme(const QString &name);
    TextTheme *fallbackTheme() const;
    QString lastErrorMessage() const;
    void setTheme(const QString &name, edbee::TextTheme *theme);

protected:
    TextThemeManager();
    virtual ~TextThemeManager();

signals:
    void themePointerChanged(const QString &name, edbee::TextTheme *oldTheme, edbee::TextTheme *newTheme);
};

// -----------------------------------------------------------------------------


// --------------------------edbee/views/textrenderer.h-------------------------
class TextRenderer : public QObject {
    
%TypeHeaderCode
#include "edbee/views/textrenderer.h"
%End

public:
    TextRenderer(TextEditorController *controller);
    virtual ~TextRenderer();
    virtual void init();
    virtual void reset();
    int lineHeight();
    int rawLineIndexForYpos(int y);
    int lineIndexForYpos(int y);
    int totalWidth();
    int totalHeight();
    int emWidth();
    int nrWidth();
    int viewHeightInLines();
    int firstVisibleLine();
    int columnIndexForXpos(int line, int x);
    int xPosForColumn(int line, int column);
    int xPosForOffset(int offset);
    int yPosForLine(int line);
    int yPosForOffset(int offset);
    QTextLayout *textLayoutForLine(int line);
    void renderBegin(const QRect &rect);
    void renderEnd(const QRect &rect);
    TextDocument *textDocument();
    TextEditorController *controller();
    TextEditorWidget *textWidget();
    void setViewport(const QRect &viewport);
    void resetCaretTime();
    bool shouldRenderCaret();
    bool isCaretVisible();
    void setCaretVisible(bool visible);
    QRect viewport();
    int viewportX();
    int viewportY();
    int viewportWidth();
    int viewportHeight();
    QString themeName() const;
    TextTheme *theme();
    void setThemeByName(const QString &name);
    void setTheme(TextTheme *theme);
    const QRect *clipRect();
    int startOffset();
    int endOffset();
    int startLine();
    int endLine();

private:
    void updateWidthCacheForRange(int offset, int length);

public slots:
    void invalidateTextLayoutCaches(int fromLine = 0);
    void invalidateCaches();

protected slots:
    void textDocumentChanged(edbee::TextDocument *oldDocument, edbee::TextDocument *newDocument);
    void lastScopedOffsetChanged(int previousOffset, int newOffset);

signals:
    void themeChanged(TextTheme *theme);
};

// -----------------------------------------------------------------------------


// -------------------------edbee/models/textdocument.h-------------------------
class TextDocument : public QObject {
    
%TypeHeaderCode
#include "edbee/models/textdocument.h"
%End

public:
    TextDocument(QObject *parent / TransferThis / = 0);
    virtual ~TextDocument();
    virtual TextBuffer *buffer() const = 0;
    virtual void setLineDataFieldsPerLine(int count);
    virtual TextLineDataManager *lineDataManager() = 0;
    virtual void giveLineData(int line, int field, TextLineData *dataItem);
    virtual TextLineData *getLineData(int line, int field);
    virtual TextDocumentScopes *scopes() = 0;
    virtual TextCodec *encoding() = 0;
    virtual void setEncoding(TextCodec *codec) = 0;
    virtual const LineEnding *lineEnding() = 0;
    virtual void setLineEnding(const LineEnding *lineENding) = 0;
    virtual TextLexer *textLexer() = 0;
    virtual TextGrammar *languageGrammar() = 0;
    virtual void setLanguageGrammar(TextGrammar *grammar) = 0;
    virtual TextAutoCompleteProviderList *autoCompleteProviderList() = 0;
    virtual TextUndoStack *textUndoStack() = 0;
    virtual void beginUndoGroup(ChangeGroup *group = 0);
    virtual void endUndoGroup(int coalesceId, bool flatten = false);
    virtual void endUndoGroupAndDiscard();
    virtual bool isUndoCollectionEnabled();
    virtual void setUndoCollectionEnabled(bool enabled);
    virtual bool isUndoRunning();
    virtual bool isRedoRunning();
    virtual bool isUndoOrRedoRunning();
    virtual bool isPersisted();
    virtual void setPersisted(bool enabled = true);
    virtual TextEditorConfig *config() const = 0;
    virtual void setDocumentFilter(TextDocumentFilter *filter);
    virtual void giveDocumentFilter(TextDocumentFilter *filter);
    virtual TextDocumentFilter *documentFilter();
    void beginChanges(TextEditorController *controller);
    void replaceRangeSet(TextRangeSet &rangeSet, const QString &text);
    void replaceRangeSet(TextRangeSet &rangeSet, const QStringList &texts);
    void giveSelection(TextEditorController *controller, TextRangeSet *rangeSet);
    void endChanges(int coalesceId);
    Change *executeAndGiveChange(Change *change, int coalesceId);
    virtual Change *giveChangeWithoutFilter(Change *change, int coalesceId) = 0;
    void append(const QString &text, int coalesceId = 0);
    void replace(int offset, int length, const QString &text, int coalesceId = 0);
    void setText(const QString &text);
    void rawAppendBegin();
    void rawAppendEnd();
    void rawAppend(QChar c);
    void rawAppend(const QChar *chars, int length);
    int length();
    int lineCount();
    QChar charAt(int idx);
    QChar charAtOrNull(int idx);
    int offsetFromLine(int line);
    int lineFromOffset(int offset);
    int columnFromOffsetAndLine(int offset, int line = -1);
    int offsetFromLineAndColumn(int line, int column);
    int lineLength(int line);
    int lineLengthWithoutNewline(int line);
    QString text();
    QString textPart(int offset, int length);
    QString lineWithoutNewline(int line);
    QString line(int line);

signals:
    void persistedChanged(bool persisted);
    void languageGrammarChanged();
    void lastScopedOffsetChanged(int previousOffset, int lastScopedOffset);
};

// -----------------------------------------------------------------------------


// --------------------------------edbee/edbee.h--------------------------------
class Edbee : public QObject {
    
%TypeHeaderCode
#include "edbee/edbee.h"
%End

public:
    static Edbee *instance();
    void setKeyMapPath(const QString &keyMapPath);
    void setGrammarPath(const QString &grammarPath);
    void setThemePath(const QString &themePath);
    void autoInit();
    TextGrammarManager *grammarManager();
    TextThemeManager *themeManager();

private:
    Edbee();
    virtual ~Edbee();

public slots:
    void init();
    void shutdown();
    void autoShutDownOnAppExit();
};

// -----------------------------------------------------------------------------


// ---------------------------edbee/texteditorwidget.h--------------------------
class TextEditorWidget : public QWidget {
    
%TypeHeaderCode
#include "edbee/texteditorwidget.h"
%End

public:
    explicit TextEditorWidget(QWidget *parent / TransferThis / = 0);
    virtual ~TextEditorWidget();
    void scrollPositionVisible(int xPosIn, int yPosIn);
    TextEditorController *controller() const;
    TextDocument *textDocument() const;
    TextRenderer *textRenderer() const;
    void resetCaretTime();
    void fullUpdate();
    QScrollBar *horizontalScrollBar() const;
    QScrollBar *verticalScrollBar() const;
    void setVerticalScrollBar(QScrollBar *scrollBar);
    void setHorizontalScrollBar(QScrollBar *scrollBar);

protected:
    virtual void resizeEvent(QResizeEvent *event);
    bool eventFilter(QObject *obj, QEvent *event);

public slots:
    void scrollTopToLine(int line);
    virtual void updateLineAtOffset(int offset);
    virtual void updateAreaAroundOffset(int offset, int width = 8);
    virtual void updateLine(int line, int length = 1);
    virtual void updateComponents();
    virtual void updateGeometryComponents();
    virtual void updateRendererViewport();

protected slots:
    void connectVerticalScrollBar();
    void connectHorizontalScrollBar();

signals:
    void focusIn(QWidget *event);
    void focusOut(QWidget *event);
    void verticalScrollBarChanged(QScrollBar *newScrollBar);
    void horizontalScrollBarChanged(QScrollBar *newScrollBar);
};

// -----------------------------------------------------------------------------


// ------------------------------edbee/test_dummy.h-----------------------------
class TestDummy {
    
%TypeHeaderCode
#include "edbee/test_dummy.h"
%End

public:
    void test_replace();
};

// -----------------------------------------------------------------------------



};

Anyway, I wish the best to this project... I'll be checking it out here and there to see how it evolves. If eventually becomes fully feature-complete maybe I'll be back ;)

To be fair, amongst the dozen of options I've explored this one was the most promising but the fact Rick is busy with other more important projects and the pace is quite low made me to decide to choose another suboptimal choices.

Ps. I'd said few days ago I was going to post some links, here we go:

Mostly of the concepts teached by Lakos can be applied not just in the context of c++ but also with other different languages, really interesting stuff

\o

brupelo avatar Mar 20 '19 09:03 brupelo

@gamecreature Argh, I've added a lot of offtopic/noise for this particular thread, sorry about it. Anyway, how hard do you think it'd be to address this one? You'd mentioned in the past that:

To do this, I need to remember the index of the caret has been added with the Ctrl+Click. This can be tricky with overlapping ranges which get merged... So that still is a WIP

Does this code https://github.com/codemirror/CodeMirror/blob/master/src/model/selection.js help you to think about an easy fix for this particular one?

brupelo avatar Jan 16 '21 22:01 brupelo