Tabs im Editor sind recht nützlich, um das Skript schön strukturiert darzustellen.
Aber genaugenommen interessieren uns dabei nicht die Tabs selbst, sondern die Funktionalität fehlende Zeichen bis zur nächsten Tabposition aufzufüllen. Daraus resultiert aber auch der (m.M.n.) große Nachteil von Tabs: Obwohl sie nur ein Zeichen darstellen, können sie den Platz von 1...tab.size Zeichen einnehmen.
Z.B. hat man etwas geschrieben, was mit (nicht vollem) Tab ausgerichtet wurde. Später ergänzt man vor dem Tab noch etwas und möchte von der Tabposition rückwärts eine Tabweite löschen. Der Tab hat nun evtl. nur noch 1 Zeichen Breite und ich muss also den Tab und anschliessend die davor befindlichen Leerzeichen extra löschen. Das ganze Tab-Handling gefällt mir persönlich nicht.
SciTE bietet die Möglichkeit, statt des Tabs Leerzeichen einzufügen. Dazu muss die Property use.tabs=0 gesetzt sein.
Es werden dann statt eines Tabs die Leerzeichen bis zur nächsten Tabposition eingefügt. Optisch ist das Verhalten soweit analog. Der Vorteil von Leerzeichen ist auch, dass die Darstellung in unterschiedlichen Editoren identisch ist. Man sieht ja schon, dass bei Skriptdarstellung hier im Forum die Formatierung ziemlich zerrissen wird, wenn Textbereiche enthalten sind.
Wer also auf Leerzeichen statt Tabs umsatteln möchte, dem kann ich hiermit noch weitere Funktionalität mitgeben:
- Löschen aller Zeichen vom Cursor bis zur nächsten/vorigen Tabposition in der aktuellen Zeile <Shift+Delete> / <Shift+Backspace>
- Löschen aller Zeichen bei Rectangular Selection bis zur nächsten/vorigen Tabposition in allen ausgewählten Zeilen. Die Selection bleibt nach der Operation erhalten, sodass Folgeoperationen über den Markierungsbereich möglich sind. <Shift+Delete> / <Shift+Backspace> **nur gültig, wenn kein Text markiert ist
- Springen zur nächsten/vorigen Tabposition in der aktuellen Zeile <Alt+Arrow_Right> / <Alt+Arrow_Left>
Die Datei über die SciTEStartup.lua laden. Erforderlich ist auch die CommonTools.lua
EDIT zu TabReplace
Ich habe noch ergänzt um ein Lua-Skript, mit dem in der aktuell in SciTE geöffneten Datei on-the-fly die Tab-Ersetzung durchgeführt wird.
- TabReplaceSciTE.lua abspeichern
- in der SciTEUser.properties einem Shortcut zuweisen:
command.name.20.*=Replace TAB in current file
command.20.*=dofile ("DEIN_PFAD/TabReplaceSciTE.lua")
command.mode.20.*=subsystem:lua,savebefore:no
command.shortcut.20.*=Ctrl+Alt+F10
Die Angaben in ROT anpassen! - Als Standard wird TAB-Size=4 verwendet. Falls andere Größe erforderlich, muss in der TabReplaceSciTE.lua die letzte Zeile angepasst werden:
TabReplace_FileInSciTE(4) -- If required: Change the TAB size here
EDIT
Als Ergänzung (AutoIt- und Lua-Skript):
In vorhandenen Dateien alle TAB durch die der Position (und der vorgegebenen TAB-Weite) entsprechende Anzahl von Leerzeichen ersetzen.
Dazu muss zeilenweise durch die Datei iteriert werden, da der Zeilenanfang als "Position 0" zur Berechnung der TAB-Positionen erforderlich ist.
Mein Grundgedanke war, in AutoIt die Datei mit _FileReadToArray einlesen, durchiterieren und ggf. TAB ersetzen, mit _FileWriteFromArray zurückschreiben. Aber _FileWriteFromArray hat den Dateiinhalt verändert, plötzlich stimmten bei einigen Zeilen die eingefügten Ersetzungsstrings nicht mehr, obwohl diese korrekt berechnet und gespeichert wurden. Somit stelle ich nun den Schreibstring aus dem Array selbst zusammen - und es geht.
Aber auch die Lua-Version (die hatte ich zuerst erstellt) hat mich Schweiß gekostet. Das zeilenweise Einlesen einer Datei scheint zu einer Manipulation des Zeilenendes zu führen, was wiederum zu falschen Tabpositionen und somit falscher Länge der Ersetzungsstrings führte. Beim Zusammenstellen des Schreibstrings aus den eingelesenen Zeilen wurden MAC-Zeilenumbrüche (CR) eingefügt, gleich zwei hintereinander. Aber das in Kombination mit meinem angehängten CRLF führte dann wenigstens zur korrekten Ersetzung der TAB. Die CR-CR zu entfernen war dann das kleinste Übel.
TabReplace
;-- TIME_STAMP 2022-03-16 12:36:59 v 0.1
#include <File.au3>
;===================================================================================================
; Function Name....: _TabReplace_File
; Description......: Replaces TAB in all lines of the file with the number of spaces corresponding to the columns.
; .................: Overwrites the file with the replaced content.
; Parameter(s).....: $_sPath The files path.
; .................: $_iMode The files mode. Default is UTF8-with-BOM(128).
; .................: $_iTabsize TAB size in number of characters. If it is omitted, 4 is used.
; Return Value(s)..: Count of replacements
; Author...........: BugFix (autoit<at>bug-fix.info)
;===================================================================================================
Func _TabReplace_File($_sPath, $_iMode=128, $_iTabsize=4)
If Not FileExists($_sPath) Then Return SetError(1,0,0)
Local $fh = FileOpen($_sPath, $_iMode)
Local $aFile, $iReplacements = 0, $sWrite, $sLine
_FileReadToArray($fh, $aFile, 0)
FileClose($fh)
For $i = 0 To UBound($aFile) -1
$sLine = $aFile[$i]
$iReplacements += _TabReplace_Line($sLine, $_iTabsize)
$sWrite &= $sLine & @CRLF
Next
If $iReplacements = 0 Then Return 0
Local $fh = FileOpen($_sPath, $_iMode + 2)
FileWrite($fh, $sWrite)
FileClose($fh)
Return $iReplacements
EndFunc ;==>_TabReplace_File
;===================================================================================================
; Function Name....: _TabReplace_Line
; Description......: Replaces TAB in a row (ByRef) with the number of spaces corresponding to the column.
; Parameter(s).....: $_line A line of text whose TAB are to be replaced by spaces.
; .................: $_iTabsize TAB size in number of characters. If it is omitted, 4 is used.
; Return Value(s)..: Count of replacements
; Author...........: BugFix (autoit<at>bug-fix.info)
;===================================================================================================
Func _TabReplace_Line(ByRef $_line, $_iTabsize=4)
If StringRegExp($_line, '^[\r\n]+$') Then Return 0
If $_line = '' Then Return 0
StringRegExp($_line, '\t', 1)
Local $posTab = @extended -1
If $posTab = -1 Then Return 0
Local $aTab[50]
Local $sRepl = '', $iLen = 0, $sumLen = 0, $index = -1
Local $aRepl[] = ['',' ',' ',' ',' ']
While $posTab <> -1
$index += 1
If $index = UBound($aTab) Then ReDim $aTab[UBound($aTab)+50]
$iLen = ($_iTabsize - Mod(($posTab + $sumLen -1), $_iTabsize))
$sumLen += $iLen -1
$sRepl = $aRepl[$iLen]
$aTab[$index] = $sRepl
StringRegExp($_line, '\t', 1, $posTab +1)
$posTab = @extended -1
WEnd
; Ersetzung
For $i = 0 To $index
$_line = StringRegExpReplace($_line, '\t', $aTab[$i], 1)
Next
Return $index +1
EndFunc ;==>_TabReplace_Line
Alles anzeigen
-- TIME_STAMP 2022-03-16 12:37:55 v 0.1
----------------------------------------------------------------------------------------------------
--[[
in...: _line A line of text whose TAB are to be replaced by spaces.
.....: _tabsize TAB size in number of characters. If it is omitted, 4 is used.
out..: The line, with TAB replaced if necessary, and the number of replacements.
]]
----------------------------------------------------------------------------------------------------
TabReplace_Line = function(_line, _tabsize)
if _line:find('^[\r\n]+$') then return _line, 0 end -- only a line break
if _line == '' then return _line, 0 end -- only a empty string
local posTab = _line:find('\t')
if posTab == nil then return _line, 0 end -- no TAB included
_tabsize = _tabsize or 4 -- default TAB width
local tTab, s, sRep, iLen, sumLen = {}, ' ', '', 0, 0
while posTab ~= nil do
-- calculation replacement string, taking into account characters to be inserted
iLen = (_tabsize - ((posTab + sumLen -1) % _tabsize))
sumLen = sumLen + iLen -1 -- total length of the replacements
sRep = s:rep(iLen) -- create replacement string
table.insert(tTab, sRep) -- save to table
posTab = _line:find('\t', posTab +1) -- find next TAB
end
local idx = 0
_line = _line:gsub('\t', function() idx = idx +1 return tTab[idx] end)
return _line, idx
end
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
--[[
in...: _file Path of the file whose TAB are to be replaced by spaces.
.....: _tabsize TAB size in number of characters. If it is omitted, 4 is used.
out..: Success true, count-of-replacements
.....: Failure false, 0
]]
----------------------------------------------------------------------------------------------------
TabReplace_File = function(_file, _tabsize)
local fh = io.open(_file, "rb") -- check if file exists
if fh == nil then return false, 0 end
local read = fh:read('*a')
local lineBreak = read:find('\r')
if lineBreak ~= nil then lineBreak = string.char(13,10) else lineBreak = string.char(10) end
local nRepl, sumRepl, newLine, sWrite = 0, 0, '', ''
fh:seek("set")
for line in fh:lines() do
newLine, nRepl = TabReplace_Line(line, _tabsize)
sWrite = string.format('%s%s%s', sWrite, newLine, lineBreak)
sumRepl = sumRepl + nRepl
end
fh:close()
-- • file:lines() zerstört das Zeilenende?
-- • Füge ich kein CRLF an, wird LF verwendet, was dazu führt, dass die berechneten Ersatzstrings für TAB falsch sind.
-- • Füge ich CRLF an, ist das Ergebnis: "Zeileninhalt" & CR & CR & CRLF.
-- • Deshalb wird das doppelte CR hier entfernt.
sWrite = sWrite:gsub('\r\r', '')
if sumRepl == 0 then return true, 0 end
fh = io.open(_file, "w+")
fh:write(sWrite)
fh:close()
return true, sumRepl
end
----------------------------------------------------------------------------------------------------
Alles anzeigen
-- TIME_STAMP 2022-03-17 10:43:14 v 0.1
-- Replace TAB in current SciTE buffer
----------------------------------------------------------------------------------------------------
--[[
in...: _line A line of text whose TAB are to be replaced by spaces.
.....: _tabsize TAB size in number of characters. If it is omitted, 4 is used.
out..: The line, with TAB replaced if necessary, and the number of replacements.
]]
----------------------------------------------------------------------------------------------------
TabReplace_Line = function(_line, _tabsize)
if _line:find('^[\r\n]+$') then return _line, 0 end -- only a line break
if _line == '' then return _line, 0 end -- only a empty string
local posTab = _line:find('\t')
if posTab == nil then return _line, 0 end -- no TAB included
_tabsize = _tabsize or 4 -- default TAB width
local tTab, s, sRep, iLen, sumLen = {}, ' ', '', 0, 0
while posTab ~= nil do
-- calculation replacement string, taking into account characters to be inserted
iLen = (_tabsize - ((posTab + sumLen -1) % _tabsize))
sumLen = sumLen + iLen -1 -- total length of the replacements
sRep = s:rep(iLen) -- create replacement string
table.insert(tTab, sRep) -- save to table
posTab = _line:find('\t', posTab +1) -- find next TAB
end
local idx = 0
_line = _line:gsub('\t', function() idx = idx +1 return tTab[idx] end)
return _line, idx
end
----------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
--[[
Replaces all TAB in the file currently open in SciTE
]]
----------------------------------------------------------------------------------------------------
TabReplace_FileInSciTE = function(_tabsize)
local caret = editor.CurrentPos
local fvl = editor.FirstVisibleLine
local content = ''
for i=0, editor.LineCount -1 do
local line = editor:GetLine(i)
line = TabReplace_Line(line, _tabsize)
content = content..line
end
editor:BeginUndoAction()
editor:ClearAll()
editor:InsertText(0, content)
editor:EndUndoAction()
editor.CurrentPos = caret
editor:SetSel(caret, caret)
editor.FirstVisibleLine = fvl
end
----------------------------------------------------------------------------------------------------
TabReplace_FileInSciTE(4) -- If required: Change the TAB size here
Alles anzeigen
EDIT v 0.2
SCRIPT BREAKING CHANCE - Um mit unterschiedlichen Tabweiten arbeiten zu können, wird jetzt ausschließlich die dateispezifische Property ("use.tabs.$(file.pattern.EXT)=0" oder "use.tabs.EXT=0") genutzt! use.tabs=0 ist allgemeingültig und wird deshalb nicht weiter unterstützt. Deshalb ist auch die Version 0.6 der CommonTools.lua erforderlich. Die erforderlichen Anpassungen in der Properties-Datei sind im Skriptkopf erläutert.
-- TIME_STAMP 2022-02-14 16:13:55 v 0.2
-- coding:utf-8
--[[
Load with "SciTEStartup.lua"
Require: "CommonTools.lua" ( https://autoit.de/thread/87485-scite-commontools/?postID=704965#post704965 )
min. version: v 0.6
To use with spaces as tab (property "use.tabs.$(file.pattern.EXT)=0" or "use.tabs.EXT=0"). Don't use "use.tabs=0"!
If the property is set to ">0", the script will not respond.
For use with values other than the default, see remarks.
Additional functions <Shift+Backspace> <Shift+Del>
single caret : Delete chars from caret until previous tab position Delete chars from caret until next tab position
rectangular selection: As above, but in each line of selection
The rectangular selection remains after the operation.
<Alt+Arrow_Left> <Alt+Arrow_Right>
single caret Skip to previous Tab position Skip to next Tab position
Remarks:
The default Tab size is 4. You can change this setting specified for your file types. The following properties need changes:
tab.size.$(file.pattern.EXT)=VALUE
indent.size.$(file.patterns.EXT)=VALUE
The (not file type specific) values:
tab.indents=1
backspace.unindents=1
should also set.
If tab.indents is set then pressing tab within indentation whitespace indents by indent.size rather than inserting a tab character.
If backspace.unindents then pressing backspace within indentation whitespace unindents by indent.size rather than deleting the character before the caret.
]]
-- v 0.2 changed: SCRIPT BREAKING CHANCE - Now only file specific properties ("use.tabs.$(file.pattern.EXT)=0" or "use.tabs.EXT=0") are considered!
local ct = require 'CommonTools'
Tabspaces = EventClass:new(Common)
-- Gets the carets column and position
Tabspaces.Caret = function(self)
local pos = editor.CurrentPos
local col = editor.Column[editor.CurrentPos]
return col, pos
end
-- Gets a table with {{line,pos}} for rectangular selection without selected text [caret only!]
Tabspaces.GetRect = function(self)
local tRet = {}
local selLines = editor.Selections
if (editor.SelectionMode == 0) or
(selLines < 2) or (editor.SelectionIsRectangle == false) then
return false, {}
end
local s_start = editor.SelectionStart
local s_end = editor.SelectionEnd
if s_start > s_end then s_start, s_end = s_end, s_start end
local line, s, e = editor:LineFromPosition(s_start)
repeat
s = editor:GetLineSelStartPosition(line)
e = editor:GetLineSelEndPosition(line)
if s ~= e then return false, {} end -- don't use with selected text
table.insert(tRet, {line,s})
line = line +1
selLines = selLines -1
until selLines == 0
return true, tRet
end
-- Deletes all characters left/right from cursor until the previous/next Tab position in line from caret
-- left side, keys: <Shift+Backspace>
-- right side, keys: <Shift+Del>
Tabspaces.DeleteLine = function(self, b_right)
local colCaret, posCaret = self:Caret()
local colTab, posTab
if b_right then
colTab, posTab = ct:EditorTabColPosInLine() -- next Tab
local posLast = editor.LineEndPosition[editor:LineFromPosition(posCaret)]
if posTab > posLast then posTab = posLast end
else
colTab, posTab = ct:EditorTabColPosInLine(true) -- previous Tab
end
editor:SetSelection(posTab, posCaret)
editor:ReplaceSel('')
end
-- Performs the deletion in the selected line(s) on the right or left side
Tabspaces.DeleteLeftRight = function(self, b_right)
local bRect, tRect = self:GetRect()
if bRect then
for i = #tRect, 1, -1 do
editor.CurrentPos = tRect[i][2]
self:DeleteLine(b_right)
end
scite.MenuCommand(IDM_SAVE)
local col = editor.Column[editor.CurrentPos]
editor.RectangularSelectionAnchor = editor:FindColumn(tRect[1][1], col)
for i = 2, #tRect do
editor.RectangularSelectionCaret = editor:FindColumn(tRect[i][1], col)
end
else
self:DeleteLine(b_right)
end
end
-- Skip to previous/next Tab position
-- left side, keys: <Alt+Arrow_Left>
-- right side, keys: <Alt+Arrow_Right>
Tabspaces.SkipLeftRight = function(self, b_right)
local colCaret, posCaret = self:Caret()
local posTab
if b_right then
_, posTab = ct:EditorTabColPosInLine() -- next Tab
local posLast = editor.LineEndPosition[editor:LineFromPosition(posCaret)]
if posTab > posLast then posTab = posLast end
else
_, posTab = ct:EditorTabColPosInLine(true) -- previous Tab
end
editor:SetSelection(posTab, posTab)
end
Tabspaces.OnKey = function(self, _keycode, _shift, _ctrl, _alt)
if ct:PropExt('use.tabs.', props['FileExt']) == '0' then -- file type specific setting is required
local tKeys = {[8]='backspace',[37]='ar_left', [39]='ar_right', [46]='delete'}
local key = tKeys[_keycode]
if key == nil then return nil end
if _shift and not _ctrl and not _alt then
if key == 'backspace' then
self:DeleteLeftRight()
return true
elseif key == 'delete' then
self:DeleteLeftRight(true)
return true
else
return nil
end
elseif not _shift and not _ctrl and _alt then
if key == 'ar_left' then
self:SkipLeftRight()
return true
elseif key == 'ar_right' then
self:SkipLeftRight(true)
return true
else
return nil
end
end
end
return nil
end
Alles anzeigen