Scite top/bottom indent level locator feature?

124 views
Skip to first unread message

Ric Borg

unread,
Apr 9, 2013, 7:03:39 PM4/9/13
to scite-i...@googlegroups.com
Hi all.
I'm looking for a feature which will take me to the top/bottom line of
content at a particular indent level. Similar to CTRL-E taking you to
matched brackets/braces, but this feature looks to indent levels
and any non-whitespace characters.

Example:

function foo() {
function bar() {
//(some code here...)

/*
Now imagine I used this feature at the beginning of any of these
lines (column 9) - it would take me to the "function bar()" line.
(Or if SHIFT is also pressed, it goes to the bottom - in this case
it can only fallback to the closing brace of bar().)
*/

//(more code here...)
}
}

The idea is as a general purpose editing assistant, which would be especially
useful e.g. when working within large nested blocks/functions (which
may be within a large function), to find the start/end at any
particular indentation level.

I don't know if it has this already, or if it would be considered for
addition? It's something we often need, and figure it would be
generally useful to any programmers.

Regards,
Ric.

steve donovan

unread,
Apr 10, 2013, 2:55:30 AM4/10/13
to scite-i...@googlegroups.com
On Wed, Apr 10, 2013 at 1:03 AM, Ric Borg <ad...@metaseller.com> wrote:
I'm looking for a feature which will take me to the top/bottom line of
content at a particular indent level. Similar to CTRL-E taking you to
matched brackets/braces, but this feature looks to indent levels
and any non-whitespace characters.

That would be a consistent extension of CTRL-E's functionality, particularly for whitespace-indent-sensitive languages (and there's an increasing number of them apart from Python)

Neil Hodgson

unread,
Apr 10, 2013, 6:45:23 AM4/10/13
to scite-i...@googlegroups.com
Indent levels can be messy due to differences between physical and logical indents. With _ indicating space, this sequence

____f()
__
____g()

is probably all on one logical indent level. Also this

____f(1, \
"argument 2")
____g()

You can try to use Scintilla's fold levels but be there may also be some complexity there with whitespace or comment lines treated differently.

It may be an idea to write these commands in the container (for SciTE, as Lua macros) to experiment with the behaviour before adding it to Scintilla.

Neil

Ric Borg

unread,
Apr 10, 2013, 7:55:44 AM4/10/13
to scite-i...@googlegroups.com
Hi again. Sorry, there's a mistake in my wording. Where I wrote
"column 9", I meant to write "column 5" (of the comment lines), and
what I mean to illustrate here (which hopefully makes more sense), is
that it would take you to the top/bottom of the indent level that the
cursor is currently on where it first finds some non-whitespace
characters.

Put another way, suppose you press CTRL-E anywhere within a (maybe
nested) block, and your cursor is not on a brace/bracket right now,
but rather somewhere within the indent(s), or even back at column 1 -
currently CTRL-E would do nothing; but with this alternate
functionality, it would take you to the top or bottom of the matching
block indent - almost the equivalent to locating the braces/brackets
for you of the relevant indent level.

So in the given example, if you're one indent in, it would locate
"function bar()" (or its closing brace), or if no indents in, then it
would locate "function foo()" (or its closing brace).

For this very reason, yes it could potentially integrate into the
CTRL-E functionality nicely for me personally - the described
alternate mode triggering only if you're not on a brace/bracket at the
time, however if it's on its own shortcut-key(s), that too would be
fine. I wouldn't mind either way.

(Incidentally, we actually only work with tabs in our source-code
(JavaScript, PHP, Pike, C++, SQL, shell-scripts, etc - all edited in
Scite :-) - not spaces; I only put spaces in this example considering
it's going over email. As far as Scite/Scintilla is concerned, I don't
know if this makes any difference or is any easier for it to figure
things out or not, but just in case it does.)

Thanks. Regards,
Ric.

Ric Borg

unread,
Apr 11, 2013, 8:24:33 PM4/11/13
to scite-i...@googlegroups.com
On Wed, Apr 10, 2013 at 8:45 PM, Neil Hodgson <nyama...@me.com> wrote:
> Indent levels can be messy due to differences between physical and logical indents. With _ indicating space, this sequence
>
> ____f()
> __
> ____g()
>
> is probably all on one logical indent level.

Yep, or its more common cousin the entirely-blank line.

> Also this
>
> ____f(1, \
> "argument 2")
> ____g()

I'd let this one break the searcher (it'd stop on the "argument 2"
line). Keep it simple: As soon as a line is found which has a
non-blank character at or prior to the cursor's current indentation
level, that's the line to stop on. It mightn't be perfect for all
situations (like your second example), but it's clear and simple to
explain.

Incidentally, the example you give I would indent:

____f(1, \
______"argument 2")
____g()

But with multi-line literal strings, the exact same thing can happen:

____echo 'Description: "Demo Upstart job"
start on net-device-up
exec /usr/local/bin/foo --bar
expect stop
" >/etc/init/foobar.conf
____start foobar

Indenting the intermediate lines would indent the contents of the
file. I wouldn't consider this sufficiently common or critical to
damage the descriptive clarity of "scan up/down till you find
something in this column".

Chris Angelico
(the other Meta Seller dev - also been on this list for personal
reasons, as ros...@gmail.com)

Meta Seller Admin

unread,
Apr 15, 2013, 9:55:16 PM4/15/13
to scite-i...@googlegroups.com
Is anyone interested in adding this?
(I use Scite, but I don't know how to add to or extend its features,
and am too busy with other projects to go investigating at this time.)
Regards,
Ric.

Neil Hodgson

unread,
Apr 17, 2013, 8:04:54 PM4/17/13
to scite-i...@googlegroups.com
With this attached to Match Brace, I would have expected the caret to move to the brace responsible for that indent level instead of the start of the line.

Neil

Chris Angelico

unread,
Apr 17, 2013, 9:30:43 PM4/17/13
to scite-i...@googlegroups.com
On Thu, Apr 18, 2013 at 10:04 AM, Neil Hodgson <nyama...@me.com> wrote:
> With this attached to Match Brace, I would have expected the caret to move to the brace responsible for that indent level instead of the start of the line.

Yes, that was considered. However, that depends on there actually
_being_ a brace; defining the functionality purely on indentation
means it works cleanly for all languages.

If the Allman style is used, the cursor will be on the brace. If OTBS,
chances are you can just hit End to go to the brace. But if it's
Python, or if there's something else at the indentation level you're
scanning to, then there's no brace to find.

ChrisA

Meta Seller Admin

unread,
Apr 17, 2013, 10:43:02 PM4/17/13
to scite-i...@googlegroups.com
Nice, thanks Chris.

Regarding the matching of brace/bracket: how about adding this (which
should hopefully cover most other use cases):
If after pressing CTRL-E, your backup handling code triggers, then it
does exactly what it does now, with one further/final check:
* Is the cursor *now* on an opening bracket/brace? If so, then done
(current behavior already).
* If not, then look for the last opening bracket/brace on the line
that it's now on (perhaps optionally - to be thorough - one that isn't
within a trailing comment), and move the cursor to its position.
* If the above doesn't find an opening bracket/brace, then leave the
cursor at the beginning of the line - done.

If the followup worked, then further pressing of CTRL-E will then
automatically revert to the original CTRL-E handler.
This will then also handle typical cases of e.g.:
function name(p1, foo(), 'ex') {/*...*/} //Matches the opening brace here.
if ( //Matches this opening bracket.
a == b &&
c !== d &&
//bunch of other conditions...
) ...; //...

I can foresee some cases where this won't find the "right" opening
bracket/brace for that indentation level, and trying to handle
everything would be too much trouble (or not work for everything
anyway due the variety of languages to be dealt with), but if the
simple cases - which are likely the majority anyway - were to all
work, then those that don't, shouldn't be a problem.

Either way - with or without this further extension - I'll make very
good use of it. :-)

Cheers,
Ric.


On Thu, Apr 18, 2013 at 4:18 AM, Rosuav <ros...@gmail.com> wrote:
> On Tuesday, 16 April 2013 11:55:16 UTC+10, Ric Borg wrote:
>> Is anyone interested in adding this?
>> (I use Scite, but I don't know how to add to or extend its features,
>> and am too busy with other projects to go investigating at this time.)
>
> After some discussion about the matter, I told Ric (my boss at work) that there was a reasonable chance that I might just up and do this, at home, as an excuse to figure out how much I can do with Lua in SciTE. Turns out that happened, except for the part about Lua... the Ctrl-E handler is written in C++, so that's where the edit is.
>
> I'm not sure how to attach files in Google Groups, so I may have to send a separate list email later; in any case, the patch is a fairly simple insertion of code, no external edits required. Alternatively, the patch is here: http://rosuav.com/SciTE_ctrl_e.patch
>
> The concept is as follows: When Ctrl-E is pressed (IDM_MATCHBRACE / GoMatchingBrace() - same thing happens with IDM_SELECTTOBRACE), if there's no brace/bracket to match, the current cursor column is recorded. It then scans upward through the buffer until it finds a line whose indent is less than the column we're scanning AND has content in it, and puts the cursor on that line.
>
> The original theory of holding Shift to move downward conflicts conceptually with Ctrl-Shift-E to select the region, and also doesn't play so nicely with Python, so I didn't implement that. It'd be easy enough to do, though (just change "--line;" to "if (select) ++line; else --line;"), if anyone thinks it'd be better that way.
>
> Ric, I'll apply the patch to the build we use on Friday, so you can try this out.
>
> Chris Angelico
>
> --
> You received this message because you are subscribed to the Google Groups "scite-interest" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to scite-interes...@googlegroups.com.
> To post to this group, send email to scite-i...@googlegroups.com.
> Visit this group at http://groups.google.com/group/scite-interest?hl=en.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Neil Hodgson

unread,
Apr 19, 2013, 7:36:40 AM4/19/13
to scite-i...@googlegroups.com
Ric:

> * If the above doesn't find an opening bracket/brace, then leave the
> cursor at the beginning of the line - done.

The patch places the caret at the equivalent indent of the caret before the command instead of at the start of the destination line. Try with space-based indentation and a starting point that is not on an indent guide. Don't know if this is intentional.

While I can see this being similar to brace matching for Python, for bracey languages I'm not sure it will be easily understood or discovered. Maybe it just needs a more accurate name than "Match Brace" or be a separate command. If the caret is inaccurately placed one space before a '{' (where the '{' is the only code on a line) then the command moves the caret in the opposite direction to the user's intention.

Neil

Meta Seller Admin

unread,
Apr 28, 2013, 9:54:59 PM4/28/13
to scite-i...@googlegroups.com
Hi, back at work after a week at theatre :)

Concern understood. Hmm.

What I could do here is to reject the movement request (and,
therefore, behaving as current code does - Ctrl-E would do nothing,
just leaving the cursor there) if there is no line that has the actual
indent of the cursor. For instance:

void init()
{
//Four spaces here
if (1 == 0)
{
//Eight spaces here
abort("The universe is broken!");
}
}

Place the cursor just before the brace after the if (three spaces in,
then the cursor, then another space and the brace). then hit Ctrl-E.
In trunk, nothing will happen (cursor not on a brace). With my current
patch, the cursor will be placed on the brace just after init. What I
could do here is to reject this, because there is no line at
three-spaces of indent. This would then guarantee that the cursor, if
moved, would end up at the beginning of a logical line (that is, on
the first non-indent character).

The same would apply if the target line is longer than expected.
Switch the above code to OTBS:

void init() {
//Four spaces here
if (1 == 0) {
//Eight spaces here
abort("The universe is broken!");
}
}

Cursor three spaces in, again. Since the "void init() {" line is not
at exactly three spaces, the search will be aborted and the cursor
left where it is.

Would this make the code make more sense?

Chris Angelico

Neil Hodgson

unread,
Apr 30, 2013, 12:56:56 AM4/30/13
to scite-i...@googlegroups.com
Chris:

What I could do here is to reject the movement request (and,
therefore, behaving as current code does - Ctrl-E would do nothing,
just leaving the cursor there) if there is no line that has the actual
indent of the cursor.
 
   Seems better.

   Preprocessor conditional up/down (Ctrl+J/Ctrl+K) may to be a better model for this than match brace (Ctrl+E) since there is an explicit direction in the preprocessor movement commands.

   So that others can experiment without needing to rebuild SciTE here's a translation of the original patch as a Lua script:

function MoveToIndentStart()
    local caretPos = scite.SendEditor(SCI_GETCURRENTPOS, 0, 0)
    local column = scite.SendEditor(SCI_GETCOLUMN, caretPos, 0)
    local line = scite.SendEditor(SCI_LINEFROMPOSITION, caretPos)
    -- Find a line that has at most as much indent as the cursor column, but skip it if it has no content.
    -- If none found, move to top of file.
    while (true) do
        local pos = scite.SendEditor(SCI_FINDCOLUMN, line, column)
        local indentPos = scite.SendEditor(SCI_GETLINEINDENTPOSITION, line, 0)
        local lineEnd = scite.SendEditor(SCI_GETLINEENDPOSITION, line, 0)
        if (line <= 0) or (indentPos <= pos and indentPos < lineEnd) then
            -- Note: Currently ignores the 'select' flag; it doesn't make a lot of sense to select
            -- from the cursor up (thus getting only half the block).
            scite.SendEditor(SCI_SETSEL, pos, pos)
            break
        end
        line = line-1
    end
end

   Neil

Meta Seller Admin

unread,
Apr 30, 2013, 1:25:58 AM4/30/13
to scite-i...@googlegroups.com
On Tue, Apr 30, 2013 at 2:56 PM, Neil Hodgson <nyama...@me.com> wrote:
> Chris:
>
> What I could do here is to reject the movement request (and,
> therefore, behaving as current code does - Ctrl-E would do nothing,
> just leaving the cursor there) if there is no line that has the actual
> indent of the cursor.
>
>
> Seems better.
>
> Preprocessor conditional up/down (Ctrl+J/Ctrl+K) may to be a better model
> for this than match brace (Ctrl+E) since there is an explicit direction in
> the preprocessor movement commands.

Hmm. Kinda similar-ish, but not exactly the same. I don't think
indent-matching ties in well with preprocessor matching, because it's
possible to be nested at different levels - currently Ctrl-J/K works
only with the innermost #if block (at least, that's all I was able to
do in a quick test; I don't often use nested #ifs, so my test may have
been too contrived), whereas indent-movement needs to be able to move
to any of the current line's indent levels.

Here's how I see it. When you indent as far as one of the specified
stops (eg if you use tabs), and you place the cursor on the open or
close bracket, a blue vertical line connects the two brackets. (I'm
using the default color scheme and the C lexer here; the exact details
may differ a bit, but hopefully you'll understand what I'm referring
to.) This new feature effectively lets you put the cursor anywhere on
that blue line and then jump to the top of the corresponding block.
Conceptually, it could theoretically put the cursor actually _on_ the
brace/parenthesis/whatever, but that would introduce corner conditions
that would be messy:

* What happens if there are multiple mis-nested brackets? For
instance, open ( and { on one line, then indent another tab, then
close ) without closing } - which character should the cursor be put
on?

* What about Python?

* What about plain text? By this I mean something that's using the
plain text lexer; it could be a config file, it could be source code,
it could be anything, but to SciTE it's just plain text.

That's why I'm inclined to keep the cursor in the same column during
the movement. It's perfect for certain styles (eg Allman),
near-perfect for other styles (eg OTBS), but comprehensible for
everything. Maybe it needs to be put onto a different key, to avoid
the confusion with brace matching, but there is a similarity and
parallel, and this is a situation where the brace matcher can't do
anything anyway. Also, by looking _only_ at indentation, the code is
completely lexer-independent; it's just looking at the text, not at
its styles. Saves a lot of potential debugging, I think.

So I suppose the biggest question remaining is: If not Ctrl-E, then
what key? Is there one that "makes sense" and is available?

Chris Angelico

Neil Hodgson

unread,
May 5, 2013, 8:02:31 AM5/5/13
to scite-i...@googlegroups.com
Meta Seller Admin:

> Hmm. Kinda similar-ish, but not exactly the same. I don't think
> indent-matching ties in well with preprocessor matching, because it's
> possible to be nested at different levels - currently Ctrl-J/K works
> only

I didn't want to extend Ctrl+J/K (indeed, its the proposal's extension of an existing command that I find troubling) but to point out that it's symmetric and extends to selection easily whereas the Ctrl+E functionality proposed only handles one direction and does not handle selecting the block. If its likely that selection and both up and down are wanted then that should be looked at before integrating as the allocated key may not make sense in a pair or augmented for selection.

> Here's how I see it. When you indent as far as one of the specified
> stops (eg if you use tabs), and you place the cursor on the open or
> close bracket, a blue vertical line connects the two brackets. (I'm
> using the default color scheme and the C lexer here; the exact details
> may differ a bit, but hopefully you'll understand what I'm referring
> to.) This new feature effectively lets you put the cursor anywhere on
> that blue line and then jump to the top of the corresponding block.

If the guide line blued when the caret was on it then the feature may be more discoverable.

> Conceptually, it could theoretically put the cursor actually _on_ the
> brace/parenthesis/whatever, but that would introduce corner conditions
> that would be messy:
>
> * What happens if there are multiple mis-nested brackets? For
> instance, open ( and { on one line, then indent another tab, then
> close ) without closing } - which character should the cursor be put
> on?

The brace that turns the line blue.

> * What about Python?

The colon turns the line blue.

> * What about plain text? By this I mean something that's using the
> plain text lexer; it could be a config file, it could be source code,
> it could be anything, but to SciTE it's just plain text.

You'll get fewer features if SciTE doesn't understand the file enough to style it.

> So I suppose the biggest question remaining is: If not Ctrl-E, then
> what key? Is there one that "makes sense" and is available?

Keys can be mapped by the user using user.shortcuts - it may be enough to make it available as a command or commands. In its current form I'd probably assign Ctrl+^, that is Ctrl+Shift+6 as that has some mnemonic power for moving up.

Neil

Meta Seller Admin

unread,
May 6, 2013, 9:00:51 PM5/6/13
to scite-i...@googlegroups.com
On Sun, May 5, 2013 at 10:02 PM, Neil Hodgson <nyama...@me.com> wrote:
> Keys can be mapped by the user using user.shortcuts - it may be enough to make it available as a command or commands. In its current form I'd probably assign Ctrl+^, that is Ctrl+Shift+6 as that has some mnemonic power for moving up.

This is looking like the most promising way forward. Taking the Lua
port you posted earlier and putting it into SciTEStartup.lua and
adding this to my .SciTEUser.properties:

command.name.6.*=Move to indent start
command.subsystem.6.*=3
command.6.*=MoveToIndentStart
command.mode.6.*=savebefore:no,quiet:yes

does most of what I'm looking for. It's bound to Ctrl+6 instead of
Ctrl+Shift+6 though, and no amount of
"command.shortcut.6.*=Ctrl+Shift+6" is changing that. Also, the action
clears the output pane; is there a way to say "hey, this is just
cursor movement, it won't need output and shouldn't abort an active
command"? I poked around with user.shortcuts but the best I can do
there is to trigger an existing command, with all the same semantics.

That's one huge advantage the C version has over this one - it's
treated just as cursor movement. I'd really like to replicate that.

Chris Angelico

Neil Hodgson

unread,
May 9, 2013, 1:01:17 AM5/9/13
to scite-i...@googlegroups.com
Chris:

> command.name.6.*=Move to indent start
> command.subsystem.6.*=3
> command.6.*=MoveToIndentStart
> command.mode.6.*=savebefore:no,quiet:yes
>
> does most of what I'm looking for. It's bound to Ctrl+6 instead of
> Ctrl+Shift+6 though, and no amount of
> "command.shortcut.6.*=Ctrl+Shift+6" is changing that.

command.20.*=MoveToIndentStart()
command.subsystem.20.*=3
command.save.before.20.*=2
user.shortcuts=Ctrl+Shift+6|1120|

Then restart since user.shortcuts is only read at startup. 1100 is the start of the Tools menu commands and not having a name means that there is no menu item visible.

> Also, the action
> clears the output pane; is there a way to say "hey, this is just
> cursor movement, it won't need output and shouldn't abort an active
> command"?

That depends on how you have command execution set up but unfortunately, there is no way of saying that it shouldn't use the output pane. Access to the output pane is serialized for all sources of commands so you can't run while that long compile is running. Yes, should be fixed.

> That's one huge advantage the C version has over this one - it's
> treated just as cursor movement. I'd really like to replicate that.

Commands can be added in C without defining that they are attached to a particular key. Assign an IDM_ command number in SciTE.h (#define IDM_XXX 270); implement it in SciTEBase::MenuCommand (case IDM_XXX:); run scripts/IFaceTableGen.py so that "IDM_XXX" -> IDM_XXX.

You can then add the command to your favourite key:
user.shortcuts=Ctrl+Shift+6|IDM_XXX|

Neil

Neil Hodgson

unread,
May 9, 2013, 1:14:31 AM5/9/13
to scite-i...@googlegroups.com
Chris:

> does most of what I'm looking for. It's bound to Ctrl+6 instead of
> Ctrl+Shift+6 though, and no amount of
> "command.shortcut.6.*=Ctrl+Shift+6" is changing that.

That's working for me on Windows 7.

Neil

Neil Hodgson

unread,
May 23, 2013, 2:47:40 AM5/23/13
to scite-i...@googlegroups.com
Me:
> Chris:
>
>> Also, the action
>> clears the output pane; is there a way to say "hey, this is just
>> cursor movement, it won't need output and shouldn't abort an active
>> command"?
>
> That depends on how you have command execution set up but unfortunately, there is no way of saying that it shouldn't use the output pane. Access to the output pane is serialized for all sources of commands so you can't run while that long compile is running. Yes, should be fixed.

To allow lighter commands to be implemented in Lua and not have them serialised in the execution queue, a new command subsystem "immediate" (7) is implemented in the attached patch. This allows settings like:
command.mode.8.*=subsystem:immediate
or
command.subsystem.6.*.cxx=7

The patch is a bit large as I moved the implementation into the JobQueue module and rewrote it.

Not much attention was paid to the set of options that can be applied to immediate jobs - it appears that save.before and is.filter are applied and others aren't in the patch.

Neil
immediate.patch

Neil Hodgson

unread,
Jun 5, 2013, 5:08:24 AM6/5/13
to scite-i...@googlegroups.com
Me:

> To allow lighter commands to be implemented in Lua and not have them serialised in the execution queue, a new command subsystem "immediate" (7) is implemented in the attached patch. This allows settings like:
> command.mode.8.*=subsystem:immediate
> or
> command.subsystem.6.*.cxx=7

This feature has now been committed.

Neil

Reply all
Reply to author
Forward
0 new messages