未验证 提交 c774ffc9 编写于 作者: L LBJ-the-GOAT 提交者: GitHub

feat(gui): search in resource files (#347) (#1032) (PR #1108)

Co-authored-by: tobias <tobias.hotmail.com>
上级 c93e7fb9
......@@ -256,6 +256,56 @@ public class StringUtils {
return count;
}
/**
* returns how many lines does it have between start to pos in content.
*/
public static int countLinesByPos(String content, int pos, int start) {
if (start >= pos) {
return 0;
}
int count = 0;
int tempPos = start;
do {
tempPos = content.indexOf("\n", tempPos);
if (tempPos == -1) {
break;
}
if (tempPos >= pos) {
break;
}
count += 1;
tempPos += 1;
} while (tempPos < content.length());
return count;
}
/**
* returns lines that contain pos to end if end is not -1.
*/
public static String getLine(String content, int pos, int end) {
if (pos >= content.length()) {
return "";
}
if (end != -1) {
if (end > content.length()) {
end = content.length() - 1;
}
} else {
end = pos + 1;
}
// get to line head
int headPos = content.lastIndexOf("\n", pos);
if (headPos == -1) {
headPos = 0;
}
// get to line end
int endPos = content.indexOf("\n", end);
if (endPos == -1) {
endPos = content.length();
}
return content.substring(headPos, endPos);
}
public static boolean isWhite(char chr) {
return WHITES.indexOf(chr) != -1;
}
......
......@@ -12,7 +12,6 @@ import jadx.gui.JadxWrapper;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.search.StringRef;
......@@ -30,12 +29,12 @@ public class IndexJob extends BackgroundJob {
@Override
protected void runJob() {
JNodeCache nodeCache = cache.getNodeCache();
TextSearchIndex index = new TextSearchIndex(nodeCache);
CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache);
TextSearchIndex index = new TextSearchIndex(cache);
CodeUsageInfo usageInfo = new CodeUsageInfo(cache.getNodeCache());
cache.setTextIndex(index);
cache.setUsageInfo(usageInfo);
addTask(index::indexResource);
for (final JavaClass cls : wrapper.getIncludedClasses()) {
addTask(() -> indexCls(cache, cls));
}
......
......@@ -63,6 +63,10 @@ public class JadxSettings extends JadxCLIArgs {
private Map<String, WindowLocation> windowPos = new HashMap<>();
private int mainWindowExtendedState = JFrame.NORMAL;
private boolean codeAreaLineWrap = false;
private int srhResourceSkipSize = 1000;
private String srhResourceFileExt = ".xml|.html|.js|.json|.txt";
private boolean keepCommonDialogOpen = false;
/**
* UI setting: the width of the tree showing the classes, resources, ...
*/
......@@ -400,6 +404,30 @@ public class JadxSettings extends JadxCLIArgs {
return this.codeAreaLineWrap;
}
public int getSrhResourceSkipSize() {
return srhResourceSkipSize;
}
public void setSrhResourceSkipSize(int size) {
srhResourceSkipSize = size;
}
public String getSrhResourceFileExt() {
return srhResourceFileExt;
}
public void setSrhResourceFileExt(String all) {
srhResourceFileExt = all.trim();
}
public void setKeepCommonDialogOpen(boolean yes) {
keepCommonDialogOpen = yes;
}
public boolean getKeepCommonDialogOpen() {
return keepCommonDialogOpen;
}
private void upgradeSettings(int fromVersion) {
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
if (fromVersion == 0) {
......@@ -454,6 +482,10 @@ public class JadxSettings extends JadxCLIArgs {
showHeapUsageBar = false;
fromVersion++;
}
if (fromVersion == 10) {
srhResourceSkipSize = 3;
srhResourceFileExt = ".xml|.html|.js|.json|.txt";
}
settingsVersion = CURRENT_SETTINGS_VERSION;
sync();
}
......
......@@ -11,6 +11,10 @@ import java.util.Arrays;
import java.util.Collection;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -74,6 +78,7 @@ public class JadxSettingsWindow extends JDialog {
leftPanel.add(makeProjectGroup());
leftPanel.add(makeEditorGroup());
leftPanel.add(makeOtherGroup());
leftPanel.add(makeSearchResGroup());
rightPanel.add(makeDecompilationGroup());
......@@ -474,6 +479,51 @@ public class JadxSettingsWindow extends JDialog {
return group;
}
private SettingsGroup makeSearchResGroup() {
SettingsGroup group = new SettingsGroup(NLS.str("preferences.search_res_title"));
int prevSize = settings.getSrhResourceSkipSize();
String prevExts = settings.getSrhResourceFileExt();
SpinnerNumberModel sizeLimitModel = new SpinnerNumberModel(prevSize,
0, Integer.MAX_VALUE, 1);
JSpinner spinner = new JSpinner(sizeLimitModel);
JTextField fileExtField = new JTextField();
group.addRow(NLS.str("preferences.res_skip_file"), spinner);
group.addRow(NLS.str("preferences.res_file_ext"), fileExtField);
spinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int size = (Integer) spinner.getValue();
settings.setSrhResourceSkipSize(size);
}
});
fileExtField.getDocument().addDocumentListener(new DocumentListener() {
private void update() {
String ext = fileExtField.getText();
settings.setSrhResourceFileExt(ext);
}
@Override
public void insertUpdate(DocumentEvent e) {
update();
}
@Override
public void removeUpdate(DocumentEvent e) {
update();
}
@Override
public void changedUpdate(DocumentEvent e) {
update();
}
});
fileExtField.setText(prevExts);
return group;
}
private void needReload() {
needReload = true;
}
......
package jadx.gui.treemodel;
import javax.swing.*;
import jadx.core.utils.StringUtils;
public class JResSearchNode extends JNode {
private static final long serialVersionUID = -2222084945157778639L;
private final transient JResource resNode;
private final transient String text;
private final transient int line;
private final transient int pos;
public JResSearchNode(JResource resNode, String text, int line, int pos) {
this.pos = pos;
this.text = text;
this.line = line;
this.resNode = resNode;
}
public JResource getResNode() {
return resNode;
}
public int getPos() {
return pos;
}
@Override
public String makeDescString() {
return text;
}
@Override
public JClass getJParent() {
return resNode.getJParent();
}
@Override
public int getLine() {
return line;
}
@Override
public String makeLongStringHtml() {
return getName();
}
@Override
public Icon getIcon() {
return resNode.getIcon();
}
@Override
public String getName() {
return resNode.getName();
}
@Override
public String makeString() {
return resNode.makeString();
}
@Override
public boolean hasDescString() {
return !StringUtils.isEmpty(text);
}
}
......@@ -111,7 +111,10 @@ public abstract class CommonSearchDialog extends JDialog {
}
JumpPosition jmpPos;
JNode node = (JNode) resultsModel.getValueAt(selectedId, 0);
if (node instanceof CodeNode) {
if (node instanceof JResSearchNode) {
jmpPos = new JumpPosition(((JResSearchNode) node).getResNode(), node.getLine())
.setPrecise(((JResSearchNode) node).getPos());
} else if (node instanceof CodeNode) {
CodeNode codeNode = (CodeNode) node;
jmpPos = new JumpPosition(node.getRootClass(), node.getLine(), codeNode.getPos());
if (codeNode.isPrecisePos()) {
......@@ -121,8 +124,9 @@ public abstract class CommonSearchDialog extends JDialog {
jmpPos = new JumpPosition(node.getRootClass(), node.getLine());
}
tabbedPane.codeJump(jmpPos);
dispose();
if (!mainWindow.getSettings().getKeepCommonDialogOpen()) {
dispose();
}
}
@Override
......@@ -148,6 +152,15 @@ public abstract class CommonSearchDialog extends JDialog {
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
JCheckBox cbKeepOpen = new JCheckBox(NLS.str("search_dialog.keep_open"));
cbKeepOpen.setSelected(mainWindow.getSettings().getKeepCommonDialogOpen());
cbKeepOpen.addActionListener(e -> {
mainWindow.getSettings().setKeepCommonDialogOpen(cbKeepOpen.isSelected());
mainWindow.getSettings().sync();
});
buttonPane.add(cbKeepOpen);
buttonPane.add(Box.createRigidArea(new Dimension(15, 0)));
buttonPane.add(progressPane);
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
buttonPane.add(Box.createHorizontalGlue());
......@@ -251,6 +264,10 @@ public abstract class CommonSearchDialog extends JDialog {
resultsInfoLabel.setText(statusText);
}
protected void showSearchState() {
resultsInfoLabel.setText(NLS.str("search_dialog.tip_searching"));
}
protected static class ResultsTable extends JTable {
private static final long serialVersionUID = 3901184054736618969L;
private final transient ResultsTableCellRenderer renderer;
......
......@@ -445,6 +445,8 @@ public class MainWindow extends JFrame {
protected void resetCache() {
cacheObject.reset();
// TODO: decompilation freezes sometime with several threads
this.cacheObject.setJRoot(treeRoot);
this.cacheObject.setJadxSettings(settings);
int threadsCount = settings.getThreadsCount();
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
......@@ -563,6 +565,8 @@ public class MainWindow extends JFrame {
treeModel.setRoot(treeRoot);
treeRoot.update();
reloadTree();
cacheObject.setJRoot(treeRoot);
cacheObject.setJadxSettings(settings);
}
private void clearTree() {
......
......@@ -39,7 +39,8 @@ public class SearchDialog extends CommonSearchDialog {
FIELD,
CODE,
IGNORE_CASE,
USE_REGEX
USE_REGEX,
Resource
}
private transient Set<SearchOptions> options;
......@@ -76,7 +77,6 @@ public class SearchDialog extends CommonSearchDialog {
if (lastSearch != null) {
searchField.setText(lastSearch);
searchField.selectAll();
searchEmitter.emitSearch();
}
searchField.requestFocus();
}
......@@ -91,6 +91,7 @@ public class SearchDialog extends CommonSearchDialog {
JCheckBox caseChBox = makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE);
JCheckBox regexChBox = makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX);
JCheckBox resChBox = makeOptionsCheckBox(NLS.str("search_dialog.resource"), SearchOptions.Resource);
JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS);
JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD);
JCheckBox fldChBox = makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD);
......@@ -98,6 +99,7 @@ public class SearchDialog extends CommonSearchDialog {
JPanel searchInPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
searchInPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
searchInPanel.add(resChBox);
searchInPanel.add(clsChBox);
searchInPanel.add(mthChBox);
searchInPanel.add(fldChBox);
......@@ -200,6 +202,7 @@ public class SearchDialog extends CommonSearchDialog {
if (index == null) {
return Flowable.empty();
}
showSearchState();
return index.buildSearch(text, options);
}
......
......@@ -149,10 +149,10 @@ public class CodePanel extends JPanel {
}
public void refresh() {
int line;
int lineCount;
int line = 0;
int tokenIndex;
int pos = codeArea.getCaretPosition();
int lineCount = codeArea.getLineCount();
try {
// after rename the change of document is undetectable, so
// use Token offset to calculate the new caret position.
......@@ -161,10 +161,12 @@ public class CodePanel extends JPanel {
tokenIndex = getTokenIndexByOffset(token, pos);
} catch (BadLocationException e) {
e.printStackTrace();
tokenIndex = 0;
line = codeArea.getLineCount() - 1;
tokenIndex = -1;
}
if (tokenIndex == -1) {
refreshToViewport();
return;
}
lineCount = codeArea.getLineCount();
codeArea.refresh();
initLineNumbers();
int lineDiff = codeArea.getLineCount() - lineCount;
......@@ -184,14 +186,23 @@ public class CodePanel extends JPanel {
});
}
private void refreshToViewport() {
JViewport viewport = getCodeScrollPane().getViewport();
Point viewPosition = viewport.getViewPosition();
codeArea.refresh();
initLineNumbers();
SwingUtilities.invokeLater(() -> {
viewport.setViewPosition(viewPosition);
});
}
private int getTokenIndexByOffset(Token token, int offset) {
if (token != null) {
int index = 1;
while (token.getEndOffset() < offset) {
token = token.getNextToken();
if (token == null) {
index = 0;
break;
return -1;
}
index++;
}
......@@ -201,7 +212,7 @@ public class CodePanel extends JPanel {
}
private int getOffsetOfTokenByIndex(int index, Token token) {
if (token != null) {
if (token != null && index != -1) {
for (int i = 0; i < index; i++) {
token = token.getNextToken();
if (token == null) {
......
......@@ -7,6 +7,8 @@ import org.jetbrains.annotations.Nullable;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JRoot;
import jadx.gui.ui.SearchDialog;
import jadx.gui.utils.search.TextSearchIndex;
......@@ -20,12 +22,16 @@ public class CacheObject {
private String lastSearch;
private JNodeCache jNodeCache;
private Set<SearchDialog.SearchOptions> lastSearchOptions;
private JRoot jRoot;
private JadxSettings settings;
public CacheObject() {
reset();
}
public void reset() {
jRoot = null;
settings = null;
decompileJob = null;
indexJob = null;
textIndex = null;
......@@ -89,4 +95,20 @@ public class CacheObject {
public Set<SearchDialog.SearchOptions> getLastSearchOptions() {
return lastSearchOptions;
}
public void setJadxSettings(JadxSettings settings) {
this.settings = settings;
}
public JadxSettings getJadxSettings() {
return this.settings;
}
public JRoot getJRoot() {
return jRoot;
}
public void setJRoot(JRoot jRoot) {
this.jRoot = jRoot;
}
}
......@@ -47,7 +47,7 @@ public class CodeIndex {
}
LOG.debug("Code search complete: {}, memory usage: {}", searchSettings.getSearchString(), UiUtils.memoryInfo());
emitter.onComplete();
}, BackpressureStrategy.LATEST);
}, BackpressureStrategy.BUFFER);
}
public int size() {
......
package jadx.gui.utils.search;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.swing.tree.TreeNode;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.core.utils.files.FileUtils;
import jadx.gui.treemodel.JResSearchNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.utils.CacheObject;
import static jadx.core.utils.StringUtils.*;
public class ResourceIndex {
private final List<JResource> resNodes = new ArrayList<>();
private final Set<String> extSet = new HashSet<>();
private CacheObject cache;
private String fileExts;
private boolean anyExt;
private int sizeLimit;
public ResourceIndex(CacheObject cache) {
this.cache = cache;
}
private void search(final JResource resNode,
FlowableEmitter<JResSearchNode> emitter,
SearchSettings searchSettings) {
int pos = 0;
int line = 0;
int lastPos = 0;
int lastLineOccurred = -1;
JResSearchNode lastNode = null;
int searchStrLen = searchSettings.getSearchString().length();
String content;
try {
content = resNode.getContent();
} catch (Exception e) {
e.printStackTrace();
return;
}
do {
searchSettings.setStartPos(lastPos);
pos = searchSettings.find(content);
if (pos > -1) {
line += countLinesByPos(content, pos, lastPos);
lastPos = pos + searchStrLen;
String lineText = getLine(content, pos, lastPos);
if (lastLineOccurred != line) {
lastLineOccurred = line;
if (lastNode != null) {
emitter.onNext(lastNode);
}
lastNode = new JResSearchNode(resNode, lineText.trim(), line + 1, pos);
}
} else {
if (lastNode != null) { // commit the final result node.
emitter.onNext(lastNode);
}
break;
}
} while (!emitter.isCancelled() && lastPos < content.length());
}
public Flowable<JResSearchNode> search(SearchSettings settings) {
refreshSettings();
if (resNodes.size() == 0) {
return Flowable.empty();
}
return Flowable.create(emitter -> {
for (JResource resNode : resNodes) {
if (!emitter.isCancelled()) {
search(resNode, emitter, settings);
}
}
emitter.onComplete();
}, BackpressureStrategy.BUFFER);
}
public void index() {
refreshSettings();
}
private void clear() {
anyExt = false;
sizeLimit = -1;
fileExts = "";
extSet.clear();
resNodes.clear();
}
private void traverseTree(TreeNode root, ZipFile zip) {
for (int i = 0; i < root.getChildCount(); i++) {
TreeNode node = root.getChildAt(i);
if (node instanceof JResource) {
JResource resNode = (JResource) node;
try {
resNode.loadNode();
} catch (Exception e) {
e.printStackTrace();
return;
}
ResourceFile resFile = resNode.getResFile();
if (resFile == null) {
traverseTree(node, zip);
} else {
if (resFile.getType() == ResourceType.ARSC && shouldSearchXML()) {
resFile.loadContent();
resNode.getFiles().forEach(t -> traverseTree(t, null));
} else {
filter(resNode, zip);
}
}
}
}
}
private boolean shouldSearchXML() {
return anyExt || fileExts.contains(".xml");
}
private ZipFile getZipFile(TreeNode res) {
for (int i = 0; i < res.getChildCount(); i++) {
TreeNode node = res.getChildAt(i);
if (node instanceof JResource) {
JResource resNode = (JResource) node;
try {
resNode.loadNode();
} catch (Exception e) {
e.printStackTrace();
return null;
}
ResourceFile file = resNode.getResFile();
if (file == null) {
ZipFile zip = getZipFile(resNode);
if (zip != null) {
return zip;
}
} else {
File zfile = file.getZipRef().getZipFile();
if (FileUtils.isZipFile(zfile)) {
try {
return new ZipFile(zfile);
} catch (IOException ignore) {
}
}
}
}
}
return null;
}
private void filter(JResource resNode, ZipFile zip) {
ResourceFile resFile = resNode.getResFile();
if (JResource.isSupportedForView(resFile.getType())) {
long size = -1;
if (zip != null) {
ZipEntry entry = zip.getEntry(resFile.getOriginalName());
if (entry != null) {
size = entry.getSize();
}
}
if (size == -1) { // resource from ARSC is unknown size
try {
size = resNode.getContent().length();
} catch (Exception ignore) {
return;
}
}
if (size <= sizeLimit) {
if (!anyExt) {
for (String ext : extSet) {
if (resFile.getOriginalName().endsWith(ext)) {
resNodes.add(resNode);
break;
}
}
} else {
resNodes.add(resNode);
}
}
}
}
private void refreshSettings() {
int size = cache.getJadxSettings().getSrhResourceSkipSize() * 10240;
if (size != sizeLimit
|| !cache.getJadxSettings().getSrhResourceFileExt().equals(fileExts)) {
clear();
sizeLimit = size;
fileExts = cache.getJadxSettings().getSrhResourceFileExt();
String[] exts = fileExts.split("\\|");
for (String ext : exts) {
ext = ext.trim();
if (!ext.isEmpty()) {
anyExt = ext.equals("*");
if (anyExt) {
break;
}
extSet.add(ext);
}
}
ZipFile zipFile = getZipFile(cache.getJRoot());
traverseTree(cache.getJRoot(), zipFile); // reindex
try {
if (zipFile != null) {
zipFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
......@@ -35,7 +35,7 @@ public class SimpleIndex {
}
}
emitter.onComplete();
}, BackpressureStrategy.LATEST);
}, BackpressureStrategy.BUFFER);
}
public int size() {
......
......@@ -19,6 +19,7 @@ import jadx.core.codegen.CodeWriter;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.SearchDialog;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.UiUtils;
......@@ -28,6 +29,7 @@ import static jadx.gui.ui.SearchDialog.SearchOptions.CODE;
import static jadx.gui.ui.SearchDialog.SearchOptions.FIELD;
import static jadx.gui.ui.SearchDialog.SearchOptions.IGNORE_CASE;
import static jadx.gui.ui.SearchDialog.SearchOptions.METHOD;
import static jadx.gui.ui.SearchDialog.SearchOptions.Resource;
import static jadx.gui.ui.SearchDialog.SearchOptions.USE_REGEX;
public class TextSearchIndex {
......@@ -40,11 +42,13 @@ public class TextSearchIndex {
private final SimpleIndex mthSignaturesIndex;
private final SimpleIndex fldSignaturesIndex;
private final CodeIndex codeIndex;
private final ResourceIndex resIndex;
private final List<JavaClass> skippedClasses = new ArrayList<>();
public TextSearchIndex(JNodeCache nodeCache) {
this.nodeCache = nodeCache;
public TextSearchIndex(CacheObject cache) {
this.nodeCache = cache.getNodeCache();
this.resIndex = new ResourceIndex(cache);
this.clsNamesIndex = new SimpleIndex();
this.mthSignaturesIndex = new SimpleIndex();
this.fldSignaturesIndex = new SimpleIndex();
......@@ -85,6 +89,10 @@ public class TextSearchIndex {
}
}
public void indexResource() {
resIndex.index();
}
public void remove(JavaClass cls) {
this.clsNamesIndex.removeForCls(cls);
this.mthSignaturesIndex.removeForCls(cls);
......@@ -121,6 +129,9 @@ public class TextSearchIndex {
result = Flowable.concat(result, searchInSkippedClasses(searchSettings));
}
}
if (options.contains(Resource)) {
result = Flowable.concat(result, resIndex.search(searchSettings));
}
return result;
}
......@@ -146,7 +157,7 @@ public class TextSearchIndex {
}
LOG.debug("Skipped code search complete: {}, memory usage: {}", searchSettings.getSearchString(), UiUtils.memoryInfo());
emitter.onComplete();
}, BackpressureStrategy.LATEST);
}, BackpressureStrategy.BUFFER);
}
private int searchNext(FlowableEmitter<CodeNode> emitter, JavaNode javaClass, String code, final SearchSettings searchSettings) {
......
......@@ -77,6 +77,9 @@ search_dialog.info_label=Zeige Ergebnisse %1$d bis %2$d von %3$d
search_dialog.col_node=Knoten
search_dialog.col_code=Code
search_dialog.regex=Regex
#search_dialog.resource=
#search_dialog.keep_open=
#search_dialog.tip_searching=
usage_dialog.title=Verwendungssuche
usage_dialog.label=Verwendung für:
......@@ -132,6 +135,9 @@ preferences.rename=Umbenennen
preferences.rename_case=System unterscheidet zwischen Groß/Kleinschreibung
preferences.rename_valid=Ist eine gültige Kennung
preferences.rename_printable=Ist druckbar
#preferences.search_res_title=
#preferences.res_file_ext=
#preferences.res_skip_file=
msg.open_file=Bitte Datei öffnen
msg.saving_sources=Quellen speichern…
......
......@@ -77,6 +77,9 @@ search_dialog.info_label=Showing results %1$d to %2$d of %3$d
search_dialog.col_node=Node
search_dialog.col_code=Code
search_dialog.regex=Regex
search_dialog.resource=Resource
search_dialog.keep_open=Keep open
search_dialog.tip_searching=Searching ...
usage_dialog.title=Usage search
usage_dialog.label=Usage for:
......@@ -132,6 +135,9 @@ preferences.rename=Rename
preferences.rename_case=System case sensitivity
preferences.rename_valid=To be valid identifier
preferences.rename_printable=To be printable
preferences.search_res_title=Search Resource
preferences.res_file_ext=File Extensions (e.g. .xml|.html), * means all
preferences.res_skip_file=Skip files exceed (MB)
msg.open_file=Please open file
msg.saving_sources=Saving sources...
......
......@@ -77,6 +77,9 @@ search_dialog.info_label=Mostrando resultados %1$d a %2$d de %3$d
search_dialog.col_node=Nodo
search_dialog.col_code=Código
search_dialog.regex=Regex
#search_dialog.resource=
#search_dialog.keep_open=
#search_dialog.tip_searching=
usage_dialog.title=Usage search
usage_dialog.label=Usage for:
......@@ -132,6 +135,9 @@ preferences.reset_title=Reestablecer preferencias
#preferences.rename_case=
#preferences.rename_valid=
#preferences.rename_printable=
#preferences.search_res_title=
#preferences.res_file_ext=
#preferences.res_skip_file=
msg.open_file=Por favor, abra un archivo
msg.saving_sources=Guardando fuente...
......
......@@ -77,6 +77,9 @@ search_dialog.info_label=%3$d 중 %1$d-%2$d 결과 표시
search_dialog.col_node=노드
search_dialog.col_code=코드
search_dialog.regex=정규식
#search_dialog.resource=
#search_dialog.keep_open=
#search_dialog.tip_searching=
usage_dialog.title=사용 검색
usage_dialog.label=다음의 사용 검색 결과:
......@@ -132,6 +135,9 @@ preferences.rename=이름 바꾸기
preferences.rename_case=시스템 대소문자 구분
preferences.rename_valid=유효한 식별자로 바꾸기
preferences.rename_printable=출력 가능하게 바꾸기
#preferences.search_res_title=
#preferences.res_file_ext=
#preferences.res_skip_file=
msg.open_file=파일을 여십시오
msg.saving_sources=소스 저장 중 ...
......
......@@ -77,6 +77,9 @@ search_dialog.info_label=显示了 %3$d 个结果中的第 %1$d 至第 %2$d 个
search_dialog.col_node=节点
search_dialog.col_code=代码
search_dialog.regex=正则表达式
#search_dialog.resource=
#search_dialog.keep_open=
#search_dialog.tip_searching=
usage_dialog.title=查找
usage_dialog.label=查找用例:
......@@ -132,6 +135,9 @@ preferences.rename=重命名
preferences.rename_case=系统区分大小写
preferences.rename_valid=是有效的标识符
preferences.rename_printable=是可打印
#preferences.search_res_title=
#preferences.res_file_ext=
#preferences.res_skip_file=
msg.open_file=请打开文件
msg.saving_sources=正在导出源代码...
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册