#title="テキストオブジェクト" /* * Vim のテキストオブジェクトもどき version 0.0.7 * * 記号やXMLタグで囲まれた範囲に対して操作を行います。 * 対象となる範囲内にキャレットをおいた状態でこのマクロを実行し、行いたい操作を選択してください。 * * 利用可能な範囲 * * 1.記号で囲まれた内部 * 2.記号で囲まれた全体 * 3.記号のみ * * () * "" * '' * [] * {} * <> * // * XMLタグ() * C言語などにおける引数(左右がが"("と")"で囲まれている中の","または";"で区切られた1部分) * 行末まで * 行全体 * * 利用可能な操作 * * 削除 * 選択 * 切り取り * コピー * 置換 * 追加 * * 記号のみの場合、選択、切り取り、コピーは不可 * 記号で囲まれた内部、全体の場合、追加は不可 * 記号のみの場合、置換時に記号やXMLタグを指定すると、対応する記号が適切に選択される */ // キー割り当てのモード(1=Windows風/それ以外=Vim風) KeyBinding = 1; Point = function(x, y) { this.x = x; this.y = y; } Point.prototype = { }; TextReader = function(p) { this.x = p.x; this.y = p.y; this.line = document.GetLine(p.y); }; TextReader.prototype = { charAt : function() { return this.line.charAt(this.x - 1); }, back : function() { if(this.x <= 1) { if(this.y == 1) { return false; } this.line = document.GetLine(--this.y); this.x = this.line.length; if(this.line.length == 0) { return this.back(); } return true; } --this.x; return true; }, forward : function() { if(this.x >= this.line.length) { if(this.y == document.GetLines()) { return false; } this.line = document.GetLine(++this.y); this.x = 1; if(this.line.length == 0) { return this.forward(); } return true; } ++this.x; return true; }, prev : function() { if(this.x == 1) { return null; } return this.line.charAt((this.x - 1) - 1); }, next : function() { if(this.x == this.line.length) { return null; } return this.line.charAt((this.x + 1) - 1); } }; QuoteScanner = function(quote, escape) { this.quote = quote; this.escape = escape; }; QuoteScanner.prototype = { getRange : function() { caret = new Point(selection.GetActivePointX(eePosLogical), selection.GetActivePointY(eePosLogical)); begin = null; end = null; textReader = new TextReader(caret); do { ch = textReader.charAt(); if(ch == this.quote) { prev = textReader.prev(); if(prev == null || prev != this.escape) { begin = new Point(textReader.x + 1, textReader.y); break; } } } while(textReader.back()); if(!begin) { return null; } textReader = new TextReader(begin); do { ch = textReader.charAt(); if(ch == this.escape) { if(ch.next() != null) { textReader.forward(); } } else if(ch == this.quote) { end = new Point(textReader.x, textReader.y); break; } } while(textReader.forward()); if(!end) { return null; } if(!(end.y > caret.y || (end.y == caret.y && end.x >= caret.x))) { return null; } innerRange = { 'begin' : begin, 'end' : end }; outerRange = { 'begin' : new Point(begin.x - 1, begin.y), 'end' : new Point(end.x + 1, begin.y) }; return { 'inner' : innerRange, 'outer' : outerRange }; }, getBeginning : function() { return this.quote; }, getEnding : function() { return this.quote; } }; BraceScanner = function(open, close) { this.open = open; this.close = close; }; BraceScanner.prototype = { getRange : function() { caret = new Point(selection.GetActivePointX(eePosLogical), selection.GetActivePointY(eePosLogical)); begin = null; end = null; level = 1; textReader = new TextReader(caret); do { ch = textReader.charAt(); if(ch == this.open) { if(--level == 0) { begin = new Point(textReader.x + 1, textReader.y); break; } } else if(ch == this.close) { ++level; } } while(textReader.back()); if(!begin) { return null; } level = 1; textReader = new TextReader(begin); do { ch = textReader.charAt(); if(ch == this.open) { ++level; } else if(ch == this.close) { if(--level == 0) { end = new Point(textReader.x, textReader.y); break; } } } while(textReader.forward()); if(!end) { return null; } if(!(end.y > caret.y || (end.y == caret.y && end.x >= caret.x))) { return null; } innerRange = { 'begin' : begin, 'end' : end }; outerRange = { 'begin' : new Point(begin.x - 1, begin.y), 'end' : new Point(end.x + 1, end.y) }; return { 'inner' : innerRange, 'outer' : outerRange }; }, getBeginning : function() { return this.open; }, getEnding : function() { return this.close; } }; XMLScanner = function() { }; XMLScanner.prototype = { getRange : function() { caret = new Point(selection.GetActivePointX(eePosLogical), selection.GetActivePointY(eePosLogical)); begin = null; end = null; level = 1; textReader = new TextReader(caret); do { ch = textReader.charAt(); if(ch == '>') { if(textReader.prev() == '/') { for(; textReader.prev() != null && textReader.charAt() != '<'; textReader.back()) { } if(textReader.charAt() != '<') { return null; } } else { for(; textReader.prev() != null && textReader.charAt() != '<'; textReader.back()) { } if(textReader.charAt() != '<') { return null; } if(textReader.next() != '/') { if(--level == 0) { for(; textReader.charAt() != '>'; textReader.forward()) { } begin = new Point(textReader.x + 1, textReader.y); break; } } else { ++level; } } } } while(textReader.back()); if(!begin) { return null; } level = 1; textReader = new TextReader(begin); do { ch = textReader.charAt(); if(ch == '<') { if(textReader.next() == '/') { if(--level == 0) { end = new Point(textReader.x, textReader.y); break; } } else { for(; textReader.next() != null && textReader.charAt() != '>'; textReader.forward()) { } if(textReader.charAt() != '>') { return null; } if(textReader.prev() != '/') { ++level; } } } } while(textReader.forward()); if(!end) { return null; } if(!(end.y > caret.y || (end.y == caret.y && end.x >= caret.x))) { return null; } innerRange = { 'begin' : begin, 'end' : end }; begin = null; end = null; textReader = new TextReader(innerRange.begin); do { ch = textReader.charAt(); if(ch == '<') { begin = new Point(textReader.x, textReader.y); break; } } while(textReader.back()); if(!begin) { return null; } textReader = new TextReader(innerRange.end); do { ch = textReader.charAt(); if(ch == '>') { end = new Point(textReader.x + 1, textReader.y); break; } } while(textReader.forward()); if(!end) { return null; } outerRange = { 'begin' : begin, 'end' : end }; return { 'inner' : innerRange, 'outer' : outerRange }; }, getBeginning : function() { return ''; }, getEnding : function() { return ''; } }; ArgumentScanner = function() { }; ArgumentScanner.prototype = { getRange : function() { caret = new Point(selection.GetActivePointX(eePosLogical), selection.GetActivePointY(eePosLogical)); begin = null; end = null; level = 1; textReader = new TextReader(caret); do { ch = textReader.charAt(); if(ch == '(') { if(--level == 0) { begin = new Point(textReader.x + 1, textReader.y); break; } } else if(ch == ',' || ch == ';') { if(level == 1) { begin = new Point(textReader.x + 1, textReader.y); break; } } else if(ch == ')') { ++level; } } while(textReader.back()); if(!begin) { return null; } level = 1; textReader = new TextReader(begin); do { ch = textReader.charAt(); if(ch == '(') { ++level; } else if(ch == ',' || ch == ';') { if(level == 1) { end = new Point(textReader.x, textReader.y); break; } } else if(ch == ')') { if(--level == 0) { end = new Point(textReader.x, textReader.y); break; } } } while(textReader.forward()); if(!end) { return null; } if(!(end.y > caret.y || (end.y == caret.y && end.x >= caret.x))) { return null; } innerRange = { 'begin' : begin, 'end' : end }; outerRange = { 'begin' : new Point(begin.x - 1, begin.y), 'end' : new Point(end.x + 1, begin.y) }; return { 'inner' : innerRange, 'outer' : outerRange }; }, getBeginning : function() { return ''; }, getEnding : function() { return ''; } }; LineScanner = function(all) { this.all = all; }; LineScanner.prototype = { getRange : function() { caret = new Point(selection.GetActivePointX(eePosLogical), selection.GetActivePointY(eePosLogical)); begin = null; outerBegin = null; if(this.all) { begin = new Point(1, caret.y); } else { begin = caret; } end = new Point(document.GetLine(caret.y).length + 1, caret.y); range = { 'begin' : begin, 'end' : end }; return { 'inner' : range, 'outer' : range }; }, getBeginning : function() { return ''; }, getEnding : function() { return ''; } }; ie = new ActiveXObject("InternetExplorer.Application"); shiftKeyPressed = function() { ie.Navigate('about:blank'); ie.document.getElementsByTagName('body')[0].innerHTML = ''; ie.document.getElementById('button').click(); return ie.document.getElementById('result').value == 'true'; }; parenthesisScanner = new BraceScanner('(', ')'); braceScanner = new BraceScanner('{', '}'); squareBracketsScanner = new BraceScanner('[', ']'); angleBracketsScanner = new BraceScanner('<', '>'); argumentScanner = new ArgumentScanner(); Targets = [ { 'caption' : '&( )', 'scanner' : parenthesisScanner }, { 'caption' : '&" "', 'scanner' : new QuoteScanner('"', "\\") }, { 'caption' : "&' '", 'scanner' : new QuoteScanner("'", "\\") }, { 'caption' : '&[ ]', 'scanner' : squareBracketsScanner }, { 'caption' : '{ }', 'scanner' : braceScanner }, { 'caption' : '< >', 'scanner' : angleBracketsScanner }, { 'caption' : '&/ /', 'scanner' : new QuoteScanner('/', "\\") }, { 'caption' : 'XMLタグ(&T)', 'scanner' : new XMLScanner() }, { 'caption' : '引数(&,)', 'scanner' : argumentScanner }, { 'caption' : '行末まで(&$)', 'scanner' : new LineScanner(false) }, { 'caption' : '行全体(&S)', 'scanner' : new LineScanner(true) } ]; Delete = 1; Select = 2; Cut = 3; Copy = 4; Replace = 5; Addition = 6; InnerBlock = 1; ABlock = 2; Surroundings = 3; selection = document.selection; function selectMode() { popup = CreatePopupMenu(); popup.Add("記号の内側(&I)", InnerBlock); popup.Add("記号を含めた全体(&A)", ABlock); popup.Add("記号のみ(&S)", Surroundings); return popup.Track(0); } function selectOperation() { popup = CreatePopupMenu(); if(KeyBinding != 1) { popup.Add("削除(&D)", Delete); popup.Add("選択(&V)", Select); popup.Add("切り取り(&T)", Cut); popup.Add("コピー(&Y)", Copy); popup.Add("置換(&C)", Replace); popup.Add("追加(&Y)", Addition); } else { popup.Add("削除(&D)", Delete); popup.Add("選択(&S)", Select); popup.Add("切り取り(&T)", Cut); popup.Add("コピー(&C)", Copy); popup.Add("置換(&R)", Replace); popup.Add("追加(&A)", Addition); } return popup.Track(0); } function selectScanner() { popup = CreatePopupMenu(); for(var i = 0; i < Targets.length; ++i) { popup.Add(Targets[i].caption, i + 1); } index = popup.Track(0) - 1; if(index < 0) { return null; } scanner = Targets[index].scanner; if(scanner == squareBracketsScanner) { if(shiftKeyPressed()) { scanner = braceScanner; } } else if(scanner == argumentScanner) { if(shiftKeyPressed()) { scanner = angleBracketsScanner; } } return scanner; } selectedOp = selectOperation(); if(selectedOp == 0) { Quit(); } selectedMode = selectMode(); if(selectedMode == 0) { Quit(); } selectedSacnner = selectScanner(); if(selectedSacnner == null) { Quit(); } if(selectedMode == Surroundings) { if(selectedOp == Select || selectedOp == Cut || selectedOp == Copy) { alert('記号のみを選択している場合、この操作は実行できません。'); Quit(); } } caret = new Point(selection.GetActivePointX(eePosLogical), selection.GetActivePointY(eePosLogical)) ranges = selectedSacnner.getRange(); if(!ranges) { Quit(); } if(selectedMode == InnerBlock || selectedMode == ABlock) { Redraw = false; range = null; if(selectedMode == InnerBlock) { range = ranges.inner; } else { range = ranges.outer; } selection.SetActivePoint(eePosLogical, range.end.x, range.end.y, false); selection.SetAnchorPoint(eePosLogical, range.begin.x, range.begin.y); switch(selectedOp) { case Delete: selection.Delete(); break; case Select: // do nothing break; case Cut: selection.Cut(); break; case Copy: selection.Copy(0); selection.SetActivePoint(eePosLogical, caret.x, caret.y, false); break; case Replace: after = prompt("置換後の文字列", ""); if(after != '') { selection.Text = after; } else { selection.SetActivePoint(eePosLogical, caret.x, caret.y, false); } break; } Redraw = true; } else if(selectedMode == Surroundings) { inner = ranges.inner; outer = ranges.outer; Redraw = false; selection.SetActivePoint(eePosLogical, inner.end.x, inner.end.y, false); selection.SetAnchorPoint(eePosLogical, inner.begin.x, inner.begin.y); bodyText = selection.Text; switch(selectedOp) { case Delete: selection.SetActivePoint(eePosLogical, outer.end.x, outer.end.y, false); selection.SetAnchorPoint(eePosLogical, outer.begin.x, outer.begin.y); selection.Text = bodyText; if(caret.y == outer.begin.y) { selection.SetActivePoint(eePosLogical, caret.x - (inner.begin.x - outer.begin.x), inner.begin.y, false); } else { selection.SetActivePoint(eePosLogical, caret.x, caret.y, false); } break; case Replace: case Addition: after = prompt(selectedOp == Replace ? "置換後の文字列" : "追加する文字列", ""); if(after != '') { newBeginning = ''; newEnding = ''; if(after.charAt(0) == '<') { tagName = ''; if(after.charAt(after.length - 1) != '>') { after += '>'; } i = after.indexOf(' ', 0); if(i == -1) { i = after.length - 1; } tagName = after.substr(1, i - 1); newBeginning = after; newEnding = ""; } else { for(var i = 0; i < Targets.length; ++i) { if(after == Targets[i].scanner.getBeginning()) { newBeginning = after; newEnding = Targets[i].scanner.getEnding(); break; } else if(after == Targets[i].scanner.getEnding()) { newBeginning = Targets[i].scanner.getBeginning(); newEnding = after; break; } } if(newBodyText == '') { newBeginning = after; newEnding = after; } } if(selectedOp == Replace) { selection.SetActivePoint(eePosLogical, outer.end.x, outer.end.y, false); selection.SetAnchorPoint(eePosLogical, outer.begin.x, outer.begin.y); } selection.Text = newBeginning + bodyText + newEnding; if(caret.y == outer.begin.y) { if(selectedOp == Replace) { selection.SetActivePoint(eePosLogical, caret.x + newBeginning.length - (inner.begin.x - outer.begin.x), caret.y, false); } else { selection.SetActivePoint(eePosLogical, caret.x + newBeginning.length, caret.y, false); } } else { selection.SetActivePoint(eePosLogical, caret.x, caret.y, false); } } else { selection.SetActivePoint(eePosLogical, caret.x, caret.y, false); } break; } Redraw = true; }