Index: trunk/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.config.js |
— | — | @@ -963,24 +963,31 @@ |
964 | 964 | // TODO: Find a cleaner way to share this function |
965 | 965 | $(this).data( 'replaceCallback', function( mode ) { |
966 | 966 | $( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide(); |
| 967 | + |
| 968 | + // Search string cannot be empty |
967 | 969 | var searchStr = $( '#wikieditor-toolbar-replace-search' ).val(); |
968 | 970 | if ( searchStr == '' ) { |
969 | 971 | $( '#wikieditor-toolbar-replace-emptysearch' ).show(); |
970 | 972 | return; |
971 | 973 | } |
| 974 | + |
| 975 | + // Replace string can be empty |
972 | 976 | var replaceStr = $( '#wikieditor-toolbar-replace-replace' ).val(); |
| 977 | + |
| 978 | + // Prepare the regular expression flags |
973 | 979 | var flags = 'm'; |
974 | 980 | var matchCase = $( '#wikieditor-toolbar-replace-case' ).is( ':checked' ); |
975 | | - var isRegex = $( '#wikieditor-toolbar-replace-regex' ).is( ':checked' ); |
976 | 981 | if ( !matchCase ) { |
977 | 982 | flags += 'i'; |
978 | 983 | } |
| 984 | + var isRegex = $( '#wikieditor-toolbar-replace-regex' ).is( ':checked' ); |
| 985 | + if ( !isRegex ) { |
| 986 | + searchStr = $.escapeRE( searchStr ); |
| 987 | + } |
979 | 988 | if ( mode == 'replaceAll' ) { |
980 | 989 | flags += 'g'; |
981 | 990 | } |
982 | | - if ( !isRegex ) { |
983 | | - searchStr = $.escapeRE( searchStr ); |
984 | | - } |
| 991 | + |
985 | 992 | try { |
986 | 993 | var regex = new RegExp( searchStr, flags ); |
987 | 994 | } catch( e ) { |
— | — | @@ -990,20 +997,26 @@ |
991 | 998 | .show(); |
992 | 999 | return; |
993 | 1000 | } |
| 1001 | + |
994 | 1002 | var $textarea = $(this).data( 'context' ).$textarea; |
995 | 1003 | var text = $textarea.textSelection( 'getContents' ); |
996 | 1004 | var match = false; |
997 | | - var offset, s; |
| 1005 | + var offset, textRemainder; |
998 | 1006 | if ( mode != 'replaceAll' ) { |
999 | | - offset = $(this).data( 'offset' ); |
1000 | | - s = text.substr( offset ); |
1001 | | - match = s.match( regex ); |
| 1007 | + if (mode == 'replace') { |
| 1008 | + offset = $(this).data( 'matchIndex' ); |
| 1009 | + } else { |
| 1010 | + offset = $(this).data( 'offset' ); |
| 1011 | + } |
| 1012 | + textRemainder = text.substr( offset ); |
| 1013 | + match = textRemainder.match( regex ); |
1002 | 1014 | } |
1003 | 1015 | if ( !match ) { |
1004 | 1016 | // Search hit BOTTOM, continuing at TOP |
| 1017 | + // TODO: Add a "Wrap around" option. |
1005 | 1018 | offset = 0; |
1006 | | - s = text; |
1007 | | - match = s.match( regex ); |
| 1019 | + textRemainder = text; |
| 1020 | + match = textRemainder.match( regex ); |
1008 | 1021 | } |
1009 | 1022 | |
1010 | 1023 | if ( !match ) { |
— | — | @@ -1017,13 +1030,13 @@ |
1018 | 1031 | // in Firefox/Webkit, but in IE replacing the entire content once is better. |
1019 | 1032 | var index; |
1020 | 1033 | for ( var i = 0; i < match.length; i++ ) { |
1021 | | - index = s.indexOf( match[i] ); |
| 1034 | + index = textRemainder.indexOf( match[i] ); |
1022 | 1035 | if ( index == -1 ) { |
1023 | 1036 | // This shouldn't happen |
1024 | 1037 | break; |
1025 | 1038 | } |
1026 | | - var matchedText = s.substr( index, match[i].length ); |
1027 | | - s = s.substr( index + match[i].length ); |
| 1039 | + var matchedText = textRemainder.substr( index, match[i].length ); |
| 1040 | + textRemainder = textRemainder.substr( index + match[i].length ); |
1028 | 1041 | |
1029 | 1042 | var start = index + offset; |
1030 | 1043 | var end = start + match[i].length; |
— | — | @@ -1043,27 +1056,63 @@ |
1044 | 1057 | .show(); |
1045 | 1058 | $(this).data( 'offset', 0 ); |
1046 | 1059 | } else { |
1047 | | - // Make regex placeholder substitution ($1) work |
1048 | | - var replace = isRegex ? match[0].replace( regex, replaceStr ): replaceStr; |
1049 | | - var start = match.index + offset; |
1050 | | - var end = start + match[0].length; |
1051 | | - var newEnd = start + replace.length; |
1052 | | - var context = $( this ).data( 'context' ); |
1053 | | - $textarea.textSelection( 'setSelection', { 'start': start, |
1054 | | - 'end': end } ); |
| 1060 | + var start, end; |
| 1061 | + |
1055 | 1062 | if ( mode == 'replace' ) { |
1056 | | - $textarea |
1057 | | - .textSelection( 'encapsulateSelection', { |
1058 | | - 'peri': replace, |
1059 | | - 'replace': true } ) |
1060 | | - .textSelection( 'setSelection', { |
1061 | | - 'start': start, |
1062 | | - 'end': newEnd } ); |
| 1063 | + var actualReplacement; |
| 1064 | + |
| 1065 | + if (isRegex) { |
| 1066 | + // If backreferences (like $1) are used, the actual actual replacement string will be different |
| 1067 | + actualReplacement = match[0].replace( regex, replaceStr ); |
| 1068 | + } else { |
| 1069 | + actualReplacement = replaceStr; |
| 1070 | + } |
| 1071 | + |
| 1072 | + if (match) { |
| 1073 | + // Do the replacement |
| 1074 | + $textarea.textSelection( 'encapsulateSelection', { |
| 1075 | + 'peri': actualReplacement, |
| 1076 | + 'replace': true } ); |
| 1077 | + // Reload the text after replacement |
| 1078 | + text = $textarea.textSelection( 'getContents' ); |
| 1079 | + } |
| 1080 | + |
| 1081 | + // Find the next instance |
| 1082 | + offset = offset + match[0].length + actualReplacement.length; |
| 1083 | + textRemainder = text.substr( offset ); |
| 1084 | + match = textRemainder.match( regex ); |
| 1085 | + |
| 1086 | + if (match) { |
| 1087 | + start = offset + match.index; |
| 1088 | + end = start + match[0].length; |
| 1089 | + } else { |
| 1090 | + // If no new string was found, try searching from the beginning. |
| 1091 | + // TODO: Add a "Wrap around" option. |
| 1092 | + textRemainder = text; |
| 1093 | + match = textRemainder.match( regex ); |
| 1094 | + if (match) { |
| 1095 | + start = match.index; |
| 1096 | + end = start + match[0].length; |
| 1097 | + } else { |
| 1098 | + // Give up |
| 1099 | + start = 0; |
| 1100 | + end = 0; |
| 1101 | + } |
| 1102 | + } |
| 1103 | + } else { |
| 1104 | + start = offset + match.index; |
| 1105 | + end = start + match[0].length; |
1063 | 1106 | } |
| 1107 | + |
| 1108 | + $( this ).data( 'matchIndex', start); |
| 1109 | + |
| 1110 | + $textarea.textSelection( 'setSelection', { |
| 1111 | + 'start': start, |
| 1112 | + 'end': end |
| 1113 | + } ); |
1064 | 1114 | $textarea.textSelection( 'scrollToCaretPosition' ); |
1065 | | - $textarea.textSelection( 'setSelection', { 'start': start, |
1066 | | - 'end': mode == 'replace' ? newEnd : end } ); |
1067 | | - $( this ).data( 'offset', mode == 'replace' ? newEnd : end ); |
| 1115 | + $( this ).data( 'offset', end ); |
| 1116 | + var context = $( this ).data( 'context' ); |
1068 | 1117 | var textbox = typeof context.$iframe != 'undefined' ? |
1069 | 1118 | context.$iframe[0].contentWindow : $textarea[0]; |
1070 | 1119 | textbox.focus(); |
— | — | @@ -1092,6 +1141,8 @@ |
1093 | 1142 | }, |
1094 | 1143 | open: function() { |
1095 | 1144 | $(this).data( 'offset', 0 ); |
| 1145 | + $(this).data( 'matchIndex', 0 ); |
| 1146 | + |
1096 | 1147 | $( '#wikieditor-toolbar-replace-search' ).focus(); |
1097 | 1148 | $( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide(); |
1098 | 1149 | if ( !( $(this).data( 'onetimeonlystuff' ) ) ) { |