From 272cae5e6a25bcffc3239b5628e5d4257ae98264 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Fri, 23 Aug 2024 11:33:31 +0200 Subject: [PATCH 01/25] Drop Me --- Hallo.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 Hallo.txt diff --git a/Hallo.txt b/Hallo.txt new file mode 100644 index 00000000..53f50075 --- /dev/null +++ b/Hallo.txt @@ -0,0 +1 @@ +Hallo \ No newline at end of file From a54cb7fcc078f77c4a68c2071b020c25951fec21 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Sat, 26 Oct 2024 16:49:01 +0200 Subject: [PATCH 02/25] Die Klasse DiffNode wurde zum speichern der Zeile mit dem endif erweitert --- .../diffdetective/variation/diff/DiffNode.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java index 4244fadf..5dd4010a 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java @@ -51,6 +51,8 @@ public class DiffNode implements HasNodeType { private Node featureMapping; + private String endIf = null; + /** * The parents {@link DiffNode} before and after the edit. * This array has to be indexed by {@code Time.ordinal()} @@ -146,6 +148,22 @@ public void setLabel(L newLabel) { label.setInnerLabel(newLabel); } + /** + * Returns the line with the endif of the corresponding if, if the node is an if node, otherwise null + * @return String, the Line with endif + */ + public String getEndIf() { + return endIf; + } + + /** + * Sets the line with the endif of the corresponding if, if the node is an if node + * @param endIf String, the Line with endif + */ + public void setEndIf(String endIf) { + this.endIf = endIf; + } + /** * Gets the first {@code if} node in the path from the root to this node at the time * {@code time}. From 02bbdb73e3efe20302f72945e832e965e5a56b15 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Sat, 26 Oct 2024 20:04:29 +0200 Subject: [PATCH 03/25] Die Klasse VariationTreeNode wurde zum speichern der Zeile mit dem endif erweitert --- .../variation/tree/VariationTreeNode.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTreeNode.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTreeNode.java index 0609a8fd..6ecbac59 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTreeNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTreeNode.java @@ -82,6 +82,11 @@ public class VariationTreeNode extends VariationNode> childOrder; + /** + * The line with the endif of the corresponding if, if the node is an if node, otherwise null + */ + private String endIf = null; + /** * Creates a new node of a variation tree. * @@ -226,6 +231,22 @@ public void removeAllChildren() { childOrder.clear(); } + /** + * Returns the line with the endif of the corresponding if, if the node is an if node, otherwise null + * @return String, the Line with endif + */ + public String getEndIf() { + return endIf; + } + + /** + * Sets the line with the endif of the corresponding if, if the node is an if node + * @param endIf String, the Line with endif + */ + public void setEndIf(String endIf) { + this.endIf = endIf; + } + @Override public Node getFormula() { return featureMapping; From 558bad1335d679d08b73901147d2054dc1543f57 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Sat, 26 Oct 2024 20:21:07 +0200 Subject: [PATCH 04/25] Die Klassen VariationNode und Projection wurden um eine Methode zum Ausgeben von endif erweitert --- .../diffdetective/variation/diff/Projection.java | 5 +++++ .../diffdetective/variation/tree/VariationNode.java | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java b/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java index 20e476f7..619bafb3 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java @@ -121,4 +121,9 @@ public Node getFormula() { public int getID() { return getBackingNode().getID(); } + + @Override + public String getEndIf() { + return getBackingNode().getEndIf(); + } }; diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java index d1622423..fccbccf5 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java @@ -113,6 +113,12 @@ public VariationNode downCast() { */ public abstract List getChildren(); + /** + * Returns the line with the endif of the corresponding if, if the node is an if node, otherwise null + * @return String, the Line with endif + */ + public abstract String getEndIf(); + /** * Returns {@code true} iff this node has no parent. * From f4e3966ae20749cf9b2698398ff5e2acaa5feb95 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Sat, 26 Oct 2024 20:24:55 +0200 Subject: [PATCH 05/25] =?UTF-8?q?Beim=20Erstellen=20eines=20VariationTrees?= =?UTF-8?q?=20aus=20einem=20Variation-Diff=20wird=20jetzt=20auch=20dei=20Z?= =?UTF-8?q?eile=20mit=20dem=20endif=20mit=20=C3=BCbertragen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diffdetective/variation/tree/VariationNode.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java index fccbccf5..5f73d300 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java @@ -483,6 +483,9 @@ public VariationTreeNode toVariationTree(final Map Date: Mon, 28 Oct 2024 17:37:40 +0100 Subject: [PATCH 06/25] =?UTF-8?q?Unparser=20f=C3=BCr=20Variation-Trees=20w?= =?UTF-8?q?urde=20eingebaut=20dazu=20eine=20Testkalsse=20erstellet=20und?= =?UTF-8?q?=20Testdaten=20in=20resourcen=20gepackt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../variation/VariationUnparser.java | 35 ++++++++ src/test/java/VariationUnparserTest.java | 80 +++++++++++++++++++ src/test/resources/unparser/test1.txt | 0 src/test/resources/unparser/test2.txt | 3 + 4 files changed, 118 insertions(+) create mode 100644 src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java create mode 100644 src/test/java/VariationUnparserTest.java create mode 100644 src/test/resources/unparser/test1.txt create mode 100644 src/test/resources/unparser/test2.txt diff --git a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java new file mode 100644 index 00000000..2f9a0db6 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java @@ -0,0 +1,35 @@ +package org.variantsync.diffdetective.variation; + +import java.util.ArrayList; +import java.util.Stack; +import org.variantsync.diffdetective.variation.tree.VariationTree; +import org.variantsync.diffdetective.variation.tree.VariationTreeNode; + +public class VariationUnparser { + + + public static String variationTreeUnparser(VariationTree tree){ + StringBuilder result = new StringBuilder(); + Stack> stack = new Stack<>(); + for (int i = tree.root().getChildren().size()-1; i>=0;i--) { + stack.push(tree.root().getChildren().get(i)); + } + while (!stack.empty()){ + VariationTreeNode node = stack.pop(); + for(String line :node.getLabel().getLines()){ + result.append(line); + result.append("\n"); + } + if(node.isIf()){ + ArrayList list = new ArrayList<>(); + list.add(node.getEndIf()); + stack.push(new VariationTreeNode<>(NodeType.ARTIFACT, null,null, DiffLinesLabel.withInvalidLineNumbers(list) )); + } + for(int i = node.getChildren().size() -1 ; i>=0;i--){ + stack.push(node.getChildren().get(i)); + } + } + return result.substring(0,result.length()-1); + } + +} diff --git a/src/test/java/VariationUnparserTest.java b/src/test/java/VariationUnparserTest.java new file mode 100644 index 00000000..2e6085f6 --- /dev/null +++ b/src/test/java/VariationUnparserTest.java @@ -0,0 +1,80 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.tree.VariationTree; +import org.variantsync.diffdetective.variation.VariationUnparser; + +public class VariationUnparserTest { + private final static Path testDir = Constants.RESOURCE_DIR.resolve("unparser"); + + private final static String testCaseSuffix = ".txt"; + + protected static Stream findTestCases(Path dir) { + try { + return Files + .list(dir) + .filter(filename -> filename.getFileName().toString().endsWith(testCaseSuffix)); + }catch (Exception e){ + e.printStackTrace(); + } + return null; + } + + @Test + public void tests() { + findTestCases(testDir).forEach(this::test); + } + + @Test + public void teststest(){ + Path path = testDir.resolve("test2.txt"); + String temp = "b"; + VariationTree tree = null; + try{ + tree = VariationTree.fromFile(path); + temp = VariationUnparser.variationTreeUnparser(tree); + }catch (Exception e){ + e.printStackTrace(); + } + assertNotNull(tree.root().getChildren().get(0).getEndIf()); + } + + + public void test(Path basename) { + testCase(basename); + } + + public static void testCase(Path testCasePath) { + String temp = ""; + try { + temp = Files.readString(testCasePath); + }catch (Exception e){ + e.printStackTrace(); + } + temp = temp.replaceAll("\\r\\n","\n"); + String unparse = parseUnparse(testCasePath); + assertEquals(temp,unparse); + } + + public static String parseUnparse(Path path){ + String temp = "b"; + try{ + VariationTree tree = VariationTree.fromFile(path); + temp = VariationUnparser.variationTreeUnparser(tree); + }catch (Exception e){ + e.printStackTrace(); + } + return temp; + } + +} diff --git a/src/test/resources/unparser/test1.txt b/src/test/resources/unparser/test1.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/unparser/test2.txt b/src/test/resources/unparser/test2.txt new file mode 100644 index 00000000..4200a6eb --- /dev/null +++ b/src/test/resources/unparser/test2.txt @@ -0,0 +1,3 @@ +#ifdef C + boob() +#endif \ No newline at end of file From ba1d2600128cd327f09d95ed59583993ffbf9bd3 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Wed, 30 Oct 2024 14:20:24 +0100 Subject: [PATCH 07/25] =?UTF-8?q?Der=20Parser=20wurde=20zum=20Speichern=20?= =?UTF-8?q?von=20Zeilen=20mit=20endif=20erweitert.=20Der=20Typ=20von=20end?= =?UTF-8?q?if=20wurde=20von=20string=20zu=20List=20ge=C3=A4ndert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diffdetective/variation/diff/DiffNode.java | 6 +++--- .../diffdetective/variation/diff/Projection.java | 2 +- .../variation/diff/parse/VariationDiffParser.java | 15 +++++++++++++-- .../variation/tree/VariationNode.java | 2 +- .../variation/tree/VariationTreeNode.java | 6 +++--- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java index 5dd4010a..a8ff8e0d 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java @@ -51,7 +51,7 @@ public class DiffNode implements HasNodeType { private Node featureMapping; - private String endIf = null; + private List endIf = null; /** * The parents {@link DiffNode} before and after the edit. @@ -152,7 +152,7 @@ public void setLabel(L newLabel) { * Returns the line with the endif of the corresponding if, if the node is an if node, otherwise null * @return String, the Line with endif */ - public String getEndIf() { + public List getEndIf() { return endIf; } @@ -160,7 +160,7 @@ public String getEndIf() { * Sets the line with the endif of the corresponding if, if the node is an if node * @param endIf String, the Line with endif */ - public void setEndIf(String endIf) { + public void setEndIf(List endIf) { this.endIf = endIf; } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java b/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java index 619bafb3..f9ac7a02 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java @@ -123,7 +123,7 @@ public int getID() { } @Override - public String getEndIf() { + public List getEndIf() { return getBackingNode().getEndIf(); } }; diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java index 01863a8c..f6a9095f 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java @@ -1,5 +1,7 @@ package org.variantsync.diffdetective.variation.diff.parse; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.lang3.function.FailableSupplier; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.ObjectId; @@ -321,7 +323,7 @@ private void parseLine( // Do not create a node for ENDIF, but update the line numbers of the closed if-chain // and remove that if-chain from the relevant stacks. diffType.forAllTimesOfExistence(beforeStack, afterStack, stack -> - popIfChain(stack, fromLine) + popIfChain(stack, fromLine,line) ); } else if (options.collapseMultipleCodeLines() && annotationType == AnnotationType.None @@ -365,11 +367,20 @@ private void parseLine( */ private void popIfChain( Stack> stack, - DiffLineNumber elseLineNumber + DiffLineNumber elseLineNumber, + LogicalLine line ) throws DiffParseException { DiffLineNumber previousLineNumber = elseLineNumber; do { DiffNode annotation = stack.peek(); + if(annotation.isIf()){ + List list = new ArrayList<>(); + for (int i = 0; i < line.getLines().size();i++ + ) { + list.add(line.getLines().get(i).content()); + } + annotation.setEndIf(list); + } // Set the line number of now closed annotations to the beginning of the // following annotation. diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java index 5f73d300..cf896a95 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationNode.java @@ -117,7 +117,7 @@ public VariationNode downCast() { * Returns the line with the endif of the corresponding if, if the node is an if node, otherwise null * @return String, the Line with endif */ - public abstract String getEndIf(); + public abstract List getEndIf(); /** * Returns {@code true} iff this node has no parent. diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTreeNode.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTreeNode.java index 6ecbac59..d26bf3b6 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTreeNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTreeNode.java @@ -85,7 +85,7 @@ public class VariationTreeNode extends VariationNode endIf = null; /** * Creates a new node of a variation tree. @@ -235,7 +235,7 @@ public void removeAllChildren() { * Returns the line with the endif of the corresponding if, if the node is an if node, otherwise null * @return String, the Line with endif */ - public String getEndIf() { + public List getEndIf() { return endIf; } @@ -243,7 +243,7 @@ public String getEndIf() { * Sets the line with the endif of the corresponding if, if the node is an if node * @param endIf String, the Line with endif */ - public void setEndIf(String endIf) { + public void setEndIf(List endIf) { this.endIf = endIf; } From b484c49567d4b77302bdfffd6ac6c9ac427c4e35 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Fri, 1 Nov 2024 20:15:30 +0100 Subject: [PATCH 08/25] =?UTF-8?q?Die=20Unparser=20f=C3=BCr=20VariationTree?= =?UTF-8?q?s=20und=20VariationDiffs=20wurde=20erstellt.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../variation/VariationUnparser.java | 89 ++++++++++++++----- 1 file changed, 69 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java index 2f9a0db6..e1aca3f1 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java @@ -1,35 +1,84 @@ package org.variantsync.diffdetective.variation; -import java.util.ArrayList; +import java.io.IOException; +import java.util.List; import java.util.Stack; +import java.util.function.Function; +import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; +import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.construction.JGitDiff; import org.variantsync.diffdetective.variation.tree.VariationTree; import org.variantsync.diffdetective.variation.tree.VariationTreeNode; public class VariationUnparser { - public static String variationTreeUnparser(VariationTree tree){ - StringBuilder result = new StringBuilder(); - Stack> stack = new Stack<>(); - for (int i = tree.root().getChildren().size()-1; i>=0;i--) { - stack.push(tree.root().getChildren().get(i)); - } - while (!stack.empty()){ - VariationTreeNode node = stack.pop(); - for(String line :node.getLabel().getLines()){ - result.append(line); - result.append("\n"); - } - if(node.isIf()){ - ArrayList list = new ArrayList<>(); - list.add(node.getEndIf()); - stack.push(new VariationTreeNode<>(NodeType.ARTIFACT, null,null, DiffLinesLabel.withInvalidLineNumbers(list) )); + /** + * Unparst VariationTrees to Text/String + * @param tree VariationTree, that be unparsed + * @param linesToLabel Function, that return list of String and has a Class T + * @return String, the result of unparsing + * @param that implements Label + */ + public static String variationTreeUnparser(VariationTree tree, Function,T> linesToLabel){ + if(!tree.root().getChildren().isEmpty()) { + StringBuilder result = new StringBuilder(); + Stack> stack = new Stack<>(); + for (int i = tree.root().getChildren().size() - 1; i >= 0; i--) { + stack.push(tree.root().getChildren().get(i)); } - for(int i = node.getChildren().size() -1 ; i>=0;i--){ - stack.push(node.getChildren().get(i)); + while (!stack.empty()) { + VariationTreeNode node = stack.pop(); + for (String line : node.getLabel().getLines()) { + result.append(line); + result.append("\n"); + } + if (node.isIf()) { + stack.push(new VariationTreeNode<>(NodeType.ARTIFACT, null, null, + linesToLabel.apply(node.getEndIf()))); + } + for (int i = node.getChildren().size() - 1; i >= 0; i--) { + stack.push(node.getChildren().get(i)); + } } + return result.substring(0, result.length() - 1); + }else{ + return ""; } - return result.substring(0,result.length()-1); + } + + /** + * Unparst VariationTrees to Text/String + * @param tree VariationTree, that be unparsed + * @return String, the result of unparsing + */ + public static String variationTreeUnparser(VariationTree tree){ + return variationTreeUnparser(tree,DiffLinesLabel::withInvalidLineNumbers); + } + + /** + * Unparst VariationDiffs to Text/String + * @param diff VariationDiff, that be unparsed + * @param linesToLabel Function, that return list of String and has a Class T + * @return String, the result of unparsing + * @param that implements Label + * @throws IOException + */ + public static String variationDiffUnparser(VariationDiff diff,Function,T> linesToLabel) throws IOException { + String tree1 = variationTreeUnparser(diff.project(Time.BEFORE),linesToLabel); + String tree2 = variationTreeUnparser(diff.project(Time.AFTER),linesToLabel); + return JGitDiff.textDiff(tree1,tree2, SupportedAlgorithm.MYERS); + } + + /** + * Unparst VariationDiffs to Text/String + * @param diff VariationDiff, that be unparsed + * @return String, the result of unparsing + * @throws IOException + */ + public static String variationDiffUnparser(VariationDiff diff) throws IOException { + return variationDiffUnparser(diff,DiffLinesLabel::withInvalidLineNumbers); } } From a532fc45d5d0707e1f044f858a9dcd6b174be078 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Sat, 2 Nov 2024 18:50:13 +0100 Subject: [PATCH 09/25] methode zum projezieren von text diff erstellt --- .../variation/VariationUnparser.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java index e1aca3f1..4c2291ad 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java @@ -81,4 +81,26 @@ public static String variationDiffUnparser(VariationDiff diff) t return variationDiffUnparser(diff,DiffLinesLabel::withInvalidLineNumbers); } + public static String undiff(String text,Time time){ + StringBuilder result = new StringBuilder(); + String[] textSplit = text.split("\n"); + if(Time.AFTER == time){ + for (String line: textSplit) { + if(line.charAt(0) != '-'){ + result.append(line.substring(1)); + result.append("\n"); + } + } + return result.substring(0, result.length() - 1); + }else{ + for (String line: textSplit) { + if(line.charAt(0) != '+'){ + result.append(line.substring(1)); + result.append("\n"); + } + } + return result.substring(0, result.length() - 1); + } + } + } From af652caeb9468b194cb190e68a3cb6b35aa9227d Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Mon, 4 Nov 2024 14:37:14 +0100 Subject: [PATCH 10/25] =?UTF-8?q?In=20der=20Funktion=20zwei=20sachen=20mit?= =?UTF-8?q?=20einander=20vertauscht=20damit=20dem=20Algorithmus=20n=C3=A4h?= =?UTF-8?q?er=20ist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diffdetective/variation/VariationUnparser.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java index 4c2291ad..b0c02f26 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java @@ -30,14 +30,14 @@ public static String variationTreeUnparser(VariationTree t } while (!stack.empty()) { VariationTreeNode node = stack.pop(); - for (String line : node.getLabel().getLines()) { - result.append(line); - result.append("\n"); - } if (node.isIf()) { stack.push(new VariationTreeNode<>(NodeType.ARTIFACT, null, null, linesToLabel.apply(node.getEndIf()))); } + for (String line : node.getLabel().getLines()) { + result.append(line); + result.append("\n"); + } for (int i = node.getChildren().size() - 1; i >= 0; i--) { stack.push(node.getChildren().get(i)); } From 771651a64145473e4eec974db253ab6ceb77ba11 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Thu, 7 Nov 2024 16:46:35 +0100 Subject: [PATCH 11/25] =?UTF-8?q?TestKlasse=20und=20Testf=C3=A4lle=20f?= =?UTF-8?q?=C3=BCr=20das=20Testen=20von=20VariattionUnparser=20eingebaut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/VariationUnparserTest.java | 114 +++++++++++++++++------ src/test/resources/unparser/test3.txt | 11 +++ src/test/resources/unparser/test4.txt | 8 ++ src/test/resources/unparser/test5.txt | 5 + src/test/resources/unparser/test6.txt | 5 + src/test/resources/unparser/test7.txt | 9 ++ src/test/resources/unparser/test8.txt | 11 +++ 7 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 src/test/resources/unparser/test3.txt create mode 100644 src/test/resources/unparser/test4.txt create mode 100644 src/test/resources/unparser/test5.txt create mode 100644 src/test/resources/unparser/test6.txt create mode 100644 src/test/resources/unparser/test7.txt create mode 100644 src/test/resources/unparser/test8.txt diff --git a/src/test/java/VariationUnparserTest.java b/src/test/java/VariationUnparserTest.java index 2e6085f6..603321a7 100644 --- a/src/test/java/VariationUnparserTest.java +++ b/src/test/java/VariationUnparserTest.java @@ -1,25 +1,28 @@ -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import java.io.File; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.stream.Stream; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.variantsync.diffdetective.diff.result.DiffParseException; import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; import org.variantsync.diffdetective.variation.tree.VariationTree; import org.variantsync.diffdetective.variation.VariationUnparser; public class VariationUnparserTest { - private final static Path testDir = Constants.RESOURCE_DIR.resolve("unparser"); + private final static Path testDirTree = Constants.RESOURCE_DIR.resolve("unparser"); + + private final static Path testDirDiff = Constants.RESOURCE_DIR.resolve("diffs").resolve("parser"); + private final static String testCaseSuffixTree = ".txt"; - private final static String testCaseSuffix = ".txt"; + private final static String testCaseSuffixDiff = ".diff"; - protected static Stream findTestCases(Path dir) { + protected static Stream findTestCases(Path dir,String testCaseSuffix) { try { return Files .list(dir) @@ -30,46 +33,86 @@ protected static Stream findTestCases(Path dir) { return null; } - @Test - public void tests() { - findTestCases(testDir).forEach(this::test); + public static Stream testsTree() throws IOException { + return findTestCases(testDirTree,testCaseSuffixTree); } - @Test - public void teststest(){ - Path path = testDir.resolve("test2.txt"); - String temp = "b"; - VariationTree tree = null; - try{ - tree = VariationTree.fromFile(path); - temp = VariationUnparser.variationTreeUnparser(tree); + public static Stream testsDiff() throws IOException { + return findTestCases(testDirDiff,testCaseSuffixDiff); + } + @ParameterizedTest + @MethodSource("testsTree") + public void testTree(Path basename) throws IOException, DiffParseException { + testCaseTree(basename); + } + + @ParameterizedTest + @MethodSource("testsDiff") + public void testDiff(Path basename) throws IOException, DiffParseException { + testCaseDiff(basename); + } + + public static void testCaseTree(Path testCasePath) { + String temp = ""; + try { + temp = Files.readString(testCasePath); }catch (Exception e){ e.printStackTrace(); } - assertNotNull(tree.root().getChildren().get(0).getEndIf()); - } - + System.out.println(testCasePath); + temp = temp.replaceAll("\\r\\n","\n"); + String unparse1 = parseUnparseTree(testCasePath,new VariationDiffParseOptions(false,false)); + String unparse2 = parseUnparseTree(testCasePath,new VariationDiffParseOptions(false,true)); + String unparse3 = parseUnparseTree(testCasePath,new VariationDiffParseOptions(true,false)); + String unparse4 = parseUnparseTree(testCasePath,new VariationDiffParseOptions(true,true)); + System.out.println(temp.equals(unparse1) + " " + temp.equals(unparse2) + " " +temp.equals(unparse3) + " " +temp.equals(unparse4)); + temp = removeWhitespace(temp); + unparse1 = removeWhitespace(unparse1); + unparse2 = removeWhitespace(unparse2); + unparse3 = removeWhitespace(unparse3); + unparse4 = removeWhitespace(unparse4); + System.out.println(temp.equals(unparse1) + " " + temp.equals(unparse2) + " " +temp.equals(unparse3) + " " +temp.equals(unparse4)); - public void test(Path basename) { - testCase(basename); } - public static void testCase(Path testCasePath) { + public static void testCaseDiff(Path testCasePath) { String temp = ""; try { temp = Files.readString(testCasePath); }catch (Exception e){ e.printStackTrace(); } + System.out.println(testCasePath); temp = temp.replaceAll("\\r\\n","\n"); - String unparse = parseUnparse(testCasePath); - assertEquals(temp,unparse); + String unparse1 = parseUnparseDiff(testCasePath,new VariationDiffParseOptions(false,false)); + String unparse2 = parseUnparseDiff(testCasePath,new VariationDiffParseOptions(false,true)); + String unparse3 = parseUnparseDiff(testCasePath,new VariationDiffParseOptions(true,false)); + String unparse4 = parseUnparseDiff(testCasePath,new VariationDiffParseOptions(true,true)); + System.out.println(temp.equals(unparse1) + " " + temp.equals(unparse2) + " " +temp.equals(unparse3) + " " +temp.equals(unparse4)); + String temp1 = VariationUnparser.undiff(temp,Time.BEFORE); + String temp2 = VariationUnparser.undiff(temp,Time.AFTER); + String unparse11 = VariationUnparser.undiff(unparse1,Time.BEFORE); + String unparse12 = VariationUnparser.undiff(unparse1,Time.AFTER); + String unparse21 = VariationUnparser.undiff(unparse2,Time.BEFORE); + String unparse22 = VariationUnparser.undiff(unparse2,Time.AFTER); + String unparse31 = VariationUnparser.undiff(unparse3,Time.BEFORE); + String unparse32 = VariationUnparser.undiff(unparse3,Time.AFTER); + String unparse41 = VariationUnparser.undiff(unparse1,Time.BEFORE); + String unparse42 = VariationUnparser.undiff(unparse1,Time.AFTER); + System.out.println(temp1.equals(unparse11) + " " + temp2.equals(unparse12) + " " + temp1.equals(unparse21) + " " + temp2.equals(unparse22) + " " + temp1.equals(unparse31) + " " + temp2.equals(unparse32) + " " + temp1.equals(unparse41) + " " + temp2.equals(unparse42)); + System.out.println(removeWhitespace(temp1).equals(removeWhitespace(unparse11)) + " " + removeWhitespace(temp2).equals(removeWhitespace(unparse12)) + " " + removeWhitespace(temp1).equals(removeWhitespace(unparse21)) + " " + removeWhitespace(temp2).equals(removeWhitespace(unparse22)) + " " + removeWhitespace(temp1).equals(removeWhitespace(unparse31)) + " " + removeWhitespace(temp2).equals(removeWhitespace(unparse32)) + " " + removeWhitespace(temp1).equals(removeWhitespace(unparse41)) + " " + removeWhitespace(temp2).equals(removeWhitespace(unparse42))); + temp = removeWhitespace(temp); + unparse1 = removeWhitespace(unparse1); + unparse2 = removeWhitespace(unparse2); + unparse3 = removeWhitespace(unparse3); + unparse4 = removeWhitespace(unparse4); + System.out.println(temp.equals(unparse1) + " " + temp.equals(unparse2) + " " +temp.equals(unparse3) + " " +temp.equals(unparse4)); } - public static String parseUnparse(Path path){ + public static String parseUnparseTree(Path path,VariationDiffParseOptions option){ String temp = "b"; try{ - VariationTree tree = VariationTree.fromFile(path); + VariationTree tree = VariationTree.fromFile(path,option); temp = VariationUnparser.variationTreeUnparser(tree); }catch (Exception e){ e.printStackTrace(); @@ -77,4 +120,19 @@ public static String parseUnparse(Path path){ return temp; } + public static String parseUnparseDiff(Path path,VariationDiffParseOptions option){ + String temp = "b"; + try{ + VariationDiff diff = VariationDiff.fromFile(path,option); + temp = VariationUnparser.variationDiffUnparser(diff); + }catch (Exception e){ + e.printStackTrace(); + } + return temp; + } + + public static String removeWhitespace(String string){ + return string.replaceAll("\\s+",""); + } + } diff --git a/src/test/resources/unparser/test3.txt b/src/test/resources/unparser/test3.txt new file mode 100644 index 00000000..1e1a94da --- /dev/null +++ b/src/test/resources/unparser/test3.txt @@ -0,0 +1,11 @@ +#ifdef A + foo(); + bar(); +#else + #ifdef B + baz(); + #endif +#endif +#ifdef C + boob() +#endif \ No newline at end of file diff --git a/src/test/resources/unparser/test4.txt b/src/test/resources/unparser/test4.txt new file mode 100644 index 00000000..21cb52f2 --- /dev/null +++ b/src/test/resources/unparser/test4.txt @@ -0,0 +1,8 @@ +#ifdef A + foo(); + bar(); +#endif + +#if B && (!A || C) + baz(); +#endif \ No newline at end of file diff --git a/src/test/resources/unparser/test5.txt b/src/test/resources/unparser/test5.txt new file mode 100644 index 00000000..3137d7b2 --- /dev/null +++ b/src/test/resources/unparser/test5.txt @@ -0,0 +1,5 @@ +#ifdef A + baz(); + foo(); +#endif +foo(); \ No newline at end of file diff --git a/src/test/resources/unparser/test6.txt b/src/test/resources/unparser/test6.txt new file mode 100644 index 00000000..b0f909a0 --- /dev/null +++ b/src/test/resources/unparser/test6.txt @@ -0,0 +1,5 @@ +#ifdef A + baz(); + foo(); +#endif + diff --git a/src/test/resources/unparser/test7.txt b/src/test/resources/unparser/test7.txt new file mode 100644 index 00000000..2a248d68 --- /dev/null +++ b/src/test/resources/unparser/test7.txt @@ -0,0 +1,9 @@ +#ifdef FEAT_GUI + if (gui.in_use) + gui_mch_set_foreground(); +#else +# ifdef MSWIN + win32_set_foreground(); +# endif +#endif +} diff --git a/src/test/resources/unparser/test8.txt b/src/test/resources/unparser/test8.txt new file mode 100644 index 00000000..2ba7d7d2 --- /dev/null +++ b/src/test/resources/unparser/test8.txt @@ -0,0 +1,11 @@ +#ifdef FEAT_GUI + if (gui.in_use) + { + gui_mch_set_foreground(); + return; + } +#endif +#if defined(MSWIN) && (!defined(FEAT_GUI) || defined(VIMDLL)) + win32_set_foreground(); +#endif +} From a7dd02df30df41cc26ff231e7200dd93918113a4 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Wed, 27 Nov 2024 18:15:22 +0100 Subject: [PATCH 12/25] =?UTF-8?q?Dataset=20datei=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/datasets/eugen-bachelor-thesis.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/datasets/eugen-bachelor-thesis.md diff --git a/docs/datasets/eugen-bachelor-thesis.md b/docs/datasets/eugen-bachelor-thesis.md new file mode 100644 index 00000000..c24b5085 --- /dev/null +++ b/docs/datasets/eugen-bachelor-thesis.md @@ -0,0 +1,3 @@ +Project name | Domain | Source code available (\*\*y\*\*es/\*\*n\*\*o)? | Is it a git repository (\*\*y\*\*es/\*\*n\*\*o)? | Repository URL | Clone URL | Estimated number of commits +-------------------|-------------------------|-------------------------------------------------|--------------------------------------------------|--------------------------------------------------------------|----------------------------------------------------|--------------------------------- +sylpheed | e-mail client | y | y | https://github.com/jan0sch/sylpheed | https://github.com/jan0sch/sylpheed.git | 2,682 From ac68c1599504d09365c9476da91f1b48345c7fe9 Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Wed, 27 Nov 2024 18:16:34 +0100 Subject: [PATCH 13/25] Analayse von Unparser erstellt und ef Startbar in Doker gemacht --- replication/unparse-views/Dockerfile | 57 +++++ replication/unparse-views/INSTALL.md | 162 +++++++++++++++ replication/unparse-views/README.md | 131 ++++++++++++ replication/unparse-views/REQUIREMENTS.md | 17 ++ replication/unparse-views/STATUS.md | 49 +++++ replication/unparse-views/build.bat | 19 ++ replication/unparse-views/build.sh | 11 + replication/unparse-views/docker/DOCKER.md | 6 + replication/unparse-views/docker/execute.sh | 17 ++ replication/unparse-views/execute.bat | 15 ++ replication/unparse-views/execute.sh | 8 + replication/unparse-views/results/.gitignore | 4 + replication/unparse-views/stop-execution.bat | 3 + replication/unparse-views/stop-execution.sh | 4 + .../experiments/views_es/Main.java | 83 ++++++++ .../experiments/views_es/UnparseAnalysis.java | 194 ++++++++++++++++++ .../views_es/UnparseEvaluation.java | 136 ++++++++++++ 17 files changed, 916 insertions(+) create mode 100644 replication/unparse-views/Dockerfile create mode 100644 replication/unparse-views/INSTALL.md create mode 100644 replication/unparse-views/README.md create mode 100644 replication/unparse-views/REQUIREMENTS.md create mode 100644 replication/unparse-views/STATUS.md create mode 100644 replication/unparse-views/build.bat create mode 100644 replication/unparse-views/build.sh create mode 100644 replication/unparse-views/docker/DOCKER.md create mode 100644 replication/unparse-views/docker/execute.sh create mode 100644 replication/unparse-views/execute.bat create mode 100644 replication/unparse-views/execute.sh create mode 100644 replication/unparse-views/results/.gitignore create mode 100644 replication/unparse-views/stop-execution.bat create mode 100644 replication/unparse-views/stop-execution.sh create mode 100644 src/main/java/org/variantsync/diffdetective/experiments/views_es/Main.java create mode 100644 src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java create mode 100644 src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseEvaluation.java diff --git a/replication/unparse-views/Dockerfile b/replication/unparse-views/Dockerfile new file mode 100644 index 00000000..ccb14066 --- /dev/null +++ b/replication/unparse-views/Dockerfile @@ -0,0 +1,57 @@ +# syntax=docker/dockerfile:1 + +FROM alpine:3.15 +# PACKAGE STAGE + +# Prepare the compile environment. JDK is automatically installed +RUN apk add maven + +# Create and navigate to a working directory +WORKDIR /home/user + +COPY local-maven-repo ./local-maven-repo + +# Copy the source code +COPY src ./src +# Copy the pom.xml if Maven is used +COPY pom.xml . +# Execute the maven package process +RUN mvn package || exit + +FROM alpine:3.15 + +# Create a user +RUN adduser --disabled-password --home /home/sherlock --gecos '' sherlock + +RUN apk add --no-cache --upgrade bash +RUN apk add --update openjdk17 + +# Change into the home directory +WORKDIR /home/sherlock + +# Copy the compiled JAR file from the first stage into the second stage +# Syntax: COPY --from=STAGE_ID SOURCE_PATH TARGET_PATH +WORKDIR /home/sherlock/holmes +COPY --from=0 /home/user/target/diffdetective-*-jar-with-dependencies.jar ./DiffDetective.jar +WORKDIR /home/sherlock +RUN mkdir results + +# Copy the setup +COPY docs holmes/docs + +# Copy the docker resources +COPY docker/* ./ +COPY replication/unparse-views/docker/* ./ +RUN mkdir DiffDetectiveMining + +# Adjust permissions +RUN chown sherlock:sherlock /home/sherlock -R +RUN chmod +x execute.sh +RUN chmod +x entrypoint.sh +RUN chmod +x fix-perms.sh + +# Set the entrypoint +ENTRYPOINT ["./entrypoint.sh", "./execute.sh"] + +# Set the user +USER sherlock \ No newline at end of file diff --git a/replication/unparse-views/INSTALL.md b/replication/unparse-views/INSTALL.md new file mode 100644 index 00000000..66c7a9fd --- /dev/null +++ b/replication/unparse-views/INSTALL.md @@ -0,0 +1,162 @@ +# Installation +## Installation Instructions +In the following, we describe how to replicate the validation from our paper (Section 5) step-by-step. +The instructions explain how to build the Docker image and run the validation in a Docker container. + +### 1. Install Docker (if required) +How to install Docker depends on your operating system: + +- _Windows or Mac_: You can find download and installation instructions [here](https://www.docker.com/get-started). +- _Linux Distributions_: How to install Docker on your system, depends on your distribution. The chances are high that Docker is part of your distributions package database. +Docker's [documentation](https://docs.docker.com/engine/install/) contains instructions for common distributions. + +Then, start the docker deamon. + +### 2. Open a Suitable Terminal +``` +# Windows Command Prompt: + - Press 'Windows Key + R' on your keyboard + - Type in 'cmd' + - Click 'OK' or press 'Enter' on your keyboard + +# Windows PowerShell: + - Open the search bar (Default: 'Windows Key') and search for 'PowerShell' + - Start the PowerShell + +# Linux: + - Press 'ctrl + alt + T' on your keyboard +``` + +Clone this repository to a directory of your choice using git: +```shell +git clone https://github.com/VariantSync/DiffDetective.git +``` +Then, navigate to the `esecfse22` folder in your local clone of this repository: +```shell +cd DiffDetective/replication/esecfse22 +``` + +### 3. Build the Docker Container +To build the Docker container you can run the `build` script corresponding to your operating system: +``` +# Windows: + .\build.bat +# Linux/Mac (bash): + ./build.sh +``` + +## 4. Verification & Replication + +### Running the Replication or Verification +To execute the replication you can run the `execute` script corresponding to your operating system with `replication` as first argument. To execute the script you first have to navigate to the `esecfse22` directory, if you have not done so. +```shell +cd DiffDetective/replication/esecfse22 +``` + +#### Windows: +`.\execute.bat replication` +#### Linux/Mac (bash): +`./execute.sh replication` + +> WARNING! +> The replication will at least require an hour and might require up to a day depending on your system. +> Therefore, we offer a short verification (5-10 minutes) which runs DiffDetective on only four of the datasets. +> You can run it by providing "verification" as argument instead of "replication" (i.e., `.\execute.bat verification`, `./execute.sh verification`). +> If you want to stop the execution, you can call the provided script for stopping the container in a separate terminal. +> When restarted, the execution will continue processing by restarting at the last unfinished repository. +> #### Windows: +> `.\stop-execution.bat` +> #### Linux/Mac (bash): +> `./stop-execution.sh` + +You might see warnings or errors reported from SLF4J like `Failed to load class "org.slf4j.impl.StaticLoggerBinder"` which you can safely ignore. +Further troubleshooting advice can be found at the bottom of this file. + +The results of the verification will be stored in the [results](results) directory. + +### Expected Output of the Verification +The aggregated results of the verification/replication can be found in the following files. +The example file content shown below should match your results when running the _verification_. +(Note that the links below only have a target _after_ running the replication or verification.) + +- The [speed statistics](results/validation/current/speedstatistics.txt) contain information about the total runtime, median runtime, mean runtime, and more: + ``` + #Commits: 24701 + Total commit process time is: 14.065916666666668min + Fastest commit process time is: d86e352859e797f6792d6013054435ae0538ef6d___xfig___0ms + Slowest commit process time is: 9838b7032ea9792bec21af424c53c07078636d21___xorg-server___7996ms + Median commit process time is: f77ffeb9b26f49ef66f77929848f2ac9486f1081___tcl___13ms + Average commit process time is: 34.166835350795516ms + ``` +- The [classification results](results/validation/current/ultimateresult.metadata.txt) contain information about how often each pattern was matched, and more. + ``` + repository: + total commits: 42323 + filtered commits: 7425 + failed commits: 0 + empty commits: 10197 + processed commits: 24701 + tree diffs: 80751 + fastestCommit: 518e205b06d0dc7a0cd35fbc2c6a4376f2959020___xorg-server___0ms + slowestCommit: 9838b7032ea9792bec21af424c53c07078636d21___xorg-server___7996ms + runtime in seconds: 853.9739999999999 + runtime with multithreading in seconds: 144.549 + treeformat: org.variantsync.diffdetective.variation.diff.serialize.treeformat.CommitDiffVariationDiffLabelFormat + nodeformat: org.variantsync.diffdetective.mining.formats.ReleaseMiningDiffNodeFormat + edgeformat: org.variantsync.diffdetective.mining.formats.DirectedEdgeLabelFormat with org.variantsync.diffdetective.mining.formats.ReleaseMiningDiffNodeFormat + analysis: org.variantsync.diffdetective.validation.PatternValidationTask + #NON nodes: 0 + #ADD nodes: 0 + #REM nodes: 0 + filtered because not (is not empty): 212 + AddToPC: { total = 443451; commits = 22470 } + AddWithMapping: { total = 51036; commits = 2971 } + RemFromPC: { total = 406809; commits = 21384 } + RemWithMapping: { total = 36622; commits = 2373 } + Specialization: { total = 7949; commits = 1251 } + Generalization: { total = 11057; commits = 955 } + Reconfiguration: { total = 3186; commits = 381 } + Refactoring: { total = 4862; commits = 504 } + Untouched: { total = 0; commits = 0 } + #Error[conditional macro without expression]: 2 + #Error[#else after #else]: 2 + #Error[#else or #elif without #if]: 11 + #Error[#endif without #if]: 12 + #Error[not all annotations closed]: 8 + ``` + +Moreover, the results comprise the (LaTeX) tables that are part of our paper and appendix. +The processing times might deviate because performance depends on your hardware. + +### (Optional) Running DiffDetective on Custom Datasets +You can also run DiffDetective on other datasets by providing the path to the dataset file as first argument to the execution script: + +#### Windows: +`.\execute.bat path\to\custom\dataset.md` +#### Linux/Mac (bash): +`./execute.sh path/to/custom/dataset.md` + +The input file must have the same format as the other dataset files (i.e., repositories are listed in a Markdown table). You can find [dataset files](../../docs/datasets/all.md) in the [docs/datasets](../../docs/datasets) folder. + +## Troubleshooting + +### 'Got permission denied while trying to connect to the Docker daemon socket' +`Problem:` This is a common problem under Linux, if the user trying to execute Docker commands does not have the permissions to do so. + +`Fix:` You can fix this problem by either following the [post-installation instructions](https://docs.docker.com/engine/install/linux-postinstall/), or by executing the scripts in the replication package with elevated permissions (i.e., `sudo`). + +### 'Unable to find image 'replication-package:latest' locally' +`Problem:` The Docker container could not be found. This either means that the name of the container that was built does not fit the name of the container that is being executed (this only happens if you changed the provided scripts), or that the Docker container was not built yet. + +`Fix:` Follow the instructions described above in the section `Build the Docker Container`. + +### No results after verification, or 'cannot create directory '../results/validation/current': Permission denied' +`Problem:` This problem can occur due to how permissions are managed inside the Docker container. More specifically, it will appear, if Docker is executed with elevated permissions (i.e., `sudo`) and if there is no [results](results) directory because it was deleted manually. In this case, Docker will create the directory with elevated permissions, and the Docker user has no permissions to access the directory. + +`Fix:` If there is a _results_ directory, delete it with elevated permission (e.g., `sudo rm -r results`). +Then, create a new _results_ directory without elevated permissions, or execute `git restore .` to restore the deleted directory. + +### Failed to load class "org.slf4j.impl.StaticLoggerBinder" +`Problem:` An operation within the initialization phase of the logger library we use (tinylog) failed. + +`Fix:` Please ignore this warning. Tinylog will fall back onto a default implementation (`Defaulting to no-operation (NOP) logger implementation`) and logging will work as expected. \ No newline at end of file diff --git a/replication/unparse-views/README.md b/replication/unparse-views/README.md new file mode 100644 index 00000000..51a15047 --- /dev/null +++ b/replication/unparse-views/README.md @@ -0,0 +1,131 @@ +ACM Artifacts Evaluated Reusable + +![Maven](https://github.com/VariantSync/DiffDetective/actions/workflows/maven.yml/badge.svg) +[![Documentation](https://img.shields.io/badge/Documentation-Read-purple)][documentation] +[![Install](https://img.shields.io/badge/Install-Instructions-blue)](INSTALL.md) +[![GitHubPages](https://img.shields.io/badge/GitHub%20Pages-online-blue.svg?style=flat)][website] +[![License](https://img.shields.io/badge/License-GNU%20LGPLv3-blue)](../../LICENSE.LGPL3) +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7110095.svg)](https://doi.org/10.5281/zenodo.7110095) + +# Classifying Edits to Variability in Source Code + +This is the replication package for our paper _Classifying Edits to Variability in Source Code_ accepted at the 30th ACM Joint European Software Engineering Conference and Symposium on the Foundations of Software Engineering (ESEC/FSE 2022). + +This replication package consists of four parts: + +1. **DiffDetective**: For our validation, we built _DiffDetective_, a java library and command-line tool to classify edits to variability in git histories of preprocessor-based software product lines. +2. **Appendix**: The appendix of our paper is given in PDF format in the file [appendix.pdf][appendix]. +3. **Haskell Formalization**: We provide an extended formalization in the Haskell programming language as described in our appendix. Its implementation can be found in the Haskell project in the [proofs](../../proofs) directory. +4. **Dataset Overview**: We provide an overview of the 44 inspected datasets with updated links to their repositories in the file [docs/datasets/all.md][dataset]. + +## 1. DiffDetective +DiffDetective is a java library and command-line tool to parse and classify edits to variability in git histories of preprocessor-based software product lines by creating [variation diffs][difftree_class] and operating on them. + +We offer a [Docker](https://www.docker.com/) setup to easily __replicate__ the validation performed in our paper. +In the following, we provide a quickstart guide for running the replication. +You can find detailed information on how to install Docker and build the container in the [INSTALL](INSTALL.md) file, including detailed descriptions of each step and troubleshooting advice. + +### Prerequisite +All following commands assume that working directory of your terminal is the `esecfse` directory. Please switch directories, if this is not the case: +```shell +cd DiffDetective/replication/esecfse22 +``` + +### 1.1 Build the Docker container +Start the docker deamon. +Clone this repository. +Open a terminal and navigate to the root directory of this repository. +To build the Docker container you can run the `build` script corresponding to your operating system. +#### Windows: +`.\build.bat` +#### Linux/Mac (bash): +`./build.sh` + +### 1.2 Start the replication +To execute the replication you can run the `execute` script corresponding to your operating system with `replication` as first argument. + +#### Windows: +`.\execute.bat replication` +#### Linux/Mac (bash): +`./execute.sh replication` + +> WARNING! +> The replication will at least require an hour and might require up to a day depending on your system. +> Therefore, we offer a short verification (5-10 minutes) which runs DiffDetective on only four of the datasets. +> You can run it by providing "verification" as argument instead of "replication" (i.e., `.\execute.bat verification`, `./execute.sh verification`). +> If you want to stop the execution, you can call the provided script for stopping the container in a separate terminal. +> When restarted, the execution will continue processing by restarting at the last unfinished repository. +> #### Windows: +> `.\stop-execution.bat` +> #### Linux/Mac (bash): +> `./stop-execution.sh` + +You might see warnings or errors reported from SLF4J like `Failed to load class "org.slf4j.impl.StaticLoggerBinder"` which you can safely ignore. +Further troubleshooting advice can be found at the bottom of the [Install](INSTALL.md) file. + +### 1.3 View the results in the [results][resultsdir] directory +All raw results are stored in the [results][resultsdir] directory. +The aggregated results can be found in the following files. +(Note that the links below only have a target _after_ running the replication or verification.) +- [speed statistics][resultsdir_speed_statistics]: contains information about the total runtime, median runtime, mean runtime, and more. +- [classification results][resultsdir_classification_results]: contains information about how often each class was found, and more. + +Moreover, the results comprise the (LaTeX) tables that are part of our paper and appendix. + +### Documentation + +DiffDetective is documented with javadoc. The documentation can be accessed on this [website][documentation]. Notable classes of our library are: +- [DiffTree](https://variantsync.github.io/DiffDetective/docs/javadoc/org/variantsync/diffdetective/diff/difftree/DiffTree.html) and [DiffNode](https://variantsync.github.io/DiffDetective/docs/javadoc/org/variantsync/diffdetective/diff/difftree/DiffNode.html) implement variation diffs from our paper. A variation diff is represented by an instance of the `DiffTree` class. It stores the root node of the diff and offers various methods to parse, traverse, and analyze variation diffs. `DiffNode`s represent individual nodes within a variation diff. +- [EditClassValidation](https://variantsync.github.io/DiffDetective/docs/javadoc/org/variantsync/diffdetective/validation/Validation.html) contains the main method for our validation. +- [ProposedEditClasses](https://variantsync.github.io/DiffDetective/docs/javadoc/org/variantsync/diffdetective/editclass/proposed/ProposedEditClasses.html) holds the catalog of the nine edit classes we proposed in our paper. It implements the interface [EditClassCatalogue](https://variantsync.github.io/DiffDetective/docs/javadoc/org/variantsync/diffdetective/editclass/EditClassCatalogue.html), which allows to define custom edit classifications. +- [BooleanAbstraction](https://variantsync.github.io/DiffDetective/docs/javadoc/org/variantsync/diffdetective/feature/BooleanAbstraction.html) contains data and methods for boolean abstraction of higher-order logic formulas. We use this for macro parsing. +- [GitDiffer](https://variantsync.github.io/DiffDetective/docs/javadoc/org/variantsync/diffdetective/diff/GitDiffer.html) may parse the history of a git repository to variation diffs. +- The [datasets](https://variantsync.github.io/DiffDetective/docs/javadoc/org/variantsync/diffdetective/datasets/package-summary.html) package contains various classes for describing and loading datasets. + +## 2. Appendix + +Our [appendix][appendix] consists of: +1. An extended formalization of our concepts in the [Haskell][haskell] programming language. The corresponding source code is also part of this replication package (see below). +2. The proofs for (a) the completeness of variation diffs to represent edits to variation trees, and (b) the completeness and unambiguity of our edit classes. +3. An inspection of edit patterns from related work to show that existing patterns are either composite patterns built from our edit classes or similar to one of our edit classes. The used diffs of these patterns can also be found in [docs/compositepatterns](../../docs/compositepatterns). +4. The complete results of our validation for all 44 datasets. + +## 3. Haskell Formalization +The extended formalization is a [Haskell][haskell] library in the [`proofs`](../../proofs) subdirectory. +Since the `proofs` library is its own software project, we provide a separate documentation of requirements and installation instructions within the projects subdirectory. +Requirements and instructions for setting up the build environment (Stack) are given in [proofs/REQUIREMENTS.md](../../proofs/REQUIREMENTS.md). +How to build our library and how to run the example is described in the [proofs/INSTALL.md](../../proofs/INSTALL.md). + + +## 4. Dataset Overview +### 4.1 Open-Source Repositories +We provide an overview of the used 44 open-source preprocessor-based software product lines in the [docs/datasets/all.md][dataset] file. +As described in our paper in Section 5.1, this list contains all systems that were studied by Liebig et al., extended by four new subject systems (Busybox, Marlin, LibSSH, Godot). +We provide updated links for each system's repository. + +### 4.2 Forked Repositories for Replication +To guarantee the exact replication of our validation, we created forks of all 44 open-source repositories at the state we performed the validation for our paper. +The forked repositories are listed in the [replication datasets](../../docs/datasets/esecfse22-replication.md) and are located at the Github user profile [DiffDetective](https://github.com/DiffDetective?tab=repositories). +These repositories are used when running the replication as described under `1.2` and in the [INSTALL](INSTALL.md). + +## 5. Running DiffDetective on Custom Datasets +You can also run DiffDetective on other datasets by providing the path to the dataset file as first argument to the execution script: + +#### Windows: +`.\execute.bat path\to\custom\dataset.md` +#### Linux/Mac (bash): +`./execute.sh path/to/custom/dataset.md` + +The input file must have the same format as the other dataset files (i.e., repositories are listed in a Markdown table). You can find [dataset files](../../docs/datasets/all.md) in the [docs/datasets](../../docs/datasets) folder. + +[difftree_class]: https://variantsync.github.io/DiffDetective/docs/javadoc/org/variantsync/diffdetective/diff/difftree/DiffTree.html +[haskell]: https://www.haskell.org/ +[dataset]: ../../docs/datasets/all.md +[appendix]: ../../appendix/appendix-esecfse22.pdf + +[documentation]: https://variantsync.github.io/DiffDetective/docs/javadoc/ +[website]: https://variantsync.github.io/DiffDetective/ + +[resultsdir]: results +[resultsdir_classification_results]: results/validation/current/ultimateresult.metadata.txt +[resultsdir_speed_statistics]: results/validation/current/speedstatistics.txt diff --git a/replication/unparse-views/REQUIREMENTS.md b/replication/unparse-views/REQUIREMENTS.md new file mode 100644 index 00000000..f20870e4 --- /dev/null +++ b/replication/unparse-views/REQUIREMENTS.md @@ -0,0 +1,17 @@ +## Hardware Requirements + +None + +## Software Requirements + +We do not require a certain operating system or prepared environment. +The setup is tested on Windows 10, WSL2, Manjaro, Ubuntu, and MacOS Monterey. + +To run DiffDetective, JDK16, and Maven are required. +Dependencies to other packages are documented in the maven build file ([pom.xml](../../pom.xml)) and are handled automatically by Maven. +Alternatively, the docker container can be used on any system supporting docker. +Docker will take care of all requirements and dependencies to replicate our validation. + +The requirements to build our `proofs` Haskell library are documented in its respective [proofs/REQUIREMENTS.md](../../proofs/REQUIREMENTS.md) file. + +[stack]: https://docs.haskellstack.org/en/stable/README/ \ No newline at end of file diff --git a/replication/unparse-views/STATUS.md b/replication/unparse-views/STATUS.md new file mode 100644 index 00000000..eaeac01c --- /dev/null +++ b/replication/unparse-views/STATUS.md @@ -0,0 +1,49 @@ +# STATUS +## Overview +The artifact for the paper _Classifying Edits to Variability in Source Code_ consists of four parts: + +1. **DiffDetective**: For our validation, we built DiffDetective, a java library and command-line tool to classify edits to variability in git histories of preprocessor-based software product lines. + DiffDetective is the main artifact used to replicate the validation of our paper (see Section 5). + DiffDetective is self-contained in that it does not require or depend on in-depth knowledge on the theoretical foundation of our work. + Practitioners and researches are free to ignore the appendix as well as the haskell formalization and may use DiffDetective out-of-the-box. +2. **Appendix**: The appendix of our paper is given in PDF format in the file [`appendix.pdf`][ddappendix]. +3. **Haskell Formalization**: We provide an extended formalization in the Haskell programming language as described in our appendix. Its implementation can be found in the Haskell project in the [`proofs`][ddproofs] directory. +4. **Dataset Overview**: We provide an overview of the 44 inspected open-source software product lines with updated links to their repositories in the file [docs/datasets/all.md][dddatasets]. + +## Purpose +Our artifact has the following purposes: + +### **Replicability** +We provide replication instructions that allow to replicate the validation we performed in Section 5 of our paper. +The replication is executed in a Docker container. To replicate our results, we also provide [forks of all 44 datasets][ddforks] in the very state we performed our validation on. + +### **Reusability** +DiffDetective is designed as a library that offers reusable functionality. +Researchers and practitioners can use our DiffDetective library to build on our theory and results (e.g., for future prototypes to study the evolution of variability in source code). + +DiffDetective offers various features, including but not limited to: +parsing variation diffs from unix diffs, obtaining variation diffs for certain patches and commits, classifying edits in variation diffs, defining custom classifications, rendering, traversing, and transforming variation diffs, various de-/serialization methods, and running analyses for the git histories of C preprocessor-based software product lines. We documented each part of the library and provide a [javadoc website][dddocumentation] within the repository. +Moreover, our validation (see _replicability_ above) may also be run on any custom dataset as described in our [README.md][ddreadme]. + +### **Extended Formal Specification** +The [`proofs`][ddproofs] Haskell project provides an extended formal specification of our theory. +Its main purpose is to document the theory and its extensions to serve as a reference for the proofs in our appendix. +Yet, the project can also be used as a library to reason on variation trees and diffs in Haskell projects. +The library is accompanied by a small demo application that shows an example test case for our proof of completeness by creating a variation diff from two variation trees and re-projecting them. +The `proofs` project is described in detail in our appendix. + +## Claims +We claim the _Artifacts Available_ badge as we made our artifacts publicly available on [Github][ddgithub] and [Zenodo][ddzenodo] with an [open-source license][ddlicense]. All [44 input datasets][ddforks] are open-source projects and publicly available. + +We claim the _Artifacts Evaluated Reusable_ badge as we implemented DiffDetective as a reusable library (see above). +Furthermore, both DiffDetective and our Haskell formalization serve as reference implementations if researchers or practitioners want to reimplement our theory in other programming languages. + +[ddgithub]: https://github.com/VariantSync/DiffDetective/tree/esecfse22 +[ddzenodo]: https://doi.org/10.5281/zenodo.6818140 +[ddreadme]: https://github.com/VariantSync/DiffDetective/tree/esecfse22/README.md +[ddappendix]: https://github.com/VariantSync/DiffDetective/raw/esecfse22/appendix.pdf +[ddproofs]: https://github.com/VariantSync/DiffDetective/tree/esecfse22/proofs +[ddlicense]: https://github.com/VariantSync/DiffDetective/blob/main/LICENSE.LGPL3 +[dddatasets]: ../../docs/datasets/all.md +[ddforks]: ../../docs/datasets/esecfse22-replication.md +[dddocumentation]: https://variantsync.github.io/DiffDetective/docs/javadoc/ diff --git a/replication/unparse-views/build.bat b/replication/unparse-views/build.bat new file mode 100644 index 00000000..9c844ee6 --- /dev/null +++ b/replication/unparse-views/build.bat @@ -0,0 +1,19 @@ +@echo off +setlocal + +set "targetSubPath=unparse-views" + +rem Get the current directory +for %%A in ("%CD%") do set "currentDir=%%~nxA" + +rem Check if the current directory ends with the target sub-path + +if "%currentDir:~-9%"=="%targetSubPath%" ( + cd ..\.. + docker build -t diff-detective-unparse -f replication\unparse-views\Dockerfile . + @pause +) else ( + echo error: the script must be run from inside the unparse-views directory, i.e., DiffDetective\replication\%targetSubPath% +) +endlocal + diff --git a/replication/unparse-views/build.sh b/replication/unparse-views/build.sh new file mode 100644 index 00000000..ae2b0d95 --- /dev/null +++ b/replication/unparse-views/build.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# We have to switch to the root directory of the project and build the Docker image from there, +# because Docker only allows access to the files in the current file system subtree (i.e., no access to ancestors). +# We have to do this to get access to 'src', 'docker', 'local-maven-repo', etc. +# For resiliency against different working directories during execution of this +# script we calculate the correct path using the special bash variable +# BASH_SOURCE. +cd "$(dirname "${BASH_SOURCE[0]}")/../.." || exit + +docker build -t diff-detective-unparse -f replication/unparse-views/Dockerfile . diff --git a/replication/unparse-views/docker/DOCKER.md b/replication/unparse-views/docker/DOCKER.md new file mode 100644 index 00000000..966ebc0d --- /dev/null +++ b/replication/unparse-views/docker/DOCKER.md @@ -0,0 +1,6 @@ +# Docker Files + +This directory contains the files that are required to run the Docker container. + +## Execution +The [`execute.sh`](execute.sh) script can be adjusted to run the program that should be executed by the Docker container. \ No newline at end of file diff --git a/replication/unparse-views/docker/execute.sh b/replication/unparse-views/docker/execute.sh new file mode 100644 index 00000000..8cc53740 --- /dev/null +++ b/replication/unparse-views/docker/execute.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [ "$1" == '' ] || [ "$1" == '--help' ] || [ "$1" == '-help' ]; then + echo ">>>>>>>>> USAGE <<<<<<<<<<" + echo "Either fully run DiffDetective with dataset from dataset file at docs/datasets/eugen-bachelor-thesis.md." + exit +fi +cd /home/sherlock || exit + cd holmes || exit + +echo "Running a the check." +java -cp DiffDetective.jar org.variantsync.diffdetective.experiments.views_es.Main + +echo "Collecting results." +cp -r results/* ../results/ +echo "The results are located in the 'results' directory." + diff --git a/replication/unparse-views/execute.bat b/replication/unparse-views/execute.bat new file mode 100644 index 00000000..278a9c70 --- /dev/null +++ b/replication/unparse-views/execute.bat @@ -0,0 +1,15 @@ +@echo off +setlocal + +set "targetSubPath=unparse-views>" + +rem Get the current directory +for %%A in ("%CD%") do set "currentDir=%%~nxA" + +rem Check if the current directory ends with the target sub-path +if "%currentDir:~-9%"=="%targetSubPath%" ( +docker run --rm -v "%cd%\results":"/home/sherlock/results" diff-detective-unparse %* +) else ( + echo error: the script must be run from inside the unparse-views directory, i.e., DiffDetective\replication\%targetSubPath% +) +endlocal diff --git a/replication/unparse-views/execute.sh b/replication/unparse-views/execute.sh new file mode 100644 index 00000000..88e01d9d --- /dev/null +++ b/replication/unparse-views/execute.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Assure that the script is only called from the unparse-views folder +cd "$(dirname "${BASH_SOURCE[0]}")" || exit + +if [[ $# -gt 0 ]]; then +echo "Executing $1" +fi +docker run --rm -v "$(pwd)/results":"/home/sherlock/results" diff-detective-unparse "$@" diff --git a/replication/unparse-views/results/.gitignore b/replication/unparse-views/results/.gitignore new file mode 100644 index 00000000..86d0cb27 --- /dev/null +++ b/replication/unparse-views/results/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/replication/unparse-views/stop-execution.bat b/replication/unparse-views/stop-execution.bat new file mode 100644 index 00000000..009494d8 --- /dev/null +++ b/replication/unparse-views/stop-execution.bat @@ -0,0 +1,3 @@ +@echo "Stopping all running simulations. This will take a moment..." +@FOR /f "tokens=*" %%i IN ('docker ps -a -q --filter "ancestor=diff-detective-unparse"') DO docker stop %%i +@echo "...done." \ No newline at end of file diff --git a/replication/unparse-views/stop-execution.sh b/replication/unparse-views/stop-execution.sh new file mode 100644 index 00000000..0ebe6a83 --- /dev/null +++ b/replication/unparse-views/stop-execution.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +echo "Stopping Docker container. This will take a moment..." +docker stop "$(docker ps -a -q --filter "ancestor=diff-detective-unparse")" +echo "...done." diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views_es/Main.java b/src/main/java/org/variantsync/diffdetective/experiments/views_es/Main.java new file mode 100644 index 00000000..025c2c0e --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/experiments/views_es/Main.java @@ -0,0 +1,83 @@ +package org.variantsync.diffdetective.experiments.views_es; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import org.variantsync.diffdetective.AnalysisRunner; +import org.variantsync.diffdetective.analysis.Analysis; +import org.variantsync.diffdetective.analysis.FilterAnalysis; +import org.variantsync.diffdetective.analysis.StatisticsAnalysis; +import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; +import org.variantsync.diffdetective.datasets.Repository; +import org.variantsync.diffdetective.diff.git.DiffFilter; +import org.variantsync.diffdetective.variation.diff.filter.VariationDiffFilter; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; + +public class Main { + + public static String dataSetPath = ""; + + public static void main(String[] args) throws IOException { + startAnalysis(); + + + } + + + private static Analysis AnalysisFactory1(Repository repo, Path repoOutputDir) { + return new Analysis( + "Unparse Analysis", + new ArrayList<>(List.of( + new FilterAnalysis( // filters unwanted trees + VariationDiffFilter.notEmpty() + ), + new UnparseAnalysis(), + new StatisticsAnalysis() + )), + repo, + repoOutputDir + ); + } + + private static void startAnalysis() throws IOException{ + final AnalysisRunner.Options analysisOptions = new AnalysisRunner.Options( + Paths.get("..", "DiffDetectiveReplicationDatasets"), + Paths.get("results","views_es" ), + Path.of("/mnt/c/Users/eshul/IdeaProjects/DiffDetective/docs/datasets","eugen-bachelor-thesis.md"), + repo -> new PatchDiffParseOptions( + PatchDiffParseOptions.DiffStoragePolicy.DO_NOT_REMEMBER, + VariationDiffParseOptions.Default + ), + repo -> new DiffFilter.Builder().allowMerge(true) + .allowedFileExtensions("c", "cpp").build(), + true, + false + ); + + /* + AnalysisRunner.run(analysisOptions, (repository, path) -> { + Analysis.forEachCommit(() -> AnalysisFactory(repository, path) + ); + }); + */ + AnalysisRunner.run(analysisOptions,extractionRunner()); + } + + protected static BiConsumer extractionRunner() { + return (repo, repoOutputDir) -> { + + + final BiFunction AnalysisFactory = + (r, out) -> new Analysis("PCAnalysis", List.of(new UnparseAnalysis()), r, out); + + Analysis.forEachCommit(() -> AnalysisFactory.apply(repo, repoOutputDir)); + + }; + } + + +} diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java new file mode 100644 index 00000000..0d4254ac --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java @@ -0,0 +1,194 @@ +package org.variantsync.diffdetective.experiments.views_es; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; +import org.variantsync.diffdetective.analysis.Analysis; +import org.variantsync.diffdetective.diff.git.PatchDiff; +import org.variantsync.diffdetective.util.CSV; +import org.variantsync.diffdetective.util.FileUtils; +import org.variantsync.diffdetective.util.IO; +import org.variantsync.diffdetective.util.StringUtils; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.VariationUnparser; +import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.construction.JGitDiff; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.tree.VariationTree; + +public class UnparseAnalysis implements Analysis.Hooks { + + public static final String VIEW_CSV_EXTENSION = ".views_es.csv"; + + private StringBuilder csv; + + @Override + public void initializeResults(Analysis analysis) { + Analysis.Hooks.super.initializeResults(analysis); + + csv = new StringBuilder(); + csv.append(UnparseEvaluation.makeHeader(CSV.DEFAULT_CSV_DELIMITER)).append(StringUtils.LINEBREAK); + } + + @Override + public boolean analyzeVariationDiff(Analysis analysis) throws Exception { + PatchDiff patch = analysis.getCurrentPatch(); + String textDiff = patch.getDiff(); + String codeBefore = ""; + String codeAfter = ""; + codeBefore = Files.readString(Path.of(patch.getFileName(Time.BEFORE))); + codeAfter = Files.readString(Path.of(patch.getFileName(Time.AFTER))); + codeBefore = codeBefore.replaceAll("\\r\\n","\n"); + codeAfter = codeAfter.replaceAll("\\r\\n","\n"); + boolean[][] diffTestAll = runTestsDiff(textDiff); + boolean[] treeBeforeTest = runTestsTree(codeBefore,Path.of(patch.getFileName(Time.BEFORE))); + boolean[] treeAfterTest = runTestsTree(codeAfter,Path.of(patch.getFileName(Time.AFTER))); + boolean[] dataTests = runDataTest(textDiff,codeBefore,codeAfter); + int error = 1; + String[] errorSave = new String[]{null,null,null}; + if(!(boolOr(diffTestAll[0]) || (boolOr(diffTestAll[1]) && boolOr(diffTestAll[2])))){ + error = error * 2; + errorSave[0] = textDiff; + } + if(!boolOr(treeBeforeTest)){ + error = error * 3; + errorSave[1] = codeBefore; + } + if(!boolOr(treeAfterTest)){ + error = error * 5; + errorSave[2] = codeAfter; + } + if(!boolOr(dataTests)){ + error = error * 7; + } + + final UnparseEvaluation ue = new UnparseEvaluation( + boolToInt(dataTests), + boolToInt(diffTestAll[0]), + boolToInt(diffTestAll[1]), + boolToInt(diffTestAll[2]), + boolToInt(treeBeforeTest), + boolToInt(treeAfterTest), + error, + errorSave + ); + csv.append(ue.toCSV()).append(StringUtils.LINEBREAK); + return Analysis.Hooks.super.analyzeVariationDiff(analysis); + } + + @Override + public void endBatch(Analysis analysis) throws IOException { + IO.write( + FileUtils.addExtension(analysis.getOutputFile(), VIEW_CSV_EXTENSION), + csv.toString() + ); + } + + public static String removeWhitespace(String string){ + return string.replaceAll("\\s+",""); + } + + public static String parseUnparseTree(Path path, VariationDiffParseOptions option){ + String temp = "b"; + try{ + VariationTree tree = VariationTree.fromFile(path,option); + temp = VariationUnparser.variationTreeUnparser(tree); + }catch (Exception e){ + e.printStackTrace(); + } + return temp; + } + + public static String parseUnparseDiff(String textDiff,VariationDiffParseOptions option){ + String temp = "b"; + try{ + VariationDiff diff = VariationDiff.fromDiff(textDiff,option); + temp = VariationUnparser.variationDiffUnparser(diff); + }catch (Exception e){ + e.printStackTrace(); + } + return temp; + } + + public static VariationDiffParseOptions optionsSetter(int i){ + if(i == 0){ + return new VariationDiffParseOptions(false,false); + }else if(i == 1){ + return new VariationDiffParseOptions(true,false); + }else if(i == 2){ + return new VariationDiffParseOptions(false,true); + }else{ + return new VariationDiffParseOptions(true,true); + } + } + + public static boolean equalsText(String text1, String text2, boolean whitespace){ + if(whitespace){ + return text1.equals(text2); + }else{ + return removeWhitespace(text1).equals(removeWhitespace(text2)); + } + } + + public static boolean[][] runTestsDiff(String text){ + boolean[][] array = new boolean[3][8]; + for (int i=0;i<4;i++){ + String diff = parseUnparseDiff(text,optionsSetter(i)); + array[0][i] = equalsText(text,diff,true); + array[0][i+4] = equalsText(text,diff,false); + array[1][i] = equalsText(VariationUnparser.undiff(text,Time.BEFORE),VariationUnparser.undiff(diff,Time.BEFORE),true); + array[1][i+4] = equalsText(VariationUnparser.undiff(text,Time.BEFORE),VariationUnparser.undiff(diff,Time.BEFORE),false); + array[2][i] = equalsText(VariationUnparser.undiff(text,Time.AFTER),VariationUnparser.undiff(diff,Time.AFTER),true); + array[2][i+4] = equalsText(VariationUnparser.undiff(text,Time.AFTER),VariationUnparser.undiff(diff,Time.AFTER),false); + } + return array; + } + + public static boolean[] runTestsTree(String text, Path path){ + boolean[] array = new boolean[8]; + for (int i=0;i<4;i++){ + String temp = parseUnparseTree(path,optionsSetter(i)); + array[i] = equalsText(text,temp,true); + array[i+4] = equalsText(text,temp,false); + } + return array; + } + + public static boolean[] runDataTest(String textDiff, String treeBefore, String treeAfter) throws IOException{ + boolean[] array = new boolean[8]; + array[0] = JGitDiff.textDiff(treeBefore,treeAfter, SupportedAlgorithm.MYERS).equals(textDiff); + array[1] = removeWhitespace(JGitDiff.textDiff(treeBefore,treeAfter, SupportedAlgorithm.MYERS)).equals(removeWhitespace(textDiff)); + array[2] = JGitDiff.textDiff(treeBefore,treeAfter, SupportedAlgorithm.HISTOGRAM).equals(textDiff); + array[3] = removeWhitespace(JGitDiff.textDiff(treeBefore,treeAfter, SupportedAlgorithm.HISTOGRAM)).equals(removeWhitespace(textDiff)); + array[4] = treeBefore.equals(VariationUnparser.undiff(textDiff,Time.BEFORE)); + array[5] = removeWhitespace(treeBefore).equals(removeWhitespace(VariationUnparser.undiff(textDiff,Time.BEFORE))); + array[6] = treeAfter.equals(VariationUnparser.undiff(textDiff,Time.AFTER)); + array[7] = removeWhitespace(treeAfter).equals(removeWhitespace(VariationUnparser.undiff(textDiff,Time.AFTER))); + return array; + } + + public static int[] boolToInt(boolean[] array){ + int[] temp = new int[array.length]; + for (int i=0;i< array.length;i++){ + if(array[i]){ + temp[i] = 1; + }else { + temp[i] = 0; + } + } + return temp; + } + + public static boolean boolOr(boolean[] array){ + for (boolean temp: array) { + if(temp){ + return true; + } + } + return false; + } + + +} diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseEvaluation.java b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseEvaluation.java new file mode 100644 index 00000000..34fdbff5 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseEvaluation.java @@ -0,0 +1,136 @@ +package org.variantsync.diffdetective.experiments.views_es; + +import static org.variantsync.functjonal.Functjonal.intercalate; + +import org.variantsync.diffdetective.util.CSV; + +public record UnparseEvaluation( + int[] dataTest, + int[] diffTest, + int[] diffBeforeTreeTest, + int[] diffAfterTreeTest, + int[] treeBeforeTest, + int[] treeAfterTest, + int error, + String[] errorSave +) implements CSV { + + @Override + public String toCSV(String delimiter){ + return intercalate(delimiter, + dataTest[0], + dataTest[1], + dataTest[2], + dataTest[3], + dataTest[4], + dataTest[5], + dataTest[6], + dataTest[7], + diffTest[0], + diffTest[1], + diffTest[2], + diffTest[3], + diffTest[4], + diffTest[5], + diffTest[6], + diffTest[7], + diffBeforeTreeTest[0], + diffBeforeTreeTest[1], + diffBeforeTreeTest[2], + diffBeforeTreeTest[3], + diffBeforeTreeTest[4], + diffBeforeTreeTest[5], + diffBeforeTreeTest[6], + diffBeforeTreeTest[7], + diffAfterTreeTest[0], + diffAfterTreeTest[1], + diffAfterTreeTest[2], + diffAfterTreeTest[3], + diffAfterTreeTest[4], + diffAfterTreeTest[5], + diffAfterTreeTest[6], + diffAfterTreeTest[7], + treeBeforeTest[0], + treeBeforeTest[1], + treeBeforeTest[2], + treeBeforeTest[3], + treeBeforeTest[4], + treeBeforeTest[5], + treeBeforeTest[6], + treeBeforeTest[7], + treeAfterTest[0], + treeAfterTest[1], + treeAfterTest[2], + treeAfterTest[3], + treeAfterTest[4], + treeAfterTest[5], + treeAfterTest[6], + treeAfterTest[7], + error, + errorSave[0], + errorSave[1], + errorSave[2] + ); + } + + public static String makeHeader(String delimiter) { + return intercalate(delimiter, + "diffFromTreesEqTextDiffMye", + "diffFromTreesEqTextDiffMyeWhite", + "diffFromTreesEqTextDiffHis", + "diffFromTreesEqTextDiffHisWhite", + "treeBefEqTreeBefDiff", + "treeBefEqTreeBefDiffWhite", + "treeAftEqTreeAftDiff", + "treeAftEqTreeAftDiffWhite", + "diffEqTestMultiL0EmptyL0", + "diffEqTestMultiL1EmptyL0", + "diffEqTestMultiL0EmptyL1", + "diffEqTestMultiL1EmptyL1", + "diffEqTestMultiL0EmptyL0White", + "diffEqTestMultiL1EmptyL0White", + "diffEqTestMultiL0EmptyL1White", + "diffEqTestMultiL1EmptyL1White", + "diffEqTestBefTreeMultiL0EmptyL0", + "diffEqTestBefTreeMultiL1EmptyL0", + "diffEqTestBefTreeMultiL0EmptyL1", + "diffEqTestBefTreeMultiL1EmptyL1", + "diffEqTestBefTreeMultiL0EmptyL0White", + "diffEqTestBefTreeMultiL1EmptyL0White", + "diffEqTestBefTreeMultiL0EmptyL1White", + "diffEqTestBefTreeMultiL1EmptyL1White", + "diffEqTestAftTreeMultiL0EmptyL0", + "diffEqTestAftTreeMultiL1EmptyL0", + "diffEqTestAftTreeMultiL0EmptyL1", + "diffEqTestAftTreeMultiL1EmptyL1", + "diffEqTestAftTreeMultiL0EmptyL0White", + "diffEqTestAftTreeMultiL1EmptyL0White", + "diffEqTestAftTreeMultiL0EmptyL1White", + "diffEqTestAftTreeMultiL1EmptyL1White", + "treeBeforeEqTestMultiL0EmptyL0", + "treeBeforeEqTestMultiL1EmptyL0", + "treeBeforeEqTestMultiL0EmptyL1", + "treeBeforeEqTestMultiL1EmptyL1", + "treeBeforeEqTestMultiL0EmptyL0White", + "treeBeforeEqTestMultiL1EmptyL0White", + "treeBeforeEqTestMultiL0EmptyL1White", + "treeBeforeEqTestMultiL1EmptyL1White", + "treeAfterEqTestMultiL0EmptyL0", + "treeAfterEqTestMultiL1EmptyL0", + "treeAfterEqTestMultiL0EmptyL1", + "treeAfterEqTestMultiL1EmptyL1", + "treeAfterEqTestMultiL0EmptyL0White", + "treeAfterEqTestMultiL1EmptyL0White", + "treeAfterEqTestMultiL0EmptyL1White", + "treeAfterEqTestMultiL1EmptyL1White", + "errorTyp", + "errorData1", + "errorData2", + "errorData3" + ); + } + + + + +} From 718866d65e6b031cbb201d43cc71b841a77052bf Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Thu, 28 Nov 2024 11:29:43 +0100 Subject: [PATCH 14/25] Analayse von Unparser repariert --- .../diffdetective/experiments/views_es/UnparseAnalysis.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java index 0d4254ac..c9e039cd 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java @@ -38,8 +38,8 @@ public boolean analyzeVariationDiff(Analysis analysis) throws Exception { String textDiff = patch.getDiff(); String codeBefore = ""; String codeAfter = ""; - codeBefore = Files.readString(Path.of(patch.getFileName(Time.BEFORE))); - codeAfter = Files.readString(Path.of(patch.getFileName(Time.AFTER))); + codeBefore = VariationUnparser.undiff(patch.getDiff(),Time.BEFORE); + codeAfter = VariationUnparser.undiff(patch.getDiff(),Time.AFTER); codeBefore = codeBefore.replaceAll("\\r\\n","\n"); codeAfter = codeAfter.replaceAll("\\r\\n","\n"); boolean[][] diffTestAll = runTestsDiff(textDiff); From 4b25def932509133e975869c34f294e64fc7639e Mon Sep 17 00:00:00 2001 From: "DARIA-ACER\\eshul" Date: Thu, 28 Nov 2024 11:56:47 +0100 Subject: [PATCH 15/25] Analayse von Unparser repariert --- .../org/variantsync/diffdetective/experiments/views/Main.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views/Main.java b/src/main/java/org/variantsync/diffdetective/experiments/views/Main.java index 668800d2..722dbdcf 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/views/Main.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/views/Main.java @@ -5,6 +5,7 @@ import org.variantsync.diffdetective.analysis.FilterAnalysis; import org.variantsync.diffdetective.analysis.StatisticsAnalysis; import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; +import org.variantsync.diffdetective.datasets.PatchDiffParseOptions.DiffStoragePolicy; import org.variantsync.diffdetective.datasets.Repository; import org.variantsync.diffdetective.variation.diff.filter.VariationDiffFilter; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; @@ -73,7 +74,7 @@ public static void main(String[] args) throws IOException { Paths.get("results", "views", "current"), defaultOptions.datasetsFile(), repo -> new PatchDiffParseOptions( - PatchDiffParseOptions.DiffStoragePolicy.DO_NOT_REMEMBER, + DiffStoragePolicy.REMEMBER_FULL_DIFF, VARIATION_DIFF_PARSE_OPTIONS ), defaultOptions.getFilterForRepo(), From 06ebe213295abc97c84291cba45dc7a0272fdaa9 Mon Sep 17 00:00:00 2001 From: eshul Date: Tue, 3 Dec 2024 16:41:47 +0100 Subject: [PATCH 16/25] =?UTF-8?q?=C3=84nderung=20der=20=C3=BCberpr=C3=BCft?= =?UTF-8?q?en=20Daten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/datasets/eugen-bachelor-thesis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/datasets/eugen-bachelor-thesis.md b/docs/datasets/eugen-bachelor-thesis.md index c24b5085..21118546 100644 --- a/docs/datasets/eugen-bachelor-thesis.md +++ b/docs/datasets/eugen-bachelor-thesis.md @@ -1,3 +1,3 @@ Project name | Domain | Source code available (\*\*y\*\*es/\*\*n\*\*o)? | Is it a git repository (\*\*y\*\*es/\*\*n\*\*o)? | Repository URL | Clone URL | Estimated number of commits -------------------|-------------------------|-------------------------------------------------|--------------------------------------------------|--------------------------------------------------------------|----------------------------------------------------|--------------------------------- -sylpheed | e-mail client | y | y | https://github.com/jan0sch/sylpheed | https://github.com/jan0sch/sylpheed.git | 2,682 +xfig | vector graphics editor | y | y | https://github.com/hhoeflin/xfig | https://github.com/hhoeflin/xfig.git | 9 \ No newline at end of file From acf895695f604f0263c31e49113fbedc70050ad3 Mon Sep 17 00:00:00 2001 From: eshul Date: Thu, 5 Dec 2024 12:24:43 +0100 Subject: [PATCH 17/25] =?UTF-8?q?=C3=84nderung=20der=20bei=20der=20Analyse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../experiments/views_es/Main.java | 107 +++++++++++++++++- .../experiments/views_es/UnparseAnalysis.java | 79 +++++++++---- .../views_es/UnparseEvaluation.java | 45 ++------ .../variation/VariationUnparser.java | 34 +++--- .../variation/tree/VariationTree.java | 18 +++ src/test/java/VariationUnparserTest.java | 36 +++++- 6 files changed, 242 insertions(+), 77 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views_es/Main.java b/src/main/java/org/variantsync/diffdetective/experiments/views_es/Main.java index 025c2c0e..43170dea 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/views_es/Main.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/views_es/Main.java @@ -1,17 +1,20 @@ package org.variantsync.diffdetective.experiments.views_es; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.BiFunction; +import java.util.stream.Stream; import org.variantsync.diffdetective.AnalysisRunner; import org.variantsync.diffdetective.analysis.Analysis; import org.variantsync.diffdetective.analysis.FilterAnalysis; import org.variantsync.diffdetective.analysis.StatisticsAnalysis; import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; +import org.variantsync.diffdetective.datasets.PatchDiffParseOptions.DiffStoragePolicy; import org.variantsync.diffdetective.datasets.Repository; import org.variantsync.diffdetective.diff.git.DiffFilter; import org.variantsync.diffdetective.variation.diff.filter.VariationDiffFilter; @@ -23,7 +26,7 @@ public class Main { public static void main(String[] args) throws IOException { startAnalysis(); - + //evaluationAnalysis(Path.of("docs","datasets","eugen-bachelor-thesis.md")); } @@ -47,9 +50,9 @@ private static void startAnalysis() throws IOException{ final AnalysisRunner.Options analysisOptions = new AnalysisRunner.Options( Paths.get("..", "DiffDetectiveReplicationDatasets"), Paths.get("results","views_es" ), - Path.of("/mnt/c/Users/eshul/IdeaProjects/DiffDetective/docs/datasets","eugen-bachelor-thesis.md"), + Path.of("docs","datasets","eugen-bachelor-thesis.md"), repo -> new PatchDiffParseOptions( - PatchDiffParseOptions.DiffStoragePolicy.DO_NOT_REMEMBER, + DiffStoragePolicy.REMEMBER_FULL_DIFF, VariationDiffParseOptions.Default ), repo -> new DiffFilter.Builder().allowMerge(true) @@ -79,5 +82,103 @@ protected static BiConsumer extractionRunner() { }; } + private static void evaluationAnalysis(Path path) throws IOException{ + int count = 0; + int error = 0; + int[] diffTest = {0,0,0,0,0,0,0,0}; + int[] diffSemEqTest = {0,0,0,0,}; + int[] treeTest = {0,0,0,0,0,0,0,0}; + List errorList = new ArrayList<>(); + String data = Files.readString(path); + String[] splitPathData = data.split("\n"); + for(int i=2; i files = Files + .list(Path.of("results","views_es",name)) + .filter(filename -> filename.getFileName().toString().endsWith(".views_es.csv")); + for (Path tempPath : files.toList()){ + String[] splitFileData = Files.readString(tempPath).split("\n"); + for(int j=1;j result = new ArrayList<>(); + result.add("Anzahl geprüfter Diffs : " + count); + result.add("Anzahl syntaktisch korrekter Diffs mit MultiLine0 und EmptyLine0 : " + diffTest[0]); + result.add("Anzahl syntaktisch korrekter Diffs mit MultiLine1 und EmptyLine0 : " + diffTest[1]); + result.add("Anzahl syntaktisch korrekter Diffs mit MultiLine0 und EmptyLine1 : " + diffTest[2]); + result.add("Anzahl syntaktisch korrekter Diffs mit MultiLine1 und EmptyLine1 : " + diffTest[3]); + result.add("Anzahl syntaktisch korrekter Diffs ohne Whitespace mit MultiLine0 und EmptyLine0 : " + diffTest[4]); + result.add("Anzahl syntaktisch korrekter Diffs ohne Whitespace mit MultiLine1 und EmptyLine0 : " + diffTest[5]); + result.add("Anzahl syntaktisch korrekter Diffs ohne Whitespace mit MultiLine0 und EmptyLine1 : " + diffTest[6]); + result.add("Anzahl syntaktisch korrekter Diffs ohne Whitespace mit MultiLine1 und EmptyLine1 : " + diffTest[7]); + result.add("Anzahl semantisch korrekter Diffs mit MultiLine0 und EmptyLine0 : " + diffSemEqTest[0]); + result.add("Anzahl semantisch korrekter Diffs mit MultiLine1 und EmptyLine0 : " + diffSemEqTest[1]); + result.add("Anzahl semantisch korrekter Diffs mit MultiLine0 und EmptyLine1 : " + diffSemEqTest[2]); + result.add("Anzahl semantisch korrekter Diffs mit MultiLine1 und EmptyLine1 : " + diffSemEqTest[3]); + result.add("Anzahl syntaktisch korrekter Trees mit MultiLine0 und EmptyLine0 : " + treeTest[0]); + result.add("Anzahl syntaktisch korrekter Trees mit MultiLine1 und EmptyLine0 : " + treeTest[1]); + result.add("Anzahl syntaktisch korrekter Trees mit MultiLine0 und EmptyLine1 : " + treeTest[2]); + result.add("Anzahl syntaktisch korrekter Trees mit MultiLine1 und EmptyLine1 : " + treeTest[3]); + result.add("Anzahl syntaktisch korrekter Trees ohne Whitespace mit MultiLine0 und EmptyLine0 : " + treeTest[4]); + result.add("Anzahl syntaktisch korrekter Trees ohne Whitespace mit MultiLine1 und EmptyLine0 : " + treeTest[5]); + result.add("Anzahl syntaktisch korrekter Trees ohne Whitespace mit MultiLine0 und EmptyLine1 : " + treeTest[6]); + result.add("Anzahl syntaktisch korrekter Trees ohne Whitespace mit MultiLine1 und EmptyLine1 : " + treeTest[7]); + result.add("Anzahl Fehler : " + error); + + Files.write(Path.of("results","views_es","resultOfAnalysis.txt"),result); + int c = 1; + for (String line : errorList){ + Files.writeString(Path.of("results","views_es","errors",c+"error.txt"),line); + c = c +1; + } + + } + } diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java index c9e039cd..cde78476 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm; import org.variantsync.diffdetective.analysis.Analysis; import org.variantsync.diffdetective.diff.git.PatchDiff; @@ -17,11 +18,14 @@ import org.variantsync.diffdetective.variation.diff.construction.JGitDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; import org.variantsync.diffdetective.variation.tree.VariationTree; +import org.variantsync.diffdetective.variation.tree.source.VariationTreeSource; public class UnparseAnalysis implements Analysis.Hooks { public static final String VIEW_CSV_EXTENSION = ".views_es.csv"; + public static int count = 0; + private StringBuilder csv; @Override @@ -38,19 +42,29 @@ public boolean analyzeVariationDiff(Analysis analysis) throws Exception { String textDiff = patch.getDiff(); String codeBefore = ""; String codeAfter = ""; + /*if(count < 10){ + Path pathTreeBefore = Paths.get("results","views_es" ,"treeBefore" + count + ".txt"); + Path pathTreeAfter = Paths.get("results","views_es" ,"treeAfter" + count + ".txt"); + Path pathDiff = Paths.get("results","views_es" ,"diff" + count + ".txt"); + Files.writeString(pathTreeBefore, codeBefore); + Files.writeString(pathTreeAfter, codeAfter); + Files.writeString(pathDiff, textDiff); + count++; + }*/ + codeBefore = VariationUnparser.undiff(patch.getDiff(),Time.BEFORE); codeAfter = VariationUnparser.undiff(patch.getDiff(),Time.AFTER); - codeBefore = codeBefore.replaceAll("\\r\\n","\n"); - codeAfter = codeAfter.replaceAll("\\r\\n","\n"); + //codeBefore = codeBefore.replaceAll("\\r\\n","\n"); + //codeAfter = codeAfter.replaceAll("\\r\\n","\n"); boolean[][] diffTestAll = runTestsDiff(textDiff); - boolean[] treeBeforeTest = runTestsTree(codeBefore,Path.of(patch.getFileName(Time.BEFORE))); - boolean[] treeAfterTest = runTestsTree(codeAfter,Path.of(patch.getFileName(Time.AFTER))); + boolean[] treeBeforeTest = runTestsTree(codeBefore); + boolean[] treeAfterTest = runTestsTree(codeAfter); boolean[] dataTests = runDataTest(textDiff,codeBefore,codeAfter); int error = 1; - String[] errorSave = new String[]{null,null,null}; - if(!(boolOr(diffTestAll[0]) || (boolOr(diffTestAll[1]) && boolOr(diffTestAll[2])))){ + String[] errorSave = new String[]{"","",""}; + if(!(boolOr(diffTestAll[0]) || boolOr(diffTestAll[1]))){ error = error * 2; - errorSave[0] = textDiff; + //errorSave[0] = ; } if(!boolOr(treeBeforeTest)){ error = error * 3; @@ -58,17 +72,16 @@ public boolean analyzeVariationDiff(Analysis analysis) throws Exception { } if(!boolOr(treeAfterTest)){ error = error * 5; - errorSave[2] = codeAfter; + //errorSave[2] = codeAfter; } - if(!boolOr(dataTests)){ + /*if(!boolOr(dataTests)){ error = error * 7; - } + }*/ final UnparseEvaluation ue = new UnparseEvaluation( boolToInt(dataTests), boolToInt(diffTestAll[0]), boolToInt(diffTestAll[1]), - boolToInt(diffTestAll[2]), boolToInt(treeBeforeTest), boolToInt(treeAfterTest), error, @@ -86,14 +99,37 @@ public void endBatch(Analysis analysis) throws IOException { ); } + public static String removeWhitespace1(String string){ + string = string.replaceAll("\n(\\s*\n)+","\n"); + string = string.replaceAll("\n\\s+", "\n"); + if(!string.isEmpty()) { + if (string.charAt(string.length() - 1) == '\n') { + return string.substring(0, string.length() - 1); + } + } + return string; + } + public static String removeWhitespace(String string){ - return string.replaceAll("\\s+",""); + if(string.isEmpty()){ + return ""; + } + else { + StringBuilder result = new StringBuilder(); + for (String line : string.split("\n")) { + if (!line.replaceAll("\\s+","").isEmpty()) { + result.append(line.trim()); + result.append("\n"); + } + } + return result.substring(0, result.length() - 1); + } } - public static String parseUnparseTree(Path path, VariationDiffParseOptions option){ + public static String parseUnparseTree(String text, VariationDiffParseOptions option){ String temp = "b"; try{ - VariationTree tree = VariationTree.fromFile(path,option); + VariationTree tree = VariationTree.fromText(text, VariationTreeSource.Unknown,option); temp = VariationUnparser.variationTreeUnparser(tree); }catch (Exception e){ e.printStackTrace(); @@ -133,23 +169,24 @@ public static boolean equalsText(String text1, String text2, boolean whitespace) } public static boolean[][] runTestsDiff(String text){ - boolean[][] array = new boolean[3][8]; + boolean[][] array = new boolean[2][8]; for (int i=0;i<4;i++){ String diff = parseUnparseDiff(text,optionsSetter(i)); array[0][i] = equalsText(text,diff,true); array[0][i+4] = equalsText(text,diff,false); - array[1][i] = equalsText(VariationUnparser.undiff(text,Time.BEFORE),VariationUnparser.undiff(diff,Time.BEFORE),true); - array[1][i+4] = equalsText(VariationUnparser.undiff(text,Time.BEFORE),VariationUnparser.undiff(diff,Time.BEFORE),false); - array[2][i] = equalsText(VariationUnparser.undiff(text,Time.AFTER),VariationUnparser.undiff(diff,Time.AFTER),true); - array[2][i+4] = equalsText(VariationUnparser.undiff(text,Time.AFTER),VariationUnparser.undiff(diff,Time.AFTER),false); + array[1][i] = ( equalsText(VariationUnparser.undiff(text,Time.BEFORE),VariationUnparser.undiff(diff,Time.BEFORE),true) + && equalsText(VariationUnparser.undiff(text,Time.AFTER),VariationUnparser.undiff(diff,Time.AFTER),true) ) + || ( equalsText(VariationUnparser.undiff(text,Time.BEFORE),VariationUnparser.undiff(diff,Time.BEFORE),false) + && equalsText(VariationUnparser.undiff(text,Time.AFTER),VariationUnparser.undiff(diff,Time.AFTER),false)); + array[1][i+4] = false; } return array; } - public static boolean[] runTestsTree(String text, Path path){ + public static boolean[] runTestsTree(String text){ boolean[] array = new boolean[8]; for (int i=0;i<4;i++){ - String temp = parseUnparseTree(path,optionsSetter(i)); + String temp = parseUnparseTree(text,optionsSetter(i)); array[i] = equalsText(text,temp,true); array[i+4] = equalsText(text,temp,false); } diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseEvaluation.java b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseEvaluation.java index 34fdbff5..8c8c5110 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseEvaluation.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseEvaluation.java @@ -7,8 +7,7 @@ public record UnparseEvaluation( int[] dataTest, int[] diffTest, - int[] diffBeforeTreeTest, - int[] diffAfterTreeTest, + int[] diffSemEqTest, int[] treeBeforeTest, int[] treeAfterTest, int error, @@ -34,22 +33,10 @@ public String toCSV(String delimiter){ diffTest[5], diffTest[6], diffTest[7], - diffBeforeTreeTest[0], - diffBeforeTreeTest[1], - diffBeforeTreeTest[2], - diffBeforeTreeTest[3], - diffBeforeTreeTest[4], - diffBeforeTreeTest[5], - diffBeforeTreeTest[6], - diffBeforeTreeTest[7], - diffAfterTreeTest[0], - diffAfterTreeTest[1], - diffAfterTreeTest[2], - diffAfterTreeTest[3], - diffAfterTreeTest[4], - diffAfterTreeTest[5], - diffAfterTreeTest[6], - diffAfterTreeTest[7], + diffSemEqTest[0], + diffSemEqTest[1], + diffSemEqTest[2], + diffSemEqTest[3], treeBeforeTest[0], treeBeforeTest[1], treeBeforeTest[2], @@ -70,7 +57,7 @@ public String toCSV(String delimiter){ errorSave[0], errorSave[1], errorSave[2] - ); + ); } public static String makeHeader(String delimiter) { @@ -91,22 +78,10 @@ public static String makeHeader(String delimiter) { "diffEqTestMultiL1EmptyL0White", "diffEqTestMultiL0EmptyL1White", "diffEqTestMultiL1EmptyL1White", - "diffEqTestBefTreeMultiL0EmptyL0", - "diffEqTestBefTreeMultiL1EmptyL0", - "diffEqTestBefTreeMultiL0EmptyL1", - "diffEqTestBefTreeMultiL1EmptyL1", - "diffEqTestBefTreeMultiL0EmptyL0White", - "diffEqTestBefTreeMultiL1EmptyL0White", - "diffEqTestBefTreeMultiL0EmptyL1White", - "diffEqTestBefTreeMultiL1EmptyL1White", - "diffEqTestAftTreeMultiL0EmptyL0", - "diffEqTestAftTreeMultiL1EmptyL0", - "diffEqTestAftTreeMultiL0EmptyL1", - "diffEqTestAftTreeMultiL1EmptyL1", - "diffEqTestAftTreeMultiL0EmptyL0White", - "diffEqTestAftTreeMultiL1EmptyL0White", - "diffEqTestAftTreeMultiL0EmptyL1White", - "diffEqTestAftTreeMultiL1EmptyL1White", + "diffSemEqTestMultiL0EmptyL0", + "diffSemEqTestMultiL1EmptyL0", + "diffSemEqTestMultiL0EmptyL1", + "diffSemEqTestMultiL1EmptyL1", "treeBeforeEqTestMultiL0EmptyL0", "treeBeforeEqTestMultiL1EmptyL0", "treeBeforeEqTestMultiL0EmptyL1", diff --git a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java index b0c02f26..71b7e280 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/VariationUnparser.java @@ -82,24 +82,30 @@ public static String variationDiffUnparser(VariationDiff diff) t } public static String undiff(String text,Time time){ - StringBuilder result = new StringBuilder(); - String[] textSplit = text.split("\n"); - if(Time.AFTER == time){ - for (String line: textSplit) { - if(line.charAt(0) != '-'){ - result.append(line.substring(1)); - result.append("\n"); - } + if(text.isEmpty()){ + return ""; + } + else { + StringBuilder result = new StringBuilder(); + String[] textSplit = text.split("\n"); + char zeichen; + if (Time.AFTER == time) { + zeichen = '-'; + } else { + zeichen = '+'; } - return result.substring(0, result.length() - 1); - }else{ - for (String line: textSplit) { - if(line.charAt(0) != '+'){ + for (String line : textSplit) { + if (line.isEmpty() ) { + result.append(line); + } + else if(line.charAt(0) != zeichen){ result.append(line.substring(1)); - result.append("\n"); } } - return result.substring(0, result.length() - 1); + if(result.isEmpty()){ + return ""; + } + return result.toString(); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java index 36cdff17..0bea42e5 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java @@ -1,5 +1,6 @@ package org.variantsync.diffdetective.variation.tree; +import java.io.StringReader; import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; import org.variantsync.diffdetective.diff.result.DiffParseException; import org.variantsync.diffdetective.util.Assert; @@ -103,6 +104,23 @@ public static VariationTree fromFile( return new VariationTree<>(tree, source); } + public static VariationTree fromText( + final String input, + final VariationTreeSource source, + final VariationDiffParseOptions parseOptions + ) throws IOException, DiffParseException { + VariationTreeNode tree = VariationDiffParser + .createVariationTree(new BufferedReader(new StringReader(input)), parseOptions) + .getRoot() + // Arbitrarily choose the BEFORE projection as both should be equal. + .projection(BEFORE) + .toVariationTree(); + + return new VariationTree<>(tree, source); + } + + + public static VariationTree fromProjection(final Projection projection, final VariationTreeSource source) { return fromVariationNode(projection, source); } diff --git a/src/test/java/VariationUnparserTest.java b/src/test/java/VariationUnparserTest.java index 603321a7..c83fe405 100644 --- a/src/test/java/VariationUnparserTest.java +++ b/src/test/java/VariationUnparserTest.java @@ -4,17 +4,26 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.experiments.views_es.UnparseAnalysis; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.Time; import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; import org.variantsync.diffdetective.variation.tree.VariationTree; import org.variantsync.diffdetective.variation.VariationUnparser; +import org.variantsync.diffdetective.variation.tree.source.VariationTreeSource; + +import static org.variantsync.diffdetective.experiments.views_es.UnparseAnalysis.removeWhitespace; public class VariationUnparserTest { + + public static void main(String[] args){ + System.out.println("Hallo"); + } private final static Path testDirTree = Constants.RESOURCE_DIR.resolve("unparser"); private final static Path testDirDiff = Constants.RESOURCE_DIR.resolve("diffs").resolve("parser"); @@ -42,16 +51,35 @@ public static Stream testsDiff() throws IOException { } @ParameterizedTest @MethodSource("testsTree") - public void testTree(Path basename) throws IOException, DiffParseException { + public void testTreeDir(Path basename) throws IOException, DiffParseException { testCaseTree(basename); } @ParameterizedTest @MethodSource("testsDiff") - public void testDiff(Path basename) throws IOException, DiffParseException { + public void testDiffDir(Path basename) throws IOException, DiffParseException { testCaseDiff(basename); } + + @Test + public void testDiff(){ + String source = ""; + String temp = ""; + try { + source = Files.readString(Path.of("src","test","resources","unparser","test9.txt")); + VariationTree tree = VariationTree.fromText(source,VariationTreeSource.Unknown,VariationDiffParseOptions.Default); + temp = VariationUnparser.variationTreeUnparser(tree); + System.out.println(removeWhitespace(source).equals(removeWhitespace(temp))); + //System.out.println(removeWhitespace(source)); + //System.out.println("Ende"); + //System.out.println(removeWhitespace(temp)); + //System.out.println("Ende"); + }catch (Exception e){ + e.printStackTrace(); + } + } + public static void testCaseTree(Path testCasePath) { String temp = ""; try { @@ -131,8 +159,8 @@ public static String parseUnparseDiff(Path path,VariationDiffParseOptions option return temp; } - public static String removeWhitespace(String string){ + /*public static String removeWhitespace(String string){ return string.replaceAll("\\s+",""); - } + }*/ } From cd762c57360e2a697dcfb8fd41445bf960e103be Mon Sep 17 00:00:00 2001 From: pmbittner Date: Thu, 5 Dec 2024 12:40:25 +0100 Subject: [PATCH 18/25] UnparseAnalysis: error reporting --- .../experiments/views_es/UnparseAnalysis.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java index cde78476..e11e65d0 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/views_es/UnparseAnalysis.java @@ -28,12 +28,17 @@ public class UnparseAnalysis implements Analysis.Hooks { private StringBuilder csv; + // for debugging + private int errorCount = 0; + @Override public void initializeResults(Analysis analysis) { Analysis.Hooks.super.initializeResults(analysis); csv = new StringBuilder(); csv.append(UnparseEvaluation.makeHeader(CSV.DEFAULT_CSV_DELIMITER)).append(StringUtils.LINEBREAK); + + errorCount = 0; } @Override @@ -68,7 +73,8 @@ public boolean analyzeVariationDiff(Analysis analysis) throws Exception { } if(!boolOr(treeBeforeTest)){ error = error * 3; - errorSave[1] = codeBefore; + reportErrorToFile(analysis, codeBefore); + //errorSave[1] = codeBefore; } if(!boolOr(treeAfterTest)){ error = error * 5; @@ -99,6 +105,14 @@ public void endBatch(Analysis analysis) throws IOException { ); } + private void reportErrorToFile(Analysis analysis, String errorMessage) throws IOException { + IO.write( + FileUtils.addExtension(analysis.getOutputFile().resolve("_error" + errorCount), ".txt"), + errorMessage + ); + ++errorCount; + } + public static String removeWhitespace1(String string){ string = string.replaceAll("\n(\\s*\n)+","\n"); string = string.replaceAll("\n\\s+", "\n"); From 047db5fd24665bd10784c7918b9ed23c8d1e9cc9 Mon Sep 17 00:00:00 2001 From: eshul Date: Sat, 21 Dec 2024 15:35:07 +0100 Subject: [PATCH 19/25] =?UTF-8?q?Das=20speichern=20von=20endif=20ber=C3=BC?= =?UTF-8?q?cksichtigt=20jetzt=20auch=20Time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diffdetective/variation/diff/DiffNode.java | 10 +++++----- .../variation/diff/Projection.java | 9 +++++---- .../diff/parse/VariationDiffParser.java | 17 ++++++++++++++--- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java index a8ff8e0d..d4d9b477 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java @@ -51,7 +51,7 @@ public class DiffNode implements HasNodeType { private Node featureMapping; - private List endIf = null; + private List[] endIf = new List[2]; /** * The parents {@link DiffNode} before and after the edit. @@ -152,16 +152,16 @@ public void setLabel(L newLabel) { * Returns the line with the endif of the corresponding if, if the node is an if node, otherwise null * @return String, the Line with endif */ - public List getEndIf() { - return endIf; + public List getEndIf(Time time) { + return endIf[time.ordinal()]; } /** * Sets the line with the endif of the corresponding if, if the node is an if node * @param endIf String, the Line with endif */ - public void setEndIf(List endIf) { - this.endIf = endIf; + public void setEndIf(List endIf,Time time) { + this.endIf[time.ordinal()] = endIf; } /** diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java b/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java index f9ac7a02..1ea63c6e 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/Projection.java @@ -51,6 +51,10 @@ public Projection upCast() { return this; } + @Override + public List getEndIf(){ + return getBackingNode().getEndIf(getTime()); + } @Override public NodeType getNodeType() { @@ -122,8 +126,5 @@ public int getID() { return getBackingNode().getID(); } - @Override - public List getEndIf() { - return getBackingNode().getEndIf(); - } + }; diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java index f6a9095f..968e1db5 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java @@ -323,7 +323,7 @@ private void parseLine( // Do not create a node for ENDIF, but update the line numbers of the closed if-chain // and remove that if-chain from the relevant stacks. diffType.forAllTimesOfExistence(beforeStack, afterStack, stack -> - popIfChain(stack, fromLine,line) + popIfChain(stack, fromLine,line,diffType) ); } else if (options.collapseMultipleCodeLines() && annotationType == AnnotationType.None @@ -368,18 +368,29 @@ private void parseLine( private void popIfChain( Stack> stack, DiffLineNumber elseLineNumber, - LogicalLine line + LogicalLine line, + DiffType diffType ) throws DiffParseException { DiffLineNumber previousLineNumber = elseLineNumber; do { DiffNode annotation = stack.peek(); + //Save endif if(annotation.isIf()){ List list = new ArrayList<>(); for (int i = 0; i < line.getLines().size();i++ ) { list.add(line.getLines().get(i).content()); } - annotation.setEndIf(list); + if(diffType.existsBefore() && diffType.existsAfter()){ + annotation.setEndIf(list,Time.BEFORE); + annotation.setEndIf(list,Time.AFTER); + }else if(diffType.existsBefore()){ + annotation.setEndIf(list,Time.BEFORE); + } + else { + annotation.setEndIf(list,Time.AFTER); + } + } // Set the line number of now closed annotations to the beginning of the From fe1319fbf2f96c7d9ab7a8ceb28e45f6c1b13326 Mon Sep 17 00:00:00 2001 From: eshul Date: Sat, 21 Dec 2024 15:36:43 +0100 Subject: [PATCH 20/25] =?UTF-8?q?F=C3=A4lle=20welche=20nicht=20korrekt=20u?= =?UTF-8?q?ngeparst=20werden=20konnten,=20aus=20der=20Auswertung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/unparser/_error0.txt | 1 + src/test/resources/unparser/diff/diff.diff | 5 + src/test/resources/unparser/diff/error0.txt | 85 + src/test/resources/unparser/diff/error00.txt | 1883 +++ src/test/resources/unparser/diff/error01.txt | 5364 +++++++ src/test/resources/unparser/diff/error010.txt | 3288 ++++ src/test/resources/unparser/diff/error02.txt | 2067 +++ src/test/resources/unparser/diff/error03.txt | 3935 +++++ src/test/resources/unparser/diff/error04.txt | 3569 +++++ src/test/resources/unparser/diff/error05.txt | 7569 +++++++++ src/test/resources/unparser/diff/error06.txt | 4293 +++++ src/test/resources/unparser/diff/error07.txt | 9768 ++++++++++++ src/test/resources/unparser/diff/error08.txt | 1230 ++ src/test/resources/unparser/diff/error09.txt | 11467 +++++++++++++ src/test/resources/unparser/diff/error1.txt | 1883 +++ src/test/resources/unparser/diff/error10.txt | 929 ++ src/test/resources/unparser/diff/error11.txt | 3288 ++++ src/test/resources/unparser/diff/error2.txt | 64 + src/test/resources/unparser/diff/error3.txt | 13302 ++++++++++++++++ src/test/resources/unparser/diff/error4.txt | 425 + src/test/resources/unparser/diff/error5.txt | 462 + src/test/resources/unparser/diff/error6.txt | 1768 ++ src/test/resources/unparser/diff/error7.txt | 3569 +++++ src/test/resources/unparser/diff/error8.txt | 4110 +++++ src/test/resources/unparser/diff/error9.txt | 333 + 25 files changed, 84657 insertions(+) create mode 100644 src/test/resources/unparser/_error0.txt create mode 100644 src/test/resources/unparser/diff/diff.diff create mode 100644 src/test/resources/unparser/diff/error0.txt create mode 100644 src/test/resources/unparser/diff/error00.txt create mode 100644 src/test/resources/unparser/diff/error01.txt create mode 100644 src/test/resources/unparser/diff/error010.txt create mode 100644 src/test/resources/unparser/diff/error02.txt create mode 100644 src/test/resources/unparser/diff/error03.txt create mode 100644 src/test/resources/unparser/diff/error04.txt create mode 100644 src/test/resources/unparser/diff/error05.txt create mode 100644 src/test/resources/unparser/diff/error06.txt create mode 100644 src/test/resources/unparser/diff/error07.txt create mode 100644 src/test/resources/unparser/diff/error08.txt create mode 100644 src/test/resources/unparser/diff/error09.txt create mode 100644 src/test/resources/unparser/diff/error1.txt create mode 100644 src/test/resources/unparser/diff/error10.txt create mode 100644 src/test/resources/unparser/diff/error11.txt create mode 100644 src/test/resources/unparser/diff/error2.txt create mode 100644 src/test/resources/unparser/diff/error3.txt create mode 100644 src/test/resources/unparser/diff/error4.txt create mode 100644 src/test/resources/unparser/diff/error5.txt create mode 100644 src/test/resources/unparser/diff/error6.txt create mode 100644 src/test/resources/unparser/diff/error7.txt create mode 100644 src/test/resources/unparser/diff/error8.txt create mode 100644 src/test/resources/unparser/diff/error9.txt diff --git a/src/test/resources/unparser/_error0.txt b/src/test/resources/unparser/_error0.txt new file mode 100644 index 00000000..3cab69de --- /dev/null +++ b/src/test/resources/unparser/_error0.txt @@ -0,0 +1 @@ +/* * TransFig: Facility for Translating Fig code * Copyright (c) 1991 by Micah Beck * Parts Copyright (c) 1985-1988 by Supoj Sutanthavibul * Parts Copyright (c) 1989-2002 by Brian V. Smith * * Any party obtaining a copy of these files is granted, free of charge, a * full and unrestricted irrevocable, world-wide, paid up, royalty-free, * nonexclusive right and license to deal in this software and * documentation files (the "Software"), including without limitation the * rights to use, copy, modify, merge, publish and/or distribute copies of * the Software, and to permit persons who receive copies from any such * party to do so, with the only requirement being that this copyright * notice remain intact. * */ /* * genps.c: PostScript driver for fig2dev * * Modified by Herbert Bauer to support ISO-Characters, * multiple page output, color mode etc. * heb@regent.e-technik.tu-muenchen.de * * Modified by Eric Picheral to support the whole set of ISO-Latin-1 * Modified by Herve Soulard to allow non-iso coding on special fonts * Herve.Soulard@inria.fr (8 Apr 1993) * * Development for new extensions at TU Darmstadt, Germany starting 2002 * Allow to "build" pictures incrementally. * To achieve this we split the complete figure into * layers in separate ps-figures. The complete figure * will be seen when overlapping all layers. * A layer is combined from adjacent depths in xfig. * This makes it possible to overlap items also when * splitting into layers. */ #include #include "fig2dev.h" #include "object.h" #include "bound.h" #include "psencode.h" #include "psfonts.h" /* for the xpm package */ #ifdef USE_XPM #include int XpmReadFileToXpmImage(); #endif /* USE_XPM */ #ifdef I18N extern Boolean support_i18n; /* enable i18n support? */ static Boolean enable_composite_font = False; #endif /* I18N */ /* for the version nubmer */ #include "../../patchlevel.h" /* include the PostScript preamble, patterns etc */ #include "genps.h" #define POINT_PER_INCH 72 #define ULIMIT_FONT_SIZE 300 /* In order that gridlines have maximum depth */ #define MAXDEPTH 999 #define min(a, b) (((a) < (b)) ? (a) : (b)) void gen_ps_eps_option(); void putword(), append(), appendhex(); static FILE *saveofile; Boolean epsflag = False; /* to distinguish PS and EPS */ Boolean pdfflag = False; /* to distinguish PDF and PS/EPS */ Boolean asciipreview = False; /* add ASCII preview? */ Boolean tiffpreview = False; /* add a TIFF preview? */ Boolean tiffcolor = False; /* color or b/w TIFF preview */ static char tmpeps[PATH_MAX]; /* temp filename for eps when adding tiff preview */ static char tmpprev[PATH_MAX]; /* temp filename for ASCII or tiff preview */ static Boolean anonymous = False; int pagewidth = -1; int pageheight = -1; int width, height; int xoff=0; int yoff=0; static double cur_thickness = 0.0; static int cur_joinstyle = 0; static int cur_capstyle = 0; int pages; int no_obj = 0; static int border_margin = 0; static float fllx, flly, furx, fury; /* arrowhead arrays */ Point bpoints[50], fpoints[50]; int nbpoints, nfpoints; Point bfillpoints[50], ffillpoints[50], clippoints[50]; int nbfillpoints, nffillpoints, nclippoints; int fpntx1, fpnty1; /* first point of object */ int fpntx2, fpnty2; /* second point of object */ int lpntx1, lpnty1; /* last point of object */ int lpntx2, lpnty2; /* second-to-last point of object */ static void fill_area(); static void clip_arrows(); static void draw_arrow(); static void encode_all_fonts(); static void set_linewidth(); static void genps_std_colors(); static void genps_usr_colors(); static Boolean iso_text_exist(); static Boolean ellipse_exist(); static Boolean approx_spline_exist(); static void draw_gridline(); static void set_style(); static void reset_style(); static void set_linejoin(); static void set_linecap(); static void convert_xpm_colors(); static void genps_itp_spline(); static void genps_ctl_spline(); #define SHADEVAL(F) 1.0*(F)/(NUMSHADES-1) #define TINTVAL(F) 1.0*(F-NUMSHADES+1)/NUMTINTS /* * Static variables for variant meps: * fig_number has the "current" figure number which has been created. * last_depth remembers the last level number processed * (we need a sufficiently large initial value) */ static int fig_number=0; static int last_depth=MAXDEPTH+4; /* define the standard 32 colors */ struct _rgb { float r, g, b; } rgbcols[NUM_STD_COLS] = { {0.00, 0.00, 0.00}, /* black */ {0.00, 0.00, 1.00}, /* blue */ {0.00, 1.00, 0.00}, /* green */ {0.00, 1.00, 1.00}, /* cyan */ {1.00, 0.00, 0.00}, /* red */ {1.00, 0.00, 1.00}, /* magenta */ {1.00, 1.00, 0.00}, /* yellow */ {1.00, 1.00, 1.00}, /* white */ {0.00, 0.00, 0.56}, /* blue1 */ {0.00, 0.00, 0.69}, /* blue2 */ {0.00, 0.00, 0.82}, /* blue3 */ {0.53, 0.81, 1.00}, /* blue4 */ {0.00, 0.56, 0.00}, /* green1 */ {0.00, 0.69, 0.00}, /* green2 */ {0.00, 0.82, 0.00}, /* green3 */ {0.00, 0.56, 0.56}, /* cyan1 */ {0.00, 0.69, 0.69}, /* cyan2 */ {0.00, 0.82, 0.82}, /* cyan3 */ {0.56, 0.00, 0.00}, /* red1 */ {0.69, 0.00, 0.00}, /* red2 */ {0.82, 0.00, 0.00}, /* red3 */ {0.56, 0.00, 0.56}, /* magenta1 */ {0.69, 0.00, 0.69}, /* magenta2 */ {0.82, 0.00, 0.82}, /* magenta3 */ {0.50, 0.19, 0.00}, /* brown1 */ {0.63, 0.25, 0.00}, /* brown2 */ {0.75, 0.38, 0.00}, /* brown3 */ {1.00, 0.50, 0.50}, /* pink1 */ {1.00, 0.63, 0.63}, /* pink2 */ {1.00, 0.75, 0.75}, /* pink3 */ {1.00, 0.88, 0.88}, /* pink4 */ {1.00, 0.84, 0.00} /* gold */ }; /* define the fill patterns */ char *fill_def[NUMPATTERNS] = { FILL_PAT01,FILL_PAT02,FILL_PAT03,FILL_PAT04, FILL_PAT05,FILL_PAT06,FILL_PAT07,FILL_PAT08, FILL_PAT09,FILL_PAT10,FILL_PAT11,FILL_PAT12, FILL_PAT13,FILL_PAT14,FILL_PAT15,FILL_PAT16, FILL_PAT17,FILL_PAT18,FILL_PAT19,FILL_PAT20, FILL_PAT21,FILL_PAT22, }; static double scalex, scaley; static double origx, origy; static double userorigx, userorigy; static double userwidthx, userwidthy; static Boolean useabsolutecoo = False; FILE *open_picfile(); void close_picfile(); static void do_split(); /* new procedure to split different depths' objects */ /* but only as comment */ int filtype; extern int read_gif(); extern int read_pcx(); extern int read_eps(); extern int read_pdf(); extern int read_ppm(); extern int read_tif(); extern int read_xbm(); #ifdef USE_PNG extern int read_png(); #endif extern int read_jpg(); /* this actually only reads the header info */ extern void JPEGtoPS(); #ifdef USE_XPM extern int read_xpm(); #endif /* headers for various image files */ static struct hdr { char *type; char *bytes; int nbytes; int (*readfunc)(); Boolean pipeok; } headers[]= { {"GIF", "GIF", 3, read_gif, False}, #ifdef V4_0 {"FIG", "#FIG", 4, read_figure, True}, #endif /* V4_0 */ {"PCX", "\012\005\001", 3, read_pcx, True}, {"EPS", "%!", 2, read_eps, True}, {"PDF", "%PDF", 4, read_pdf, True}, {"PPM", "P3", 2, read_ppm, True}, {"PPM", "P6", 2, read_ppm, True}, {"TIFF", "II*\000", 4, read_tif, False}, {"TIFF", "MM\000*", 4, read_tif, False}, {"XBM", "#define", 7, read_xbm, True}, #ifdef USE_PNG {"PNG", "\211\120\116\107\015\012\032\012", 8, read_png, True}, #endif {"JPEG", "\377\330\377\340", 4, read_jpg, True}, {"JPEG", "\377\330\377\341", 4, read_jpg, True}, #ifdef USE_XPM {"XPM", "/* XPM */", 9, read_xpm, False}, #endif }; #define NUMHEADERS sizeof(headers)/sizeof(headers[0]) /******************************/ /* various methods start here */ /******************************/ void geneps_option(opt, optarg) char opt; char *optarg; { epsflag = True; pdfflag = False; gen_ps_eps_option(opt, optarg); } void genps_option(opt, optarg) char opt; char *optarg; { epsflag = False; pdfflag = False; gen_ps_eps_option(opt, optarg); } void gen_ps_eps_option(opt, optarg) char opt; char *optarg; { int i; switch (opt) { /* don't do anything for the following args (already parsed in main) */ case 'F': /* fontsize */ case 'G': /* grid */ case 'L': /* language */ case 'm': /* magnification (already parsed in main) */ case 's': /* default font size */ case 'Z': /* max dimension */ break; case 'a': /* anonymous (don't output user name) */ anonymous = True; break; case 'A': /* add ASCII preview */ asciipreview = True; break; case 'b': /* border margin around figure */ sscanf(optarg,"%d",&border_margin); break; case 'B': /* boundingbox in absolute coordinates */ if (epsflag) { (void) strcpy (boundingbox, optarg); boundingboxspec = True; /* user-specified */ useabsolutecoo = True; } break; case 'C': /* add color TIFF preview (for MicroSloth) */ tiffpreview = True; tiffcolor = True; break; case 'c': /* center figure */ if (!epsflag) { center = True; centerspec = True; /* user-specified */ } break; case 'e': /* don't center ('e' means edge) figure */ if (!epsflag) { center = False; centerspec = True; /* user-specified */ } break; case 'f': /* default font name */ for ( i = 1; i <= MAX_PSFONT; i++ ) if ( !strcmp(optarg, PSfontnames[i]) ) break; if ( i > MAX_PSFONT ) fprintf(stderr, "warning: non-standard font name %s\n", optarg); psfontnames[0] = psfontnames[1] = optarg; PSfontnames[0] = PSfontnames[1] = optarg; break; case 'g': /* background color */ if (lookup_X_color(optarg,&background) >= 0) { bgspec = True; } else { fprintf(stderr,"Can't parse color '%s', ignoring background option\n", optarg); } break; case 'l': /* landscape mode */ if (!epsflag) { landscape = True; /* override the figure file setting */ orientspec = True; /* user-specified */ } break; case 'M': /* multi-page option */ if (!epsflag) { multi_page = True; multispec = True; /* user has overridden anything in file */ } break; case 'n': /* name to put in the "Title:" spec */ name = optarg; break; case 'N': /* convert colors to grayscale */ grayonly = True; break; case 'O': /* overlap multipage output */ overlap = True; break; case 'p': /* portrait mode */ if (!epsflag) { landscape = False; /* override the figure file setting */ orientspec = True; /* user-specified */ } break; case 'R': /* boundingbox in relative coordinates */ if (epsflag) { (void) strcpy (boundingbox, optarg); boundingboxspec = True; /* user-specified */ } break; case 'S': /* turn off multi-page option */ if (!epsflag) { multi_page = False; multispec = True; /* user has overridden anything in file */ } break; case 'T': /* add monochrome TIFF preview (for MicroSloth) */ tiffpreview = True; tiffcolor = False; break; case 'x': /* x offset on page */ if (!epsflag) { xoff = atoi(optarg); } break; case 'y': /* y offset on page */ if (!epsflag) { yoff = atoi(optarg); } break; case 'z': /* papersize */ if (!epsflag) { (void) strcpy (papersize, optarg); paperspec = True; /* user-specified */ } break; default: put_msg(Err_badarg, opt, "ps"); exit(1); } } void genps_start(objects) F_compound *objects; { char host[256]; struct passwd *who; time_t when; int itmp, jtmp; int i; int cliplx, cliply, clipux, clipuy; int userllx, userlly, userurx, userury; struct paperdef *pd; char psize[20]; char *libdir; char filename[512], str[512]; FILE *fp; /* make sure user isn't asking for both TIFF and ASCII preview */ if (tiffpreview && asciipreview) { fprintf(stderr,"Only one type of preview allowed: -A or -T/-C\n"); exit(1); } /* if the user wants a TIFF preview, route the eps file to a temporary one */ if (tiffpreview) { saveofile = tfp; /* make name for temp output file */ sprintf(tmpeps, "%s/xfig%06d.tmpeps", TMPDIR, getpid()); if ((tfp = fopen(tmpeps, "w"))==0) { fprintf(stderr,"Can't create temp file in %s\n",TMPDIR); exit(1); } } /* now that the file has been read, turn off multipage mode if eps output */ if (epsflag) multi_page = False; scalex = scaley = mag * POINT_PER_INCH / ppi; /* this seems to work around Solaris' cc optimizer bug */ /* the problem was that llx had garbage in it - this "fixes" it */ sprintf(host,"llx=%d\n",llx); /* convert to point unit */ fllx = llx * scalex; flly = lly * scaley; furx = urx * scalex; fury = ury * scaley; /* adjust for any border margin */ fllx -= border_margin; flly -= border_margin; furx += border_margin; fury += border_margin; /* convert ledger (deprecated) to tabloid */ if (strcasecmp(papersize, "ledger") == 0) strcpy(papersize, "tabloid"); for (pd = paperdef; pd->name != NULL; pd++) if (strcasecmp (papersize, pd->name) == 0) { pagewidth = pd->width; pageheight = pd->height; strcpy(papersize,pd->name); /* use the "nice" form */ break; } if (pagewidth < 0 || pageheight < 0) { (void) fprintf (stderr, "Unknown paper size `%s'\n", papersize); exit (1); } if (epsflag || pdfflag) { /* eps or pdf, shift figure to 0,0 */ origx = -fllx; origy = fury; if (epsflag && boundingboxspec) { jtmp=sscanf(boundingbox,"%lf %lf %lf %lf", &userwidthx,&userwidthy,&userorigx,&userorigy); switch (jtmp) { case 0: userwidthx=(furx-fllx)/POINT_PER_INCH; if (metric) userwidthx *= 2.54; /* now fall through and set the other user... vars */ case 1: userwidthy=(fury-flly)/POINT_PER_INCH; if (metric) userwidthy *= 2.54; /* now fall through and set the other user... vars */ case 2: /* now fall through and set the last user... var */ userorigx=0; case 3: userorigy=0; } if (userwidthx <= 0) { userwidthx=(furx-fllx)/POINT_PER_INCH; if (metric) userwidthx *= 2.54; } if (userwidthy <= 0) { userwidthy=(fury-flly)/POINT_PER_INCH; if (metric) userwidthy *= 2.54; } userorigx *= POINT_PER_INCH; userorigy *= POINT_PER_INCH; userwidthx *= POINT_PER_INCH; userwidthy *= POINT_PER_INCH; if (metric) { userorigx /= 2.54; userorigy /= 2.54; userwidthx /= 2.54; userwidthy /= 2.54; } userllx = (int) floor(userorigx); userlly = (int) floor(userorigy); userurx = (int) ceil(userorigx+userwidthx); userury = (int) ceil(userorigy+userwidthy); /* adjust for any border margin */ userllx -= border_margin; userlly -= border_margin; userurx += border_margin; userury += border_margin; if (useabsolutecoo) { userllx += origx; userurx += origx; } } } else { /* postscript, do any orientation and/or centering */ if (landscape) { itmp = pageheight; pageheight = pagewidth; pagewidth = itmp; itmp = fllx; fllx = flly; flly = itmp; itmp = furx; furx = fury; fury = itmp; } if (center) { if (landscape) { origx = (pageheight - furx - fllx)/2.0; origy = (pagewidth - fury - flly)/2.0; } else { origx = (pagewidth - furx - fllx)/2.0; origy = (pageheight + fury + flly)/2.0; } } else { origx = 0.0; origy = landscape ? 0.0 : pageheight; } } /* finally, adjust by any offset the user wants */ if (!epsflag && !pdfflag) { if (landscape) { origx += yoff; origy += xoff; } else { origx += xoff; origy += yoff; } } if (epsflag) fprintf(tfp, "%%!PS-Adobe-3.0 EPSF-3.0\n"); /* Encapsulated PostScript */ else fprintf(tfp, "%%!PS-Adobe-3.0\n"); /* PostScript magic strings */ if (gethostname(host, sizeof(host)) == -1) (void)strcpy(host, "unknown-host!?!?"); fprintf(tfp, "%%%%Title: %s\n", (name? name: ((from) ? from : "stdin"))); fprintf(tfp, "%%%%Creator: %s Version %s Patchlevel %s\n", prog, VERSION, PATCHLEVEL); (void) time(&when); fprintf(tfp, "%%%%CreationDate: %s", ctime(&when)); if ( !anonymous) { who = getpwuid(getuid()); if (who) fprintf(tfp, "%%%%For: %s@%s (%s)\n", who->pw_name, host, who->pw_gecos); } /* calc initial clipping area to size of the bounding box (this is needed for later clipping by arrowheads */ cliplx = cliply = 0; if (epsflag && !pdfflag) { /* EPS */ clipux = (int) ceil(furx-fllx); clipuy = (int) ceil(fury-flly); pages = 1; } else if (pdfflag) { /* PDF */ clipux = (int) ceil(furx-fllx); clipuy = (int) ceil(fury-flly); pages = 1; if (multi_page) { pages = (int)(1.11111*(furx-0.1*pagewidth)/pagewidth+1)* (int)(1.11111*(fury-0.1*pageheight)/pageheight+1); } } else { /* PS */ if (landscape) { clipux = pageheight; clipuy = pagewidth; /* account for overlap */ pages = (int)(1.11111*(furx-0.1*pageheight)/pageheight+1)* (int)(1.11111*(fury-0.1*pagewidth)/pagewidth+1); fprintf(tfp, "%%%%Orientation: Landscape\n"); } else { clipux = pagewidth; clipuy = pageheight; /* account for overlap */ pages = (int)(1.11111*(furx-0.1*pagewidth)/pagewidth+1)* (int)(1.11111*(fury-0.1*pageheight)/pageheight+1); fprintf(tfp, "%%%%Orientation: Portrait\n"); } } if (!epsflag || pdfflag) { /* only print Pages if PostScript or PDF */ fprintf(tfp, "%%%%Pages: %d\n", pages ); } if (!boundingboxspec) { fprintf(tfp, "%%%%BoundingBox: %d %d %d %d\n", cliplx, cliply, clipux, clipuy); /* width for tiff preview */ width = clipux-cliplx+1; height = clipuy-cliply+1; } else { fprintf(tfp, "%%%%BoundingBox: %d %d %d %d\n", userllx, userlly, userurx, userury); /* width for tiff preview */ width = userurx-userllx+1; height = userury-userlly+1; } /* only include a pagesize command if PS */ if (!epsflag && !pdfflag) { /* add comment for ghostview to recognize the page size */ /* make sure to use the lowercase paper size name */ strcpy(psize,papersize); for (i=strlen(psize)-1; i>=0; i--) psize[i] = tolower(psize[i]); fprintf(tfp, "%%%%DocumentPaperSizes: %s\n",psize); } else if (pdfflag) { /* set the page size for PDF to the figure size */ fprintf(tfp, "<< /PageSize [%d %d] >> setpagedevice\n", clipux-cliplx,clipuy-cliply); } /* put in the magnification for information purposes */ fprintf(tfp, "%%Magnification: %.4f\n",metric? mag*76.2/80.0 : mag); fprintf(tfp, "%%%%EndComments\n"); /* This %%BeginSetup .. %%EndSetup has to occur after * %%EndComments even though it includes comments, they are * not header comments. The header comment block must be * contiguous, with no non-comment lines in it. */ if (!epsflag && !pdfflag) { fprintf(tfp, "%%%%BeginSetup\n"); fprintf(tfp, "[{\n"); fprintf(tfp, "%%%%BeginFeature: *PageRegion %s\n", papersize); if (landscape) fprintf(tfp, "<> setpagedevice\n", pageheight, pagewidth); else fprintf(tfp, "<> setpagedevice\n", pagewidth, pageheight); fprintf(tfp, "%%%%EndFeature\n"); fprintf(tfp, "} stopped cleartomark\n"); fprintf(tfp, "%%%%EndSetup\n"); } /* if the user wants an ASCII preview, route the rest of the eps to a temp file */ if (asciipreview) { saveofile = tfp; /* make name for temp output file */ sprintf(tmpeps, "%s/xfig%06d.tmpeps", TMPDIR, getpid()); if ((tfp = fopen(tmpeps, "w"))==0) { fprintf(stderr,"Can't create temp file in %s\n",TMPDIR); exit(1); } } /* print any whole-figure comments prefixed with "%" */ if (objects->comments) { fprintf(tfp,"%%\n"); print_comments("% ",objects->comments, ""); fprintf(tfp,"%%\n"); } /* insert PostScript codes to select paper size, if exist */ libdir = getenv("FIG2DEV_LIBDIR"); #ifdef FIG2DEV_LIBDIR_STR if (libdir == NULL) libdir = FIG2DEV_LIBDIR_STR; #endif if (libdir != NULL) { sprintf(filename, "%s/%s.ps", libdir, papersize); /* get filename like "/usr/local/lib/fig2dev/A3.ps" and prepend it to the postscript code */ fp = fopen(filename, "rb"); if (fp != NULL) { while (fgets(str, sizeof(str), fp)) fputs(str, tfp); fclose(fp); } } fprintf(tfp,"%%%%BeginProlog\n"); if (pats_used) fprintf(tfp,"/MyAppDict 100 dict dup begin def\n"); fprintf(tfp, "%s", BEGIN_PROLOG1); /* define the standard colors */ genps_std_colors(); /* define the user colors */ genps_usr_colors(); fprintf(tfp, "\nend\n"); /* fill the Background now if specified */ if (bgspec) { fprintf(tfp, "%% Fill background color\n"); fprintf(tfp, "%d %d moveto %d %d lineto ", cliplx, cliply, clipux, cliply); fprintf(tfp, "%d %d lineto %d %d lineto\n", clipux, clipuy, cliplx, clipuy); if (grayonly) fprintf(tfp, "closepath %.2f setgray fill\n\n", rgb2luminance(background.red/65535.0, background.green/65535.0, background.blue/65535.0)); else fprintf(tfp, "closepath %.2f %.2f %.2f setrgbcolor fill\n\n", background.red/65535.0, background.green/65535.0, background.blue/65535.0); } /* translate (in multi-page mode this is done at end of this proc) */ /* (rotation and y flipping is done in %%BeginPageSetup area */ if (pats_used) { int i; /* only define the patterns that are used */ for (i=0; i 0.0) { itick = (int)(x/major)*major; /* if on a major tick, or if next minor tick is beyond major tick, make a major tick */ if (itick == x) set_linewidth(thick); else { /* not exactly on a major tick, see if next minor would be beyond a major */ ntick = (int)((x+minor)/major)*major; if (ntick < x+minor) { /* yes, draw the major */ set_linewidth(thick); draw_gridline((float) ntick, ly, (float) ntick, uy); } /* reset to draw the thin grid line */ set_linewidth(thin); } } else { set_linewidth(thin); } draw_gridline(x, ly, x, uy); } /* now the horizontal */ fprintf(tfp,"%% Horizontal\n"); for (y = ly; y <= uy; y += m) { if (major > 0.0) { itick = (int)(y/major)*major; /* if on a major tick, or if next minor tick is beyond major tick, make a major tick */ if (itick == y) set_linewidth(thick); else { /* not exactly on a major tick, see if next minor would be beyond a major */ ntick = (int)((y+minor)/major)*major; if (ntick < y+minor) { /* yes, draw the major */ set_linewidth(thick); draw_gridline(lx, (float) ntick, ux, (float) ntick); } /* reset to draw the thin grid line */ set_linewidth(thin); } } else { set_linewidth(thin); } draw_gridline(lx, y, ux, y); } /* restore original scale */ if (metric) fprintf(tfp,"gr\n"); } static void draw_gridline(x1, y1, x2, y2) float x1, y1, x2, y2; { fprintf(tfp,"n %.1f %.1f m %.1f %.1f l s\n",x1, y1, x2, y2); } int genps_end() { double dx, dy, mul; int i, page; int h, w; int epslen, tiflen; int status; struct stat fstat; /* for multipage, translate and output objects for each page */ if (multi_page) { fprintf(tfp,"%%%%EndProlog\n"); page = 1; if (overlap) mul = 0.9; else mul = 1.0; h = (landscape? pagewidth: pageheight); w = (landscape? pageheight: pagewidth); for (dy=0; (dy < (fury-h*0.1)) || (page == 1); dy += h*mul) { for (dx=0; (dx < (furx-w*0.1)) || (page == 1); dx += w*mul) { fprintf(tfp, "%%%%Page: %d %d\n",page,page); fprintf(tfp, "pageheader\n"); /* do page rotation here */ fprintf(tfp, "%%%%BeginPageSetup\n"); if (landscape) { fprintf(tfp, " 90 rot\n"); } /* increasing y goes down */ fprintf(tfp, " 1 -1 sc\n"); fprintf(tfp, "%%%%EndPageSetup\n"); fprintf(tfp, "gs\n"); if (landscape) fprintf(tfp,"%.1f %.1f tr\n", -dy, -dx); else fprintf(tfp,"%.1f %.1f tr\n", -dx, -(dy+h*mul)); fprintf(tfp, " %.3f %.3f sc\n", scalex, scaley); for (i=0; i /dev/null < /dev/null", asciipreview? "bit" : (tiffcolor? "tiff24nc": "tifflzw"), width, height, tmpprev, tmpeps); if ((status=system(gscom)) != 0) { fprintf(stderr,"Error calling ghostscript: %s\n",gscom); fprintf(stderr,"No preview will be produced\n"); /* append the eps */ append(tmpeps, tfp); /* and cancel the preview */ asciipreview = tiffpreview = False; } if (asciipreview) { width--; height--; /* now attach the preview after the prolog then attach the rest of the eps */ fprintf(tfp, "%%%%BeginPreview: %d %d %d %d\n", width, height, 1, height); appendhex(tmpprev, tfp, width, height); fprintf(tfp, "%%%%EndPreview\n"); append(tmpeps, tfp); } else if (tiffpreview) { /* now make the binary header in the final output file and append the eps and tiff files */ stat(tmpeps, &fstat); epslen = fstat.st_size; /* size of eps file */ stat(tmpprev, &fstat); tiflen = fstat.st_size; /* size of tif file */ /* write header ident C5D0D3C6 */ putc(0xC5, tfp); putc(0xD0, tfp); putc(0xD3, tfp); putc(0xC6, tfp); /* put byte offset of the EPS part (always 30 - immediately after the header) */ putword(30, tfp); /* now size of eps part */ putword(epslen, tfp); /* no Metafile */ putword(0, tfp); putword(0, tfp); /* byte offset of TIFF part */ putword(epslen+30, tfp); /* and length of TIFF part */ putword(tiflen, tfp); /* finally, FFFF (no checksum) */ putc(0xFF, tfp); putc(0xFF, tfp); /* now copy eps out */ append(tmpeps, tfp); /* and finally, the tiff file */ append(tmpprev, tfp); } /* now get rid of the tmp files */ unlink(tmpeps); unlink(tmpprev); } /* put any cleanup between %%Trailer and %EOF */ fprintf(tfp, "%%%%Trailer\n"); if (pats_used) fprintf(tfp, "end\n"); /* close off MyAppDict */ /* final DSC comment for eps output (EOF = end of document) */ fprintf(tfp, "%%EOF\n"); /* all ok */ return 0; } /* write a 32-bit value LSB first to file */ void putword(word, file) int word; FILE *file; { register int i; for (i=0; i<4; i++) { putc((unsigned char) word&0xff, file); word >>= 8; } } /* append file named in "infilename" to already open FILE "outfile" */ #define BUFLEN 4096 void append(infilename, outfile) char *infilename; FILE *outfile; { FILE *infile; char buf[BUFLEN+1]; int len; if ((infile = fopen(infilename, "r")) == 0) { fprintf(stderr,"Can't open temp file %s\n",infilename); exit(1); } while (!feof(infile)) { len = fread(buf, 1, BUFLEN, infile); fwrite(buf, len, 1, outfile); } fclose(infile); } /* read file named in "infilename", converting the binary to hex and append to already open FILE "outfile". width is the number of hex values per line that should be written and height is the number of lines total. */ void appendhex(infilename, outfile, width, height) char *infilename; FILE *outfile; int width, height; { FILE *infile; unsigned char byte; int len, i, j; if ((infile = fopen(infilename, "r")) == 0) { fprintf(stderr,"Can't open temp file %s\n",infilename); exit(1); } len = (width+7)/8; for (j=0; j 0.0) fprintf(tfp, " [%d] 0 sd\n", round(v)); } else if (s == DOTTED_LINE) { if (v > 0.0) fprintf(tfp, " [%d %d] %d sd\n", round(ppi/80.0), round(v), round(v)); } else if (s == DASH_DOT_LINE) { if (v > 0.0) fprintf(tfp, " [%d %d %d %d] 0 sd\n", round(v), round(v*0.5), round(ppi/80.0), round(v*0.5)); } else if (s == DASH_2_DOTS_LINE) { if (v > 0.0) fprintf(tfp, " [%d %d %d %d %d %d] 0 sd\n", round(v), round(v*0.45), round(ppi/80.0), round(v*0.333), round(ppi/80.0), round(v*0.45)); } else if (s == DASH_3_DOTS_LINE) { if (v > 0.0) fprintf(tfp, " [%d %d %d %d %d %d %d %d ] 0 sd\n", round(v), round(v*0.4), round(ppi/80.0), round(v*0.3), round(ppi/80.0), round(v*0.3), round(ppi/80.0), round(v*0.4)); } } static void reset_style(s, v) int s; double v; { if (s == DASH_LINE) { if (v > 0.0) fprintf(tfp, " [] 0 sd"); } else if (s == DOTTED_LINE) { if (v > 0.0) fprintf(tfp, " [] 0 sd"); } else if (s == DASH_DOT_LINE || s == DASH_2_DOTS_LINE || s == DASH_3_DOTS_LINE) { if (v > 0.0) fprintf(tfp, " [] 0 sd"); } fprintf(tfp, "\n"); } static void set_linejoin(j) int j; { if (j != cur_joinstyle) { cur_joinstyle = j; fprintf(tfp, "%d slj\n", cur_joinstyle); } } static void set_linecap(j) int j; { if (j != cur_capstyle) { cur_capstyle = j; fprintf(tfp, "%d slc\n", cur_capstyle); } } static void set_linewidth(w) double w; { if (w != cur_thickness) { cur_thickness = w; fprintf(tfp, "%.3f slw\n", cur_thickness <= THICK_SCALE ? /* make lines a little thinner */ 0.5* cur_thickness : cur_thickness - THICK_SCALE); } } static int removestr(char *buf, char *str, int *len) { int slen = strlen(str)-1; int i, found=0; char *cp = buf; while (cp=strstr(buf,str)) { *len = *len - slen; memmove(cp, cp+slen, *len-(cp-buf)); *(buf+*len) = '\0'; found = 1; } return found; } void genps_line(l) F_line *l; { F_point *p, *q; int radius; int i; FILE *picf; char buf[512], realname[PATH_MAX]; int xmin,xmax,ymin,ymax; int pic_w, pic_h, img_w, img_h; float hf_wid; do_split(l->depth); if (multi_page) fprintf(tfp, "/o%d {", no_obj++); /* print any comments prefixed with "%" */ print_comments("% ",l->comments, ""); fprintf(tfp, "%% Polyline\n"); if (l->type != T_PIC_BOX) { /* pic object has no line thickness */ set_linejoin(l->join_style); set_linecap(l->cap_style); set_linewidth((double)l->thickness); } p = l->points; q = p->next; if (q == NULL) { /* A single point line */ if (l->cap_style > 0) hf_wid = 1.0; else if (l->thickness <= THICK_SCALE) hf_wid = l->thickness/4.0; else hf_wid = (l->thickness-THICK_SCALE)/2.0; fprintf(tfp, "n %d %d m %d %d l gs col%d s gr\n", round(p->x-hf_wid), p->y, round(p->x+hf_wid), p->y, l->pen_color); if (multi_page) fprintf(tfp, "} bind def\n"); return; } if (l->type != T_PIC_BOX) { set_style(l->style, l->style_val); } xmin = xmax = p->x; ymin = ymax = p->y; while (p->next != NULL) { /* find lower left and upper right corners */ p=p->next; if (xmin > p->x) xmin = p->x; else if (xmax < p->x) xmax = p->x; if (ymin > p->y) ymin = p->y; else if (ymax < p->y) ymax = p->y; } if (l->type == T_ARC_BOX) { /* ARC BOX */ radius = l->radius; /* radius of the corner */ /* limit the radius to the smaller of the two sides or postscript crashes */ /* from T.Sato */ if ((xmax - xmin) / 2 < radius) radius = (xmax - xmin) / 2; if ((ymax - ymin) / 2 < radius) radius = (ymax - ymin) / 2; fprintf(tfp, "n %d %d m",xmin+radius, ymin); fprintf(tfp, " %d %d %d %d %d arcto 4 {pop} repeat\n", xmin, ymin, xmin, ymax-radius, radius); fprintf(tfp, " %d %d %d %d %d arcto 4 {pop} repeat\n", /* arc through bl to br */ xmin, ymax, xmax-radius, ymax, radius); fprintf(tfp, " %d %d %d %d %d arcto 4 {pop} repeat\n", /* arc through br to tr */ xmax, ymax, xmax, ymin+radius, radius); fprintf(tfp, " %d %d %d %d %d arcto 4 {pop} repeat\n", /* arc through tr to tl */ xmax, ymin, xmin+radius, ymin, radius); } else if (l->type == T_PIC_BOX) { /* imported picture */ /* PICTURE OBJECT */ int dx, dy, rotation; int pllx, plly, purx, pury; int i, j; Boolean found; int c; dx = l->points->next->next->x - l->points->x; dy = l->points->next->next->y - l->points->y; rotation = 0; if (dx < 0 && dy < 0) rotation = 180; else if (dx < 0 && dy >= 0) rotation = 90; else if (dy < 0 && dx >= 0) rotation = 270; fprintf(tfp, "%%\n"); fprintf(tfp, "%% pen to black in case this eps object doesn't set color first\n"); if (grayonly) fprintf(tfp, "0 setgray\n"); else fprintf(tfp, "0 0 0 setrgbcolor\n"); /* open the file and read a few bytes of the header to see what it is */ if ((picf=open_picfile(l->pic->file, &filtype, True, realname)) == NULL) { fprintf(stderr,"No such picture file: %s\n",l->pic->file); return; } for (i=0; i<15; i++) { if ((c=getc(picf))==EOF) break; buf[i]=(char) c; } close_picfile(picf,filtype); /* now find which header it is */ for (i=0; i=0; j--) if (buf[j] != headers[i].bytes[j]) { found = False; break; } if (found) break; } if (found) { if (headers[i].pipeok) { /* open it again (it may be a pipe so we can't just rewind) */ picf=open_picfile(l->pic->file, &filtype, headers[i].pipeok, realname); /* and read it */ if (((*headers[i].readfunc)(picf,filtype,l->pic,&pllx,&plly)) == 0) { fprintf(stderr,"%s: Bad %s format\n",l->pic->file, headers[i].type); close_picfile(picf,filtype); return; /* problem, return */ } /* close file */ close_picfile(picf,filtype); } else { /* routines that can't take a pipe (e.g. xpm) get the real filename */ if (((*headers[i].readfunc)(realname,filtype,l->pic,&pllx,&plly)) == 0) { fprintf(stderr,"%s: Bad %s format\n",l->pic->file, headers[i].type); return; /* problem, return */ } } /* Successful read */ } else { /* none of the above */ fprintf(stderr,"%s: Unknown image format\n",l->pic->file); return; } /* if we have any of the following pic types, we need the ps encoder */ if ((l->pic->subtype == P_XPM || l->pic->subtype == P_PCX || l->pic->subtype == P_PNG) && !psencode_header_done) PSencode_header(); /* if we have a GIF with a transparent color, we need the transparentimage code */ /* Actually, the GIF has been changed to PCX, but we still have the information */ if (l->pic->subtype == P_PCX && l->pic->transp != -1 && !transp_header_done) PStransp_header(); /* width, height of image bits (unrotated) */ img_w = l->pic->bit_size.x; img_h = l->pic->bit_size.y; /* calc upper-right from size and lower-left */ /* pllx, plly may not be (0,0) from some image formats */ purx = img_w+pllx; pury = img_h+plly; fprintf(tfp, "n gs\n"); /* pic_w, pic_h are the width, height of the Fig pic object, possibly rotated */ if (((rotation == 90 || rotation == 270) && !l->pic->flipped) || (rotation != 90 && rotation != 270 && l->pic->flipped)) { pic_w = pury - plly; pic_h = purx - pllx; } else { pic_w = purx - pllx; pic_h = pury - plly; } /* translate the pic stuff to the right spot on the page */ fprintf(tfp, "%d %d tr\n", xmin, ymin); /* scale the pic stuff to fit into the bounding box */ /* Note: the origin for fig is in the upper-right corner; * for postscript its in the lower right hand corner. * To fix it, we use a "negative"-y scale factor, then * translate the image up on the page */ fprintf(tfp, "%f %f sc\n", fabs((double)(xmax-xmin)/pic_w), -1.0*(double)(ymax-ymin)/pic_h); /* flip the pic stuff */ /* always translate it back so that the lower-left corner is at the origin */ /* note: fig measures rotation clockwise; postscript is counter-clockwise */ /* always translate it back so that the lower-left corner is at the origin */ switch (rotation) { case 0: if (l->pic->flipped) { fprintf(tfp, "%d 0 tr\n", pic_w); fprintf(tfp, "%d rot\n", 270); fprintf(tfp, "1 -1 sc\n"); } else { fprintf(tfp, "0 %d tr\n", -pic_h); } break; case 90: if (l->pic->flipped) { fprintf(tfp, "%d %d tr\n", pic_w, -pic_h); fprintf(tfp, "-1 1 sc\n"); } else { fprintf(tfp, "%d rot\n", 270); } break; case 180: if (l->pic->flipped) { fprintf(tfp, "0 %d tr\n", -pic_h); fprintf(tfp, "%d rot\n", 270); fprintf(tfp, "-1 1 sc\n"); } else { fprintf(tfp, "%d 0 tr\n", pic_w); fprintf(tfp, "%d rot\n", 180); } break; case 270: if (l->pic->flipped) { fprintf(tfp, "1 -1 sc\n"); } else { fprintf(tfp, "%d %d tr\n", pic_w, -pic_h); fprintf(tfp, "%d rot\n", 90); } break; } /* translate the pic stuff so that the lower-left corner is at the origin */ fprintf(tfp, "%d %d tr\n", -pllx, -plly); /* save vm so pic file won't change anything */ fprintf(tfp, "sa\n"); /* if PIC object is EPS file, set up clipping rectangle to BB * and prepare to clean up stacks and dicts of included EPS file */ if (l->pic->subtype == P_EPS) { fprintf(tfp, "n %d %d m %d %d l %d %d l %d %d l cp clip n\n", pllx,plly, purx,plly, purx,pury, pllx,pury); fprintf(tfp, "countdictstack\n"); fprintf(tfp, "mark\n"); /* if user wants grayscale (-N) then redefine setrgbcolor to setgray in imported figure */ if (grayonly) fprintf(tfp,"/setrgbcolor { 0.11 mul exch 0.59 mul add exch 0.3 mul add setgray} def\n"); } /* and undefine showpage */ fprintf(tfp, "/showpage {} def\n"); /* XBM file */ if (l->pic->subtype == P_XBM) { unsigned char *bit; int cwid; fprintf(tfp, "col%d\n ", l->pen_color); fprintf(tfp, "%% Bitmap image follows:\n"); /* scale for size in bits */ fprintf(tfp, "%d %d sc\n", purx, pury); fprintf(tfp, "/pix %d string def\n", (int)((purx+7)/8)); /* width, height and paint 0 bits */ fprintf(tfp, "%d %d false\n", purx, pury); /* transformation matrix */ fprintf(tfp, "[%d 0 0 %d 0 %d]\n", purx, -pury, pury); /* function for reading bits */ fprintf(tfp, "{currentfile pix readhexstring pop}\n"); /* use imagemask to draw in color */ fprintf(tfp, "imagemask\n"); bit = l->pic->bitmap; cwid = 0; for (i=0; i= 80) { fprintf(tfp,"\n"); cwid=0; } } fprintf(tfp,"\n"); } #ifdef USE_XPM /* XPM file */ } else if (l->pic->subtype == P_XPM) { XpmColor *coltabl; unsigned char *cdata, *cp; unsigned int *dp; /* start with width and height */ img_w = l->pic->xpmimage.width; img_h = l->pic->xpmimage.height; fprintf(tfp, "%% Pixmap image follows:\n"); /* scale for size in bits */ fprintf(tfp, "%d %d sc\n", purx, pury); /* modify colortable entries to make consistent */ coltabl = l->pic->xpmimage.colorTable; /* convert the colors to rgb constituents */ convert_xpm_colors(l->pic->cmap,coltabl,l->pic->xpmimage.ncolors); /* and convert the integer data to unsigned char */ dp = l->pic->xpmimage.data; if ((cdata = (unsigned char *) malloc(img_w*img_h*sizeof(unsigned char))) == NULL) { fprintf(stderr,"can't allocate space for XPM image\n"); return; } cp = cdata; for (i=0; ipic->xpmimage.ncolors, l->pic->cmap[RED], l->pic->cmap[GREEN], l->pic->cmap[BLUE], cdata); /* and free up the space */ free(cdata); XpmFreeXpmImage(&l->pic->xpmimage); #endif /* USE_XPM */ /* GIF, PCX, PNG, or JPEG file */ } else if (l->pic->subtype == P_GIF || l->pic->subtype == P_PCX || l->pic->subtype == P_JPEG || l->pic->subtype == P_PNG) { if (l->pic->subtype == P_GIF) fprintf(tfp, "%% GIF image follows:\n"); else if (l->pic->subtype == P_PCX) fprintf(tfp, "%% PCX image follows:\n"); else if (l->pic->subtype == P_PNG) fprintf(tfp, "%% PNG image follows:\n"); else fprintf(tfp, "%% JPEG image follows:\n"); /* scale for size in bits */ fprintf(tfp, "%d %d sc\n", purx, pury); if (l->pic->subtype == P_JPEG) { /* now actually read and format the jpeg file for PS */ JPEGtoPS(l->pic->file, tfp); } else { /* GIF, PNG and PCX */ if (l->pic->numcols > 256) { /* 24-bit image, write rgb values */ (void) PSrgbimage(tfp, img_w, img_h, l->pic->bitmap); } else { /* now write out the image data in a compressed form */ (void) PSencode(img_w, img_h, l->pic->transp, l->pic->numcols, l->pic->cmap[RED], l->pic->cmap[GREEN], l->pic->cmap[BLUE], l->pic->bitmap); } } /* EPS file */ } else if (l->pic->subtype == P_EPS) { int len; fprintf(tfp, "%% EPS file follows:\n"); if ((picf=open_picfile(l->pic->file, &filtype, True, realname)) == NULL) { fprintf(stderr, "Unable to open EPS file '%s': error: %s\n", l->pic->file, strerror(errno)); fprintf(tfp, "gr\n"); return; } /* use read/write() calls in case of binary data! */ /* but flush buffer first */ fflush(tfp); while ((len = read(fileno(picf),buf,sizeof(buf))) > 0) { /* remove any %EOF or %%EOF in file */ while (removestr(buf,"\n%EOF\n",&len) != 0) ; while (removestr(buf,"\n%%EOF\n",&len) != 0) ; write(fileno(tfp),buf,len); } close_picfile(picf,filtype); } /* if PIC object is EPS file, clean up stacks and dicts * before 'restore'ing vm */ if (l->pic->subtype == P_EPS) { fprintf(tfp, "\ncleartomark\n"); fprintf(tfp, "countdictstack exch sub { end } repeat\n"); } /* restore vm and gsave */ fprintf(tfp, "restore grestore\n"); fprintf(tfp, "%%\n"); fprintf(tfp, "%% End Imported PIC File: %s\n", l->pic->file); if (l->pic->subtype == P_EPS) fprintf(tfp, "%%%%EndDocument\n"); fprintf(tfp, "%%\n"); } else { /* POLYLINE */ p = l->points; q = p->next; /* first point */ fpntx1 = p->x; fpnty1 = p->y; /* second point */ fpntx2 = q->x; fpnty2 = q->y; /* go through the points to get the last two */ while (q->next != NULL) { p = q; q = q->next; } /* next to last point */ lpntx2 = p->x; lpnty2 = p->y; /* last point */ lpntx1 = q->x; lpnty1 = q->y; /* set clipping for any arrowheads */ if (l->for_arrow || l->back_arrow) { fprintf(tfp, "gs "); clip_arrows(l, O_POLYLINE); } /* now output the points */ p = l->points; q = p->next; fprintf(tfp, "n %d %d m", p->x, p->y); i=1; while (q->next != NULL) { p = q; q = q->next; fprintf(tfp, " %d %d l", p->x, p->y); if (i%5 == 0) fprintf(tfp, "\n"); i++; } fprintf(tfp, "\n"); } /* now fill it, draw the line and/or draw arrow heads */ if (l->type != T_PIC_BOX) { /* make sure it isn't a picture object */ if (l->type == T_POLYLINE) { fprintf(tfp, " %d %d l ", q->x, q->y); if (fpntx1==lpntx1 && fpnty1==lpnty1) fprintf(tfp, " cp "); /* endpoints are coincident, close path so that line join is used */ } else { fprintf(tfp, " cp "); /* polygon, close path */ } /* fill it if there is a fill style */ if (l->fill_style != UNFILLED) fill_area(l->fill_style, l->pen_color, l->fill_color, xmin, ymin); /* stroke if there is a line thickness */ if (l->thickness > 0) fprintf(tfp, "gs col%d s gr ", l->pen_color); /* reset clipping */ if (l->type == T_POLYLINE && ((l->for_arrow || l->back_arrow))) fprintf(tfp,"gr\n"); reset_style(l->style, l->style_val); if (l->back_arrow && l->thickness > 0) draw_arrow(l, l->back_arrow, bpoints, nbpoints, bfillpoints, nbfillpoints, l->pen_color); if (l->for_arrow && l->thickness > 0) draw_arrow(l, l->for_arrow, fpoints, nfpoints, ffillpoints, nffillpoints, l->pen_color); } if (multi_page) fprintf(tfp, "} bind def\n"); } void genps_spline(s) F_spline *s; { do_split(s->depth); if (multi_page) fprintf(tfp, "/o%d {", no_obj++); /* print any comments prefixed with "%" */ print_comments("% ",s->comments, ""); if (closed_spline(s)) { if (s->style == DOTTED_LINE) set_linecap(1); /* round dots for dotted line */ } else { set_linecap(s->cap_style); /* open splines can explicitely set capstyle */ } /* set the line thickness */ set_linewidth((double)s->thickness); if (int_spline(s)) genps_itp_spline(s); else genps_ctl_spline(s); if (multi_page) fprintf(tfp, "} bind def\n"); } static void genps_itp_spline(s) F_spline *s; { F_point *p, *q; F_control *a, *b; int xmin, ymin; fprintf(tfp, "%% Interp Spline\n"); a = s->controls; a = s->controls; p = s->points; /* first point */ fpntx1 = p->x; fpnty1 = p->y; /* second point */ fpntx2 = round(a->rx); fpnty2 = round(a->ry); /* go through the points to find the last two */ for (q = p->next; q != NULL; p = q, q = q->next) { b = a->next; a = b; } /* next to last point */ lpntx2 = round(b->lx); lpnty2 = round(b->ly); /* last point */ lpntx1 = p->x; lpnty1 = p->y; /* set clipping for any arrowheads */ fprintf(tfp, "gs "); if (s->for_arrow || s->back_arrow) clip_arrows(s, O_SPLINE); a = s->controls; p = s->points; set_style(s->style, s->style_val); fprintf(tfp, "n %d %d m\n", p->x, p->y); xmin = 999999; ymin = 999999; for (q = p->next; q != NULL; p = q, q = q->next) { xmin = min(xmin, p->x); ymin = min(ymin, p->y); b = a->next; fprintf(tfp, "\t%.1f %.1f %.1f %.1f %d %d curveto\n", a->rx, a->ry, b->lx, b->ly, q->x, q->y); a = b; } if (closed_spline(s)) fprintf(tfp, " cp "); if (s->fill_style != UNFILLED) fill_area(s->fill_style, s->pen_color, s->fill_color, xmin, ymin); if (s->thickness > 0) fprintf(tfp, " gs col%d s gr\n", s->pen_color); /* reset clipping */ fprintf(tfp," gr\n"); reset_style(s->style, s->style_val); /* draw arrowheads after spline for open arrow */ if (s->back_arrow && s->thickness > 0) draw_arrow(s, s->back_arrow, bpoints, nbpoints, bfillpoints, nbfillpoints, s->pen_color); if (s->for_arrow && s->thickness > 0) draw_arrow(s, s->for_arrow, fpoints, nfpoints, ffillpoints, nffillpoints, s->pen_color); } static void genps_ctl_spline(s) F_spline *s; { double a, b, c, d, x1, y1, x2, y2, x3, y3; F_point *p, *q; int xmin, ymin; if (closed_spline(s)) fprintf(tfp, "%% Closed spline\n"); else fprintf(tfp, "%% Open spline\n"); p = s->points; x1 = p->x; y1 = p->y; p = p->next; c = p->x; d = p->y; x3 = a = (x1 + c) / 2; y3 = b = (y1 + d) / 2; /* first point */ fpntx1 = round(x1); fpnty1 = round(y1); /* second point */ fpntx2 = round(x3); fpnty2 = round(y3); /* in case there are only two points in this spline */ x2=x1; y2=y1; /* go through the points to find the last two */ for (q = p->next; q != NULL; p = q, q = q->next) { x1 = x3; y1 = y3; x2 = c; y2 = d; c = q->x; d = q->y; x3 = (x2 + c) / 2; y3 = (y2 + d) / 2; } /* next to last point */ lpntx2 = round(x2); lpnty2 = round(y2); /* last point */ lpntx1 = round(c); lpnty1 = round(d); /* set clipping for any arrowheads */ fprintf(tfp, "gs "); if (s->for_arrow || s->back_arrow) clip_arrows(s, O_SPLINE); /* now output the points */ set_style(s->style, s->style_val); xmin = 999999; ymin = 999999; p = s->points; x1 = p->x; y1 = p->y; p = p->next; c = p->x; d = p->y; x3 = a = (x1 + c) / 2; y3 = b = (y1 + d) / 2; /* in case there are only two points in this spline */ x2=x1; y2=y1; if (closed_spline(s)) fprintf(tfp, "n %.1f %.1f m\n", a, b); else fprintf(tfp, "n %.1f %.1f m %.1f %.1f l\n", x1, y1, x3, y3); for (q = p->next; q != NULL; p = q, q = q->next) { xmin = min(xmin, p->x); ymin = min(ymin, p->y); x1 = x3; y1 = y3; x2 = c; y2 = d; c = q->x; d = q->y; x3 = (x2 + c) / 2; y3 = (y2 + d) / 2; fprintf(tfp, "\t%.1f %.1f %.1f %.1f %.1f %.1f DrawSplineSection\n", x1, y1, x2, y2, x3, y3); } /* * At this point, (x2,y2) and (c,d) are the position of the * next-to-last and last point respectively, in the point list */ if (closed_spline(s)) { fprintf(tfp, "\t%.1f %.1f %.1f %.1f %.1f %.1f DrawSplineSection closepath ", x3, y3, c, d, a, b); } else { fprintf(tfp, "\t%.1f %.1f l ", c, d); } if (s->fill_style != UNFILLED) fill_area(s->fill_style, s->pen_color, s->fill_color, xmin, ymin); if (s->thickness > 0) fprintf(tfp, " gs col%d s gr\n", s->pen_color); /* reset clipping */ fprintf(tfp," gr\n"); reset_style(s->style, s->style_val); /* draw arrowheads after spline */ if (s->back_arrow && s->thickness > 0) draw_arrow(s, s->back_arrow, bpoints, nbpoints, bfillpoints, nbfillpoints, s->pen_color); if (s->for_arrow && s->thickness > 0) draw_arrow(s, s->for_arrow, fpoints, nfpoints, ffillpoints, nffillpoints, s->pen_color); } void genps_arc(a) F_arc *a; { double angle1, angle2, dx, dy, radius; double cx, cy, sx, sy, ex, ey; int direction; do_split(a->depth); /* print any comments prefixed with "%" */ print_comments("% ",a->comments, ""); fprintf(tfp, "%% Arc\n"); if (multi_page) fprintf(tfp, "/o%d {", no_obj++); cx = a->center.x; cy = a->center.y; sx = a->point[0].x; sy = a->point[0].y; ex = a->point[2].x; ey = a->point[2].y; direction = a->direction; set_linewidth((double)a->thickness); set_linecap(a->cap_style); dx = cx - sx; dy = cy - sy; radius = sqrt(dx*dx+dy*dy); if (cx==sx) angle1 = (sy-cy > 0? 90.0: -90.0); else angle1 = atan2(sy-cy, sx-cx) * 180.0 / M_PI; if (cx==ex) angle2 = (ey-cy > 0? 90.0: -90.0); else angle2 = atan2(ey-cy, ex-cx) * 180.0 / M_PI; /* workaround for arcs with start point = end point; make angles slightly different */ if (fabs(angle1 - angle2) < 0.001) angle2 = angle1 + 0.01; if ((a->type == T_OPEN_ARC) && (a->thickness != 0) && (a->back_arrow || a->for_arrow)) { /* set clipping for any arrowheads */ fprintf(tfp, "gs "); if (a->for_arrow || a->back_arrow) clip_arrows(a, O_ARC); } set_style(a->style, a->style_val); /* draw the arc now */ /* direction = 1 -> Counterclockwise */ fprintf(tfp, "n %.1f %.1f %.1f %.4f %.4f %s\n", cx, cy, radius, angle1, angle2, ((direction == 1) ? "arcn" : "arc")); if (a->type == T_PIE_WEDGE_ARC) fprintf(tfp,"%.1f %.1f l %.1f %.1f l ",cx,cy,sx,sy); /****** The upper-left values (dx, dy) aren't really correct so ******/ /****** the fill pattern alignment between a filled arc and other ******/ /****** filled objects will not be correct ******/ if (a->fill_style != UNFILLED) fill_area(a->fill_style, a->pen_color, a->fill_color, (int)dx, (int)dy); if (a->thickness > 0) fprintf(tfp, "gs col%d s gr\n", a->pen_color); if ((a->type == T_OPEN_ARC) && (a->thickness != 0) && (a->back_arrow || a->for_arrow)) { /* reset clipping */ fprintf(tfp," gr\n"); } reset_style(a->style, a->style_val); /* now draw the arrowheads, if any */ if (a->type == T_OPEN_ARC) { if (a->back_arrow && a->thickness > 0) draw_arrow(a, a->back_arrow, bpoints, nbpoints, bfillpoints, nbfillpoints, a->pen_color); if (a->for_arrow && a->thickness > 0) draw_arrow(a, a->for_arrow, fpoints, nfpoints, ffillpoints, nffillpoints, a->pen_color); } if (multi_page) fprintf(tfp, "} bind def\n"); } void genps_ellipse(e) F_ellipse *e; { do_split(e->depth); fprintf(tfp, "%% Ellipse\n"); /* print any comments prefixed with "%" */ print_comments("% ",e->comments, ""); if (multi_page) fprintf(tfp, "/o%d {", no_obj++); set_linewidth((double)e->thickness); set_style(e->style, e->style_val); if (e->style == DOTTED_LINE) set_linecap(1); /* round dots */ if (e->angle == 0) { fprintf(tfp, "n %d %d %d %d 0 360 DrawEllipse ", e->center.x, e->center.y, e->radiuses.x, e->radiuses.y); } else { fprintf(tfp, "gs\n"); fprintf(tfp, "%d %d tr\n",e->center.x, e->center.y); fprintf(tfp, "%6.3f rot\n",-e->angle*180.0/M_PI); fprintf(tfp, "n 0 0 %d %d 0 360 DrawEllipse ", e->radiuses.x, e->radiuses.y); /* rotate back so any fill pattern will come out correct */ fprintf(tfp, "%6.3f rot\n",e->angle*180.0/M_PI); } if (e->fill_style != UNFILLED) fill_area(e->fill_style, e->pen_color, e->fill_color, e->center.x - e->radiuses.x, e->center.y - e->radiuses.y); if (e->thickness > 0) fprintf(tfp, "gs col%d s gr\n", e->pen_color); if (e->angle != 0) fprintf(tfp, "gr\n"); reset_style(e->style, e->style_val); if (multi_page) fprintf(tfp, "} bind def\n"); } #define TEXT_PS "\ /%s%s ff %.2f scf sf\n\ " void genps_text(t) F_text *t; { unsigned char *cp; #ifdef I18N #define LINE_LENGTH_LIMIT 200 Boolean composite = False; Boolean state_gr = False; int chars = 0; int gr_chars = 0; unsigned char ch; #endif /* I18N */ do_split(t->depth); /* ignore hidden text (new for xfig3.2.3/fig2dev3.2.3) */ if (hidden_text(t)) return; if (multi_page) fprintf(tfp, "/o%d {", no_obj++); /* print any comments prefixed with "%" */ print_comments("% ",t->comments, ""); #ifdef I18N if (enable_composite_font && ((t->flags & PSFONT_TEXT) ? (t->font <= 0 || t->font == 2) : (t->font <= 2))) { composite = True; if (t->font <= 0) fprintf(tfp, TEXT_PS, "CompositeRoman", "", PSFONTMAG(t)); else fprintf(tfp, TEXT_PS, "CompositeBold", "", PSFONTMAG(t)); } else #endif /* I18N */ if (PSisomap[t->font+1] == True) fprintf(tfp, TEXT_PS, PSFONT(t), "-iso", PSFONTMAG(t)); else fprintf(tfp, TEXT_PS, PSFONT(t), "", PSFONTMAG(t)); fprintf(tfp, "%d %d m\ngs ", t->base_x, t->base_y); fprintf(tfp, "1 -1 sc "); if (t->angle != 0.0) fprintf(tfp, " %.1f rot ", t->angle*180.0/M_PI); /* this loop escapes characters '(', ')', and '\' */ fputc('(', tfp); #ifdef I18N for(cp = (unsigned char *)t->cstring; *cp; cp++) { if (LINE_LENGTH_LIMIT < chars) { fputs("\\\n", tfp); chars = 0; } ch = *cp; if (enable_composite_font && composite) { if (ch & 0x80) { /* GR */ if (!state_gr) { fprintf(tfp, "\\377\\001"); chars += 8; state_gr = True; gr_chars = 0; } gr_chars++; } else { /* GL */ if (state_gr) { if (gr_chars % 2) { fprintf(stderr, "warning: incomplete multi-byte text: %s\n", t->cstring); fputc('?', tfp); } fprintf(tfp, "\\377\\000"); chars += 8; state_gr = False; } } } if (strchr("()\\", ch)) { fputc('\\', tfp); chars += 1; } if (ch>=0x80) { fprintf(tfp,"\\%o", ch); chars += 4; } else { fputc(ch, tfp); chars += 1; } } if (enable_composite_font && composite && state_gr) { if (gr_chars % 2) { fprintf(stderr, "warning: incomplete multi-byte text: %s\n", t->cstring); fputc('?', tfp); } } #else for(cp = (unsigned char *)t->cstring; *cp; cp++) { if (strchr("()\\", *cp)) fputc('\\', tfp); if (*cp>=0x80) fprintf(tfp,"\\%o", *cp); else fputc(*cp, tfp); } #endif /* I18N */ fputc(')', tfp); if ((t->type == T_CENTER_JUSTIFIED) || (t->type == T_RIGHT_JUSTIFIED)){ fprintf(tfp, " dup sw pop "); if (t->type == T_CENTER_JUSTIFIED) fprintf(tfp, "2 div "); fprintf(tfp, "neg 0 rm "); } else if ((t->type != T_LEFT_JUSTIFIED) && (t->type != DEFAULT)) fprintf(stderr, "Text incorrectly positioned\n"); fprintf(tfp, " col%d sh gr\n", t->color); if (multi_page) fprintf(tfp, "} bind def\n"); } /* draw arrow from the points array */ static void draw_arrow(obj, arrow, points, npoints, fillpoints, nfillpoints, col) F_line *obj; F_arrow *arrow; Point *points, *fillpoints; int npoints, nfillpoints; int col; { int i, type; fprintf(tfp,"%% arrowhead\n"); set_linecap(0); /* butt line cap for arrowheads */ set_linejoin(0); /* miter join for sharp points */ set_linewidth(arrow->thickness); fprintf(tfp, "n "); for (i=0; itype; if (type != 0 && type != 6 && type < 13) /* old heads, close the path */ fprintf(tfp, " cp "); if (type == 0) { /* stroke */ fprintf(tfp, " col%d s\n",col); } else { if (arrow->style == 0 && nfillpoints == 0) { /* hollow, fill with white */ fill_area(NUMSHADES-1, WHITE_COLOR, WHITE_COLOR, 0, 0); /* stroke */ fprintf(tfp, " col%d s\n",col); } else { if (nfillpoints == 0) { if (type < 13) { if (arrow->style == 0) fill_area(NUMSHADES-1, WHITE_COLOR, WHITE_COLOR, 0, 0); /* fill with white */ else fill_area(NUMSHADES-1, col, col, 0, 0); /* fill with color */ } /* stroke */ fprintf(tfp, " col%d s\n",col); } else { /* special fill, first fill whole head with white */ fill_area(NUMSHADES-1, WHITE_COLOR, WHITE_COLOR, 0, 0); /* stroke */ fprintf(tfp, " col%d s\n",col); /* now describe the special fill area */ fprintf(tfp, "n "); for (i=0; ifor_arrow) { if (objtype == O_ARC) { F_arc *a = (F_arc *) obj; /* last point */ lpntx1 = a->point[2].x; lpnty1 = a->point[2].y; compute_arcarrow_angle(a->center.x, a->center.y, (double) lpntx1, (double) lpnty1, a->direction, a->for_arrow, &lpntx2, &lpnty2); } calc_arrow(lpntx2, lpnty2, lpntx1, lpnty1, obj->thickness, obj->for_arrow, fpoints, &nfpoints, ffillpoints, &nffillpoints, clippoints, &nclippoints); /* set the clipping area */ for (i=nclippoints-1; i>=0; i--) { fprintf(tfp,"%d %d %c ",clippoints[i].x,clippoints[i].y, i==nclippoints-1? 'm': 'l'); } fprintf(tfp, "cp\n"); } /* get points for any backward arrowhead */ if (obj->back_arrow) { if (objtype == O_ARC) { F_arc *a = (F_arc *) obj; /* first point */ fpntx1 = a->point[0].x; fpnty1 = a->point[0].y; compute_arcarrow_angle(a->center.x, a->center.y, (double) fpntx1, (double) fpnty1, a->direction ^ 1, a->back_arrow, &fpntx2, &fpnty2); } calc_arrow(fpntx2, fpnty2, fpntx1, fpnty1, obj->thickness, obj->back_arrow, bpoints, &nbpoints, bfillpoints, &nbfillpoints, clippoints, &nclippoints); /* set the clipping area */ for (i=nclippoints-1; i>=0; i--) { fprintf(tfp,"%d %d %c ",clippoints[i].x,clippoints[i].y, i==nclippoints-1? 'm': 'l'); } fprintf(tfp, "cp\n"); } /* intersect the arrowhead clip path(s) with current clip path */ /* use eoclip so that the intersection with the current path guarantees the correct clip path */ fprintf(tfp, "eoclip\n"); } /* uses eofill (even/odd rule fill) */ /* ulx and uly define the upper-left corner of the object for pattern alignment */ static void fill_area(fill, pen_color, fill_color, ulx, uly) int fill, pen_color, fill_color, ulx, uly; { float pen_r, pen_g, pen_b, fill_r, fill_g, fill_b; /* get the rgb values for the fill pattern (if necessary) */ if (fill_color < NUM_STD_COLS) { fill_r=rgbcols[fill_color>0? fill_color: 0].r; fill_g=rgbcols[fill_color>0? fill_color: 0].g; fill_b=rgbcols[fill_color>0? fill_color: 0].b; } else { fill_r=user_colors[fill_color-NUM_STD_COLS].r/255.0; fill_g=user_colors[fill_color-NUM_STD_COLS].g/255.0; fill_b=user_colors[fill_color-NUM_STD_COLS].b/255.0; } if (pen_color < NUM_STD_COLS) { pen_r=rgbcols[pen_color>0? pen_color: 0].r; pen_g=rgbcols[pen_color>0? pen_color: 0].g; pen_b=rgbcols[pen_color>0? pen_color: 0].b; } else { pen_r=user_colors[pen_color-NUM_STD_COLS].r/255.0; pen_g=user_colors[pen_color-NUM_STD_COLS].g/255.0; pen_b=user_colors[pen_color-NUM_STD_COLS].b/255.0; } if (fill_color <= 0 && fill < NUMSHADES+NUMTINTS) /* use gray levels for default and black shades and tints */ fprintf(tfp, "gs %.2f setgray ef gr ", 1.0 - SHADEVAL(fill)); else if (fill < NUMSHADES) /* a shaded color (not black) */ fprintf(tfp, "gs col%d %.2f shd ef gr ", fill_color, SHADEVAL(fill)); else if (fill < NUMSHADES+NUMTINTS) /* a tint */ fprintf(tfp, "gs col%d %.2f tnt ef gr ", fill_color, TINTVAL(fill)); else { /* one of the patterns */ int patnum = fill-NUMSHADES-NUMTINTS+1; char colorspace[13], pencolor[25], fillcolor[25]; if (grayonly) { float grayfill, graypen; grayfill = rgb2luminance(fill_r, fill_g, fill_b); graypen = rgb2luminance(pen_r, pen_g, pen_b); sprintf(colorspace, "/DeviceGray"); sprintf(fillcolor, "%.2f", grayfill); sprintf(pencolor, "%.2f", graypen); } else { sprintf(colorspace, "/DeviceRGB"); sprintf(fillcolor, "%.2f %.2f %.2f", fill_r, fill_g, fill_b); sprintf(pencolor, "%.2f %.2f %.2f", pen_r, pen_g, pen_b); } fprintf(tfp, "\n%% Fill with pattern background color\n"); fprintf(tfp, "gs %s setcolorspace %s setcolor fill gr\n", colorspace, fillcolor); fprintf(tfp, "\n%% Fill with pattern pen color\n"); fprintf(tfp, "gs %s setcolorspace %s P%d setpattern fill gr\n\n", colorspace, pencolor, patnum); } } /* define standard colors as "col##" where ## is the number */ static void genps_std_colors() { int i; for (i=0; itexts != NULL) { for (t = ob->texts; t != NULL; t = t->next) { /* look for any ISO (non-ASCII) chars in non-special text except for pstex */ if (!strcmp(lang,"pstex") && special_text(t)) continue; for (s = (unsigned char*)t->cstring; *s != '\0'; s++) { /* look for characters >= 128 or ASCII '-' */ if ((*s>127) || (*s=='-')) return True; } } } for (c = ob->compounds; c != NULL; c = c->next) { if (iso_text_exist(c)) return True; } return False; } static void encode_all_fonts(ob) F_compound *ob; { F_compound *c; F_text *t; if (ob->texts != NULL) { for (t = ob->texts; t != NULL; t = t->next) if (PSisomap[t->font+1] == False) { fprintf(tfp, "/%s /%s-iso isovec ReEncode\n", PSFONT(t), PSFONT(t)); PSisomap[t->font+1] = True; } } for (c = ob->compounds; c != NULL; c = c->next) { encode_all_fonts(c); } } static Boolean ellipse_exist(ob) F_compound *ob; { F_compound *c; if (NULL != ob->ellipses) return True; for (c = ob->compounds; c != NULL; c = c->next) { if (ellipse_exist(c)) return True; } return False; } static Boolean approx_spline_exist(ob) F_compound *ob; { F_spline *s; F_compound *c; for (s = ob->splines; s != NULL; s = s->next) { if (approx_spline(s)) return True; } for (c = ob->compounds; c != NULL; c = c->next) { if (approx_spline_exist(c)) return True; } return False; } #ifdef USE_XPM /* lookup the named colors referenced in the colortable passed */ /* total colors in the table are "ncols" */ /* This is called from the XPM image import section above */ /* lookup color names and return rgb values from X11 RGB database file (e.g. /usr/lib/X11/rgb.XXX) */ static void convert_xpm_colors(cmap, coltabl, ncols) unsigned char cmap[3][MAXCOLORMAPSIZE]; XpmColor *coltabl; int ncols; { int i; char *name; RGB rgb; /* look through each entry in the colortable for the named colors */ for (i=0; ic_color; /* get the rgb values from the name */ if (lookup_X_color(name, &rgb) < 0) fprintf(stderr,"Can't parse color '%s', using black.\n",name); cmap[RED][i] = (unsigned char) (rgb.red>>8); cmap[GREEN][i] = (unsigned char) (rgb.green>>8); cmap[BLUE][i] = (unsigned char) (rgb.blue>>8); /* if user wants grayscale (-N) then pass through ppmtopgm too */ if (grayonly) cmap[RED][i] = cmap[GREEN][i] = cmap[BLUE][i] = (int) (rgb2luminance(cmap[RED][i]/255.0, cmap[GREEN][i]/255.0, cmap[BLUE][i]/255.0)*255.0); } } #endif /* USE_XPM */ /* * We must start new figure if the current * depth and the last_depth differ by more than one. * Depths will be seen with decreasing values. * Only comments will be output */ void do_split(actual_depth) int actual_depth; { if (actual_depth+1 < last_depth) { /* depths differ by more than one */ if (fig_number > 0) { /* end the current figure, if we already had one */ fprintf(tfp,"%% here ends figure;\n"); } if (actual_depth >= 0) { /* start a new figure with a comment */ fprintf(tfp,"%% \n"); fprintf(tfp,"%% here starts figure with depth %d\n",actual_depth); fig_number++; /* reset cur_values for multi-postscript. So a new image gets values being set */ /* This forces the procs that emit the codes to reset their current values * because these sections of code may be rearranged */ cur_thickness = -1; cur_capstyle = -1; cur_joinstyle = -1; } } last_depth = actual_depth; } /* driver defs */ struct driver dev_ps = { genps_option, genps_start, genps_grid, genps_arc, genps_ellipse, genps_line, genps_spline, genps_text, genps_end, INCLUDE_TEXT }; /* eps is just like ps except with no: pages, pagesize, orientation, offset */ struct driver dev_eps = { geneps_option, genps_start, genps_grid, genps_arc, genps_ellipse, genps_line, genps_spline, genps_text, genps_end, INCLUDE_TEXT }; \ No newline at end of file diff --git a/src/test/resources/unparser/diff/diff.diff b/src/test/resources/unparser/diff/diff.diff new file mode 100644 index 00000000..0b03ff3d --- /dev/null +++ b/src/test/resources/unparser/diff/diff.diff @@ -0,0 +1,5 @@ + #if A + Code ++#endif /* G_OS_WIN32 */ +-#endif /* WIN32 */ + diff --git a/src/test/resources/unparser/diff/error0.txt b/src/test/resources/unparser/diff/error0.txt new file mode 100644 index 00000000..932e09f2 --- /dev/null +++ b/src/test/resources/unparser/diff/error0.txt @@ -0,0 +1,85 @@ ++/* ++ * TransFig: Facility for Translating Fig code ++ * Copyright (c) 1985 Supoj Sutantavibul ++ * Copyright (c) 1991 Micah Beck ++ * Parts Copyright (c) 1989-2002 by Brian V. Smith ++ * ++ * Any party obtaining a copy of these files is granted, free of charge, a ++ * full and unrestricted irrevocable, world-wide, paid up, royalty-free, ++ * nonexclusive right and license to deal in this software and ++ * documentation files (the "Software"), including without limitation the ++ * rights to use, copy, modify, merge, publish and/or distribute copies of ++ * the Software, and to permit persons who receive copies from any such ++ * party to do so, with the only requirement being that this copyright ++ * notice remain intact. ++ */ ++ ++#include "alloc.h" ++#include "fig2dev.h" ++#include "object.h" ++ ++static double forward_arrow_wid = 4; ++static double forward_arrow_ht = 8; ++static int forward_arrow_type = 0; ++static int forward_arrow_style = 0; ++static double forward_arrow_thickness = 1; ++ ++static double backward_arrow_wid = 4; ++static double backward_arrow_ht = 8; ++static int backward_arrow_type = 0; ++static int backward_arrow_style = 0; ++static double backward_arrow_thickness = 1; ++ ++F_arrow * ++forward_arrow() ++{ ++ F_arrow *a; ++ ++ if (NULL == (Arrow_malloc(a))) { ++ put_msg(Err_mem); ++ return(NULL); ++ } ++ a->type = forward_arrow_type; ++ a->style = forward_arrow_style; ++ a->thickness = forward_arrow_thickness*THICK_SCALE; ++ a->wid = forward_arrow_wid; ++ a->ht = forward_arrow_ht; ++ return(a); ++ } ++ ++F_arrow * ++backward_arrow() ++{ ++ F_arrow *a; ++ ++ if (NULL == (Arrow_malloc(a))) { ++ put_msg(Err_mem); ++ return(NULL); ++ } ++ a->type = backward_arrow_type; ++ a->style = backward_arrow_style; ++ a->thickness = backward_arrow_thickness*THICK_SCALE; ++ a->wid = backward_arrow_wid; ++ a->ht = backward_arrow_ht; ++ return(a); ++ } ++ ++F_arrow * ++make_arrow(type, style, thickness, wid, ht) ++int type, style; ++double thickness, wid, ht; ++{ ++ F_arrow *a; ++ ++ if (NULL == (Arrow_malloc(a))) { ++ put_msg(Err_mem); ++ return(NULL); ++ } ++ a->type = type; ++ a->style = style; ++ a->thickness = thickness*THICK_SCALE; ++ a->wid = wid; ++ a->ht = ht; ++ return(a); ++ } + diff --git a/src/test/resources/unparser/diff/error00.txt b/src/test/resources/unparser/diff/error00.txt new file mode 100644 index 00000000..0395c9dc --- /dev/null +++ b/src/test/resources/unparser/diff/error00.txt @@ -0,0 +1,1883 @@ +textDiff: + /* + * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client +- * Copyright (C) 1999-2010 Hiroyuki Yamamoto ++ * Copyright (C) 1999-2011 Hiroyuki Yamamoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + #ifdef HAVE_CONFIG_H + # include "config.h" + #endif + + #include "defs.h" + + #include + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include + #include + #include + #include + #ifdef G_OS_UNIX + # include + #endif + + #if HAVE_LOCALE_H + # include + #endif + + #if USE_GPGME + # include + #endif + + #include "main.h" + #include "mainwindow.h" + #include "folderview.h" + #include "summaryview.h" + #include "prefs_common.h" + #include "prefs_account.h" + #include "prefs_actions.h" + #include "prefs_display_header.h" + #include "account.h" + #include "account_dialog.h" + #include "procmsg.h" + #include "filter.h" + #include "send_message.h" + #include "inc.h" + #include "manage_window.h" + #include "alertpanel.h" + #include "inputdialog.h" + #include "statusbar.h" + #include "addressbook.h" + #include "addrindex.h" + #include "compose.h" + #include "logwindow.h" + #include "folder.h" + #include "setup.h" + #include "sylmain.h" + #include "utils.h" + #include "gtkutils.h" + #include "socket.h" + #include "stock_pixmap.h" + #include "trayicon.h" + #include "plugin.h" + #include "plugin_manager.h" + #include "foldersel.h" + #include "update_check.h" + #include "colorlabel.h" + + #if USE_GPGME + # include "rfc2015.h" + #endif + #if USE_SSL + # include "ssl.h" + # include "sslmanager.h" + #endif + + #ifdef G_OS_WIN32 + # include + # include + # include + # include + #endif + + #include "version.h" + + gchar *prog_version; + + #ifdef G_OS_WIN32 + static gboolean init_console_done = FALSE; + #endif + + static gint lock_socket = -1; + static gint lock_socket_tag = 0; + static GIOChannel *lock_ch = NULL; + static gchar *instance_id = NULL; + + #if USE_THREADS + static GThread *main_thread; + #endif + + static struct RemoteCmd { + gboolean receive; + gboolean receive_all; + gboolean compose; + const gchar *compose_mailto; + GPtrArray *attach_files; + gboolean send; + gboolean status; + gboolean status_full; + GPtrArray *status_folders; + GPtrArray *status_full_folders; + gchar *open_msg; + gboolean configdir; + gboolean exit; + gboolean restart; + gchar *argv0; + #ifdef G_OS_WIN32 + gushort ipcport; + #endif + } cmd; + + #define STATUSBAR_PUSH(mainwin, str) \ + { \ + gtk_statusbar_push(GTK_STATUSBAR(mainwin->statusbar), \ + mainwin->mainwin_cid, str); \ + gtkut_widget_draw_now(mainwin->statusbar); \ + } + + #define STATUSBAR_POP(mainwin) \ + { \ + gtk_statusbar_pop(GTK_STATUSBAR(mainwin->statusbar), \ + mainwin->mainwin_cid); \ + } + +-#ifdef G_OS_WIN32 ++#if defined(G_OS_WIN32) || defined(__APPLE__) + static void fix_font_setting (void); + #endif + + static void parse_cmd_opt (int argc, + char *argv[]); + + static void app_init (void); + static void parse_gtkrc_files (void); + static void setup_rc_dir (void); + static void check_gpg (void); + static void set_log_handlers (gboolean enable); + static void register_system_events (void); + static void plugin_init (void); + + static gchar *get_socket_name (void); + static gint prohibit_duplicate_launch (void); + static gint lock_socket_remove (void); + static gboolean lock_socket_input_cb (GIOChannel *source, + GIOCondition condition, + gpointer data); + + static void remote_command_exec (void); + static void migrate_old_config (void); + + static void open_compose_new (const gchar *address, + GPtrArray *attach_files); + static void open_message (const gchar *path); + + static void send_queue (void); + + #define MAKE_DIR_IF_NOT_EXIST(dir) \ + { \ + if (!is_dir_exist(dir)) { \ + if (is_file_exist(dir)) { \ + alertpanel_warning \ + (_("File `%s' already exists.\n" \ + "Can't create folder."), \ + dir); \ + exit(1); \ + } \ + if (make_dir(dir) < 0) \ + exit(1); \ + } \ + } + + #define CHDIR_EXIT_IF_FAIL(dir, val) \ + { \ + if (change_dir(dir) < 0) \ + exit(val); \ + } + + static void load_cb(GObject *obj, GModule *module, gpointer data) + { + debug_print("load_cb: %p (%s), %p\n", module, module ? g_module_name(module) : "(null)", data); + } + + int main(int argc, char *argv[]) + { + MainWindow *mainwin; + FolderView *folderview; + GdkPixbuf *icon; + #ifdef G_OS_WIN32 + GList *iconlist = NULL; + #endif + GObject *syl_app; + PrefsAccount *new_account = NULL; + gboolean first_run = FALSE; + + app_init(); + parse_cmd_opt(argc, argv); + + /* check and create (unix domain) socket for remote operation */ + lock_socket = prohibit_duplicate_launch(); + if (lock_socket < 0) return 0; + + if (cmd.status || cmd.status_full) { + puts("0 Sylpheed not running."); + lock_socket_remove(); + return 0; + } + + #if USE_THREADS + gdk_threads_enter(); + #endif + gtk_set_locale(); + gtk_init(&argc, &argv); + + syl_app = syl_app_create(); + + gdk_rgb_init(); + gtk_widget_set_default_colormap(gdk_rgb_get_cmap()); + gtk_widget_set_default_visual(gdk_rgb_get_visual()); + + parse_gtkrc_files(); + setup_rc_dir(); + + if (is_file_exist("sylpheed.log")) { + if (rename_force("sylpheed.log", "sylpheed.log.bak") < 0) + FILE_OP_ERROR("sylpheed.log", "rename"); + } + set_log_file("sylpheed.log"); + + set_ui_update_func(gtkut_events_flush); + set_progress_func(main_window_progress_show); + set_input_query_password_func(input_dialog_query_password); + #if USE_SSL + ssl_init(); + ssl_set_verify_func(ssl_manager_verify_cert); + #endif + + CHDIR_EXIT_IF_FAIL(get_home_dir(), 1); + + prefs_common_read_config(); + filter_set_addressbook_func(addressbook_has_address); + filter_read_config(); + prefs_actions_read_config(); + prefs_display_header_read_config(); + colorlabel_read_config(); + + prefs_common.user_agent_str = g_strdup_printf + ("%s (GTK+ %d.%d.%d; %s)", + prog_version, + gtk_major_version, gtk_minor_version, gtk_micro_version, + TARGET_ALIAS); + +-#ifdef G_OS_WIN32 ++#if defined(G_OS_WIN32) || defined(__APPLE__) + fix_font_setting(); + #endif + + gtkut_stock_button_set_set_reverse(!prefs_common.comply_gnome_hig); + + check_gpg(); + + sock_set_io_timeout(prefs_common.io_timeout_secs); + + gtkut_widget_init(); + + #ifdef G_OS_WIN32 + stock_pixbuf_gdk(NULL, STOCK_PIXMAP_SYLPHEED_32, &icon); + iconlist = g_list_append(iconlist, icon); + stock_pixbuf_gdk(NULL, STOCK_PIXMAP_SYLPHEED_SMALL, &icon); + iconlist = g_list_append(iconlist, icon); + gtk_window_set_default_icon_list(iconlist); + g_list_free(iconlist); + #else + stock_pixbuf_gdk(NULL, STOCK_PIXMAP_SYLPHEED, &icon); + gtk_window_set_default_icon(icon); + #endif + + mainwin = main_window_create + (prefs_common.sep_folder | prefs_common.sep_msg << 1); + folderview = mainwin->folderview; + + /* register the callback of socket input */ + if (lock_socket > 0) { + lock_ch = g_io_channel_unix_new(lock_socket); + lock_socket_tag = g_io_add_watch(lock_ch, + G_IO_IN|G_IO_PRI|G_IO_ERR, + lock_socket_input_cb, mainwin); + } + + set_log_handlers(TRUE); + + account_read_config_all(); + account_set_menu(); + main_window_reflect_prefs_all(); + + if (folder_read_list() < 0) { + first_run = TRUE; + setup_mailbox(); + folder_write_list(); + } + if (!account_get_list()) { + new_account = setup_account(); + } + + account_set_menu(); + main_window_reflect_prefs_all(); + + account_set_missing_folder(); + folder_set_missing_folders(); + folderview_set(folderview); + if (new_account && new_account->folder) + folder_write_list(); + + addressbook_read_file(); + + register_system_events(); + + inc_autocheck_timer_init(mainwin); + + plugin_init(); + + g_signal_emit_by_name(syl_app, "init-done"); + + if (first_run) { + setup_import_data(); + setup_import_addressbook(); + } + + remote_command_exec(); + + #if USE_UPDATE_CHECK + if (prefs_common.auto_update_check) + update_check(FALSE); + #endif + + gtk_main(); + #if USE_THREADS + gdk_threads_leave(); + #endif + + return 0; + } + + static void init_console(void) + { + #ifdef G_OS_WIN32 + gint fd; + FILE *fp; + + if (init_console_done) + return; + + if (!AllocConsole()) { + g_warning("AllocConsole() failed\n"); + return; + } + + fd = _open_osfhandle((glong)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT); + _dup2(fd, 1); + fp = _fdopen(fd, "w"); + *stdout = *fp; + setvbuf(stdout, NULL, _IONBF, 0); + fd = _open_osfhandle((glong)GetStdHandle(STD_ERROR_HANDLE), _O_TEXT); + _dup2(fd, 2); + fp = _fdopen(fd, "w"); + *stderr = *fp; + setvbuf(stderr, NULL, _IONBF, 0); + + init_console_done = TRUE; + #endif + } + + static void cleanup_console(void) + { + #ifdef G_OS_WIN32 + FreeConsole(); + #endif + } + + #ifdef G_OS_WIN32 + static void read_ini_file(void) + { + static gushort ipcport = REMOTE_CMD_PORT; + static gchar *confdir = NULL; + + static PrefParam param[] = { + {"ipcport", "50215", &ipcport, P_USHORT}, + {"configdir", NULL, &confdir, P_STRING}, + + {NULL, NULL, NULL, P_OTHER} + }; + + gchar *file; + + file = g_strconcat(get_startup_dir(), G_DIR_SEPARATOR_S, "sylpheed.ini", + NULL); + if (!is_file_exist(file)) { + g_free(file); + return; + } + + prefs_read_config(param, "Sylpheed", file, + conv_get_locale_charset_str()); + g_free(file); + + cmd.ipcport = ipcport; + if (confdir) { + set_rc_dir(confdir); + g_free(confdir); + confdir = NULL; + cmd.configdir = TRUE; + } + } ++#endif /* G_OS_WIN32 */ + ++#if defined(G_OS_WIN32) || defined(__APPLE__) + static void fix_font_setting(void) + { + const gchar *str = NULL; + + if (!conv_is_ja_locale()) + return; + + if (prefs_common.textfont && + strcmp(prefs_common.textfont, DEFAULT_MESSAGE_FONT) != 0) { + if (gtkut_font_can_load(prefs_common.textfont)) { + debug_print("font '%s' load ok\n", prefs_common.textfont); + return; + } + debug_print("font '%s' load failed\n", prefs_common.textfont); + } + + debug_print("fixing prefs_common.textfont setting\n"); + ++#ifdef G_OS_WIN32 + str = "MS Gothic 12"; ++#else /* __APPLE__ */ ++ str = "Hiragino Kaku Gothic Pro Light 13"; ++#endif + + if (!gtkut_font_can_load(str)) { ++#ifdef G_OS_WIN32 + debug_print("font '%s' load failed\n", str); + str = "\xef\xbc\xad\xef\xbc\xb3 \xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf 12"; + if (!gtkut_font_can_load(str)) { + debug_print("font '%s' load failed\n", str); + str = NULL; + } ++#else /* __APPLE__ */ ++ debug_print("font '%s' load failed\n", str); ++ str = NULL; ++#endif + } + + if (str) { + debug_print("font '%s' load ok\n", str); + g_free(prefs_common.textfont); + prefs_common.textfont = g_strdup(str); + } else + g_warning("failed to load text font!"); + } + #endif + + static void parse_cmd_opt(int argc, char *argv[]) + { + gint i; + + for (i = 1; i < argc; i++) { + if (!strncmp(argv[i], "--debug", 7)) { + init_console(); + set_debug_mode(TRUE); + } else if (!strncmp(argv[i], "--receive-all", 13)) + cmd.receive_all = TRUE; + else if (!strncmp(argv[i], "--receive", 9)) + cmd.receive = TRUE; + else if (!strncmp(argv[i], "--compose", 9)) { + const gchar *p = argv[i + 1]; + + cmd.compose = TRUE; + cmd.compose_mailto = NULL; + if (p && *p != '\0' && *p != '-') { + if (!strncmp(p, "mailto:", 7)) + cmd.compose_mailto = p + 7; + else + cmd.compose_mailto = p; + i++; + } + } else if (!strncmp(argv[i], "--attach", 8)) { + const gchar *p = argv[i + 1]; + gchar *file; + + while (p && *p != '\0' && *p != '-') { + if (!cmd.attach_files) + cmd.attach_files = g_ptr_array_new(); + if (!g_path_is_absolute(p)) + file = g_strconcat(get_startup_dir(), + G_DIR_SEPARATOR_S, + p, NULL); + else + file = g_strdup(p); + g_ptr_array_add(cmd.attach_files, file); + i++; + p = argv[i + 1]; + } + } else if (!strncmp(argv[i], "--send", 6)) { + cmd.send = TRUE; + } else if (!strncmp(argv[i], "--version", 9)) { + puts("Sylpheed version " VERSION); + exit(0); + } else if (!strncmp(argv[i], "--status-full", 13)) { + const gchar *p = argv[i + 1]; + + cmd.status_full = TRUE; + while (p && *p != '\0' && *p != '-') { + if (!cmd.status_full_folders) + cmd.status_full_folders = + g_ptr_array_new(); + g_ptr_array_add(cmd.status_full_folders, + g_strdup(p)); + i++; + p = argv[i + 1]; + } + } else if (!strncmp(argv[i], "--status", 8)) { + const gchar *p = argv[i + 1]; + + cmd.status = TRUE; + while (p && *p != '\0' && *p != '-') { + if (!cmd.status_folders) + cmd.status_folders = g_ptr_array_new(); + g_ptr_array_add(cmd.status_folders, + g_strdup(p)); + i++; + p = argv[i + 1]; + } + } else if (!strncmp(argv[i], "--open", 6)) { + const gchar *p = argv[i + 1]; + + if (p && *p != '\0' && *p != '-') { + cmd.open_msg = g_locale_to_utf8 + (p, -1, NULL, NULL, NULL); + i++; + } + } else if (!strncmp(argv[i], "--configdir", 11)) { + const gchar *p = argv[i + 1]; + + if (p && *p != '\0' && *p != '-') { + /* this must only be done at startup */ + #ifdef G_OS_WIN32 + gchar *utf8dir; + + utf8dir = g_locale_to_utf8 + (p, -1, NULL, NULL, NULL); + if (utf8dir) { + set_rc_dir(utf8dir); + g_free(utf8dir); + } else + set_rc_dir(p); + #else + set_rc_dir(p); + #endif + cmd.configdir = TRUE; + i++; + } + #ifdef G_OS_WIN32 + } else if (!strncmp(argv[i], "--ipcport", 9)) { + if (argv[i + 1]) { + cmd.ipcport = atoi(argv[i + 1]); + i++; + } + #endif + } else if (!strncmp(argv[i], "--instance-id", 13)) { + if (argv[i + 1]) { + instance_id = g_locale_to_utf8 + (argv[i + 1], -1, NULL, NULL, NULL); + i++; + } + } else if (!strncmp(argv[i], "--exit", 6)) { + cmd.exit = TRUE; + } else if (!strncmp(argv[i], "--help", 6)) { + init_console(); + + g_print(_("Usage: %s [OPTION]...\n"), + g_basename(argv[0])); + + g_print("%s\n", _(" --compose [address] open composition window")); + g_print("%s\n", _(" --attach file1 [file2]...\n" + " open composition window with specified files\n" + " attached")); + g_print("%s\n", _(" --receive receive new messages")); + g_print("%s\n", _(" --receive-all receive new messages of all accounts")); + g_print("%s\n", _(" --send send all queued messages")); + g_print("%s\n", _(" --status [folder]... show the total number of messages")); + g_print("%s\n", _(" --status-full [folder]...\n" + " show the status of each folder")); + g_print("%s\n", _(" --open folderid/msgnum open message in new window")); + g_print("%s\n", _(" --configdir dirname specify directory which stores configuration files")); + #ifdef G_OS_WIN32 + g_print("%s\n", _(" --ipcport portnum specify port for IPC remote commands")); + #endif + g_print("%s\n", _(" --exit exit Sylpheed")); + g_print("%s\n", _(" --debug debug mode")); + g_print("%s\n", _(" --help display this help and exit")); + g_print("%s\n", _(" --version output version information and exit")); + + #ifdef G_OS_WIN32 + g_print("\n"); + g_print(_("Press any key...")); + _getch(); + #endif + + cleanup_console(); + exit(1); + } + } + + if (cmd.attach_files && cmd.compose == FALSE) { + cmd.compose = TRUE; + cmd.compose_mailto = NULL; + } + + cmd.argv0 = g_locale_to_utf8(argv[0], -1, NULL, NULL, NULL); + if (!cmd.argv0) + cmd.argv0 = g_strdup(argv[0]); + } + + static gint get_queued_message_num(void) + { + FolderItem *queue; + + queue = folder_get_default_queue(); + if (!queue) return -1; + + folder_item_scan(queue); + return queue->total; + } + + #if USE_THREADS + /* enables recursive locking with gdk_thread_enter / gdk_threads_leave */ + static GStaticRecMutex syl_mutex = G_STATIC_REC_MUTEX_INIT; + + static void thread_enter_func(void) + { + g_static_rec_mutex_lock(&syl_mutex); + #if 0 + syl_mutex_lock_count++; + if (syl_mutex_lock_count > 1) + g_print("enter: syl_mutex_lock_count: %d\n", syl_mutex_lock_count); + #endif + } + + static void thread_leave_func(void) + { + #if 0 + syl_mutex_lock_count--; + if (syl_mutex_lock_count > 0) + g_print("leave: syl_mutex_lock_count: %d\n", syl_mutex_lock_count); + #endif + g_static_rec_mutex_unlock(&syl_mutex); + } + + static void event_loop_iteration_func(void) + { + if (g_thread_self() != main_thread) { + g_fprintf(stderr, "event_loop_iteration_func called from non-main thread (%p)\n", g_thread_self()); + g_usleep(10000); + return; + } + gtk_main_iteration(); + } + #endif + + static void app_init(void) + { + #if USE_THREADS + if (!g_thread_supported()) + g_thread_init(NULL); + if (!g_thread_supported()) + g_error("g_thread is not supported by glib."); + else { + gdk_threads_set_lock_functions(thread_enter_func, + thread_leave_func); + gdk_threads_init(); + main_thread = g_thread_self(); + } + #endif + syl_init(); + + #if USE_THREADS + set_event_loop_func(event_loop_iteration_func); + #endif + prog_version = PROG_VERSION; + + #ifdef G_OS_WIN32 + read_ini_file(); + #endif + } + + static void parse_gtkrc_files(void) + { + gchar *userrc; + + /* parse gtkrc files */ + userrc = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".gtkrc-2.0", + NULL); + gtk_rc_parse(userrc); + g_free(userrc); + userrc = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".gtk", + G_DIR_SEPARATOR_S, "gtkrc-2.0", NULL); + gtk_rc_parse(userrc); + g_free(userrc); + userrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "gtkrc", NULL); + gtk_rc_parse(userrc); + g_free(userrc); + + userrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MENU_RC, NULL); + gtk_accel_map_load(userrc); + g_free(userrc); + } + + static void setup_rc_dir(void) + { + #ifndef G_OS_WIN32 + CHDIR_EXIT_IF_FAIL(get_home_dir(), 1); + + /* backup if old rc file exists */ + if (!cmd.configdir && is_file_exist(RC_DIR)) { + if (rename_force(RC_DIR, RC_DIR ".bak") < 0) + FILE_OP_ERROR(RC_DIR, "rename"); + } + + /* migration from ~/.sylpheed to ~/.sylpheed-2.0 */ + if (!cmd.configdir && !is_dir_exist(RC_DIR)) { + const gchar *envstr; + AlertValue val; + + /* check for filename encoding */ + if (conv_get_locale_charset() != C_UTF_8) { + envstr = g_getenv("G_FILENAME_ENCODING"); + if (!envstr) + envstr = g_getenv("G_BROKEN_FILENAMES"); + if (!envstr) { + val = alertpanel(_("Filename encoding"), + _("The locale encoding is not UTF-8, but the environmental variable G_FILENAME_ENCODING is not set.\n" + "If the locale encoding is used for file name or directory name, it will not work correctly.\n" + "In that case, you must set the following environmental variable (see README for detail):\n" + "\n" + "\tG_FILENAME_ENCODING=@locale\n" + "\n" + "Continue?"), + GTK_STOCK_OK, GTK_STOCK_QUIT, + NULL); + if (G_ALERTDEFAULT != val) + exit(1); + } + } + + if (make_dir(RC_DIR) < 0) + exit(1); + if (is_dir_exist(OLD_RC_DIR)) + migrate_old_config(); + } + #endif /* !G_OS_WIN32 */ + + syl_setup_rc_dir(); + } + + static void app_restart(void) + { + gchar *cmdline; + GError *error = NULL; + #ifdef G_OS_WIN32 + if (cmd.configdir) { + cmdline = g_strdup_printf("\"%s\"%s --configdir \"%s\" --ipcport %d", + cmd.argv0, + get_debug_mode() ? " --debug" : "", + get_rc_dir(), + cmd.ipcport); + } else { + cmdline = g_strdup_printf("\"%s\"%s --ipcport %d", + cmd.argv0, + get_debug_mode() ? " --debug" : "", + cmd.ipcport); + } + #else + if (cmd.configdir) { + cmdline = g_strdup_printf("\"%s\"%s --configdir \"%s\"", + cmd.argv0, + get_debug_mode() ? " --debug" : "", + get_rc_dir()); + } else { + cmdline = g_strdup_printf("\"%s\"%s", + cmd.argv0, + get_debug_mode() ? " --debug" : ""); + } + #endif + if (!g_spawn_command_line_async(cmdline, &error)) { + alertpanel_error("restart failed\n'%s'\n%s", cmdline, error->message); + g_error_free(error); + } + g_free(cmdline); + } + + void app_will_restart(gboolean force) + { + cmd.restart = TRUE; + app_will_exit(force); + /* canceled */ + cmd.restart = FALSE; + } + + void app_will_exit(gboolean force) + { + MainWindow *mainwin; + gchar *filename; + static gboolean on_exit = FALSE; + GList *cur; + + if (on_exit) + return; + on_exit = TRUE; + + mainwin = main_window_get(); + + if (!force && compose_get_compose_list()) { + if (alertpanel(_("Notice"), + _("Composing message exists. Really quit?"), + GTK_STOCK_OK, GTK_STOCK_CANCEL, NULL) + != G_ALERTDEFAULT) { + on_exit = FALSE; + return; + } + manage_window_focus_in(mainwin->window, NULL, NULL); + } + + if (!force && + prefs_common.warn_queued_on_exit && get_queued_message_num() > 0) { + if (alertpanel(_("Queued messages"), + _("Some unsent messages are queued. Exit now?"), + GTK_STOCK_OK, GTK_STOCK_CANCEL, NULL) + != G_ALERTDEFAULT) { + on_exit = FALSE; + return; + } + manage_window_focus_in(mainwin->window, NULL, NULL); + } + + if (force) + g_signal_emit_by_name(syl_app_get(), "app-force-exit"); + g_signal_emit_by_name(syl_app_get(), "app-exit"); + + inc_autocheck_timer_remove(); + + if (prefs_common.clean_on_exit) + main_window_empty_trash(mainwin, + !force && prefs_common.ask_on_clean); + + for (cur = account_get_list(); cur != NULL; cur = cur->next) { + PrefsAccount *ac = (PrefsAccount *)cur->data; + if (ac->protocol == A_IMAP4 && ac->imap_clear_cache_on_exit && + ac->folder) + procmsg_remove_all_cached_messages(FOLDER(ac->folder)); + } + + syl_plugin_unload_all(); + + trayicon_destroy(mainwin->tray_icon); + + /* save all state before exiting */ + summary_write_cache(mainwin->summaryview); + main_window_get_size(mainwin); + main_window_get_position(mainwin); + syl_save_all_state(); + addressbook_export_to_file(); + + filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, MENU_RC, NULL); + gtk_accel_map_save(filename); + g_free(filename); + + /* remove temporary files, close log file, socket cleanup */ + #if USE_SSL + ssl_done(); + #endif + syl_cleanup(); + lock_socket_remove(); + + #ifdef USE_UPDATE_CHECK_PLUGIN + #ifdef G_OS_WIN32 + cur = gtk_window_list_toplevels(); + g_list_foreach(cur, (GFunc)gtk_widget_hide, NULL); + g_list_free(cur); + update_check_spawn_plugin_updater(); + #endif + #endif + + cleanup_console(); + + if (gtk_main_level() > 0) + gtk_main_quit(); + + if (cmd.restart) + app_restart(); + + exit(0); + } + + #if 0 + #if USE_GPGME + static void idle_function_for_gpgme(void) + { + while (gtk_events_pending()) + gtk_main_iteration(); + } + #endif /* USE_GPGME */ + #endif /* 0 */ + + static void check_gpg(void) + { + #if USE_GPGME + const gchar *version; + gpgme_error_t err = 0; + + version = gpgme_check_version("1.0.0"); + if (version) { + debug_print("GPGME Version: %s\n", version); + err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); + if (err) + debug_print("gpgme_engine_check_version: %s\n", + gpgme_strerror(err)); + } + + if (version && !err) { + /* Also does some gpgme init */ + gpgme_engine_info_t engineInfo; + + #if HAVE_LOCALE_H + gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); + gpgme_set_locale(NULL, LC_MESSAGES, + setlocale(LC_MESSAGES, NULL)); + #endif + + if (!gpgme_get_engine_info(&engineInfo)) { + while (engineInfo) { + debug_print("GPGME Protocol: %s\n Version: %s\n", + gpgme_get_protocol_name + (engineInfo->protocol), + engineInfo->version ? + engineInfo->version : "(unknown)"); + engineInfo = engineInfo->next; + } + } + + procmsg_set_decrypt_message_func + (rfc2015_open_message_decrypted); + procmsg_set_auto_decrypt_message(TRUE); + } else { + rfc2015_disable_all(); + + if (prefs_common.gpg_warning) { + AlertValue val; + + val = alertpanel_message_with_disable + (_("Warning"), + _("GnuPG is not installed properly, or its version is too old.\n" + "OpenPGP support disabled."), + ALERT_WARNING); + if (val & G_ALERTDISABLE) + prefs_common.gpg_warning = FALSE; + } + } + /* FIXME: This function went away. We can either block until gpgme + * operations finish (currently implemented) or register callbacks + * with the gtk main loop via the gpgme io callback interface instead. + * + * gpgme_register_idle(idle_function_for_gpgme); + */ + #endif + } + + static void default_log_func(const gchar *log_domain, GLogLevelFlags log_level, + const gchar *message, gpointer user_data) + { + gchar *prefix = ""; + gchar *file_prefix = ""; + LogType level = LOG_NORMAL; + gchar *str; + const gchar *message_; + + switch (log_level) { + case G_LOG_LEVEL_ERROR: + prefix = "ERROR"; + file_prefix = "*** "; + level = LOG_ERROR; + break; + case G_LOG_LEVEL_CRITICAL: + prefix = "CRITICAL"; + file_prefix = "** "; + level = LOG_WARN; + break; + case G_LOG_LEVEL_WARNING: + prefix = "WARNING"; + file_prefix = "** "; + level = LOG_WARN; + break; + case G_LOG_LEVEL_MESSAGE: + prefix = "Message"; + file_prefix = "* "; + level = LOG_MSG; + break; + case G_LOG_LEVEL_INFO: + prefix = "INFO"; + file_prefix = "* "; + level = LOG_MSG; + break; + case G_LOG_LEVEL_DEBUG: + prefix = "DEBUG"; + break; + default: + prefix = "LOG"; + break; + } + + if (!message) + message_ = "(NULL) message"; + else + message_ = message; + if (log_domain) + str = g_strconcat(log_domain, "-", prefix, ": ", message_, "\n", + NULL); + else + str = g_strconcat(prefix, ": ", message_, "\n", NULL); + log_window_append(str, level); + log_write(str, file_prefix); + g_free(str); + + g_log_default_handler(log_domain, log_level, message, user_data); + } + + static void set_log_handlers(gboolean enable) + { + #if GLIB_CHECK_VERSION(2, 6, 0) + if (enable) + g_log_set_default_handler(default_log_func, NULL); + else + g_log_set_default_handler(g_log_default_handler, NULL); + #else + static guint handler_id[4] = {0, 0, 0, 0}; + + if (enable) { + handler_id[0] = g_log_set_handler + ("GLib", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL + | G_LOG_FLAG_RECURSION, default_log_func, NULL); + handler_id[1] = g_log_set_handler + ("Gtk", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL + | G_LOG_FLAG_RECURSION, default_log_func, NULL); + handler_id[2] = g_log_set_handler + ("LibSylph", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL + | G_LOG_FLAG_RECURSION, default_log_func, NULL); + handler_id[3] = g_log_set_handler + ("Sylpheed", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL + | G_LOG_FLAG_RECURSION, default_log_func, NULL); + } else { + g_log_remove_handler("GLib", handler_id[0]); + g_log_remove_handler("Gtk", handler_id[1]); + g_log_remove_handler("LibSylph", handler_id[2]); + g_log_remove_handler("Sylpheed", handler_id[3]); + handler_id[0] = 0; + handler_id[1] = 0; + handler_id[2] = 0; + handler_id[3] = 0; + } + #endif + } + + #ifdef G_OS_WIN32 + static BOOL WINAPI + ctrl_handler(DWORD dwctrltype) + { + log_print("ctrl_handler: received %d\n", dwctrltype); + app_will_exit(TRUE); + + return TRUE; + } + + static LRESULT CALLBACK + wndproc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) + { + switch (message) { + case WM_POWERBROADCAST: + debug_print("WM_POWERBROADCAST received: wparam = %d\n", + wparam); + if (wparam == PBT_APMSUSPEND || wparam == PBT_APMSTANDBY) { + debug_print("suspend now\n"); + inc_autocheck_timer_remove(); + } else if (wparam == PBT_APMRESUMESUSPEND || + wparam == PBT_APMRESUMESTANDBY) { + debug_print("resume now\n"); + inc_autocheck_timer_set(); + } + break; + case WM_ENDSESSION: + if (wparam == 1) { + log_print("WM_ENDSESSION received: system is quitting\n"); + app_will_exit(TRUE); + } + break; + default: + break; + } + + return DefWindowProc(hwnd, message, wparam, lparam); + } + + static void register_system_events(void) + { + WNDCLASS wclass; + static HWND hwnd = NULL; + static BOOL ctrl_handler_set = FALSE; + ATOM klass; + HINSTANCE hmodule = GetModuleHandle(NULL); + + if (init_console_done && !ctrl_handler_set) { + debug_print("register_system_events(): SetConsoleCtrlHandler\n"); + ctrl_handler_set = SetConsoleCtrlHandler(ctrl_handler, TRUE); + if (!ctrl_handler_set) + g_warning("SetConsoleCtrlHandler() failed\n"); + } + + if (hwnd) + return; + + debug_print("register_system_events(): RegisterClass\n"); + + memset(&wclass, 0, sizeof(WNDCLASS)); + wclass.lpszClassName = "sylpheed-observer"; + wclass.lpfnWndProc = wndproc; + wclass.hInstance = hmodule; + + klass = RegisterClass(&wclass); + if (!klass) + return; + + hwnd = CreateWindow(MAKEINTRESOURCE(klass), NULL, WS_POPUP, + 0, 0, 1, 1, NULL, NULL, hmodule, NULL); + if (!hwnd) + UnregisterClass(MAKEINTRESOURCE(klass), hmodule); + } + #else /* G_OS_WIN32 */ + static void sig_handler(gint signum) + { + debug_print("signal %d received\n", signum); + + switch (signum) { + case SIGHUP: + case SIGINT: + case SIGTERM: + case SIGQUIT: + app_will_exit(TRUE); + break; + default: + break; + } + } + + static void register_system_events(void) + { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sig_handler; + sa.sa_flags = SA_RESTART; + + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGHUP); + sigaddset(&sa.sa_mask, SIGINT); + sigaddset(&sa.sa_mask, SIGTERM); + sigaddset(&sa.sa_mask, SIGQUIT); + sigaddset(&sa.sa_mask, SIGPIPE); + + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + } + #endif + + #define ADD_SYM(sym) syl_plugin_add_symbol(#sym, sym) + + static void plugin_init(void) + { + MainWindow *mainwin; + gchar *path; + + mainwin = main_window_get(); + + STATUSBAR_PUSH(mainwin, _("Loading plug-ins...")); + + if (syl_plugin_init_lib() != 0) { + STATUSBAR_POP(mainwin); + return; + } + + ADD_SYM(prog_version); + ADD_SYM(app_will_exit); + + ADD_SYM(main_window_lock); + ADD_SYM(main_window_unlock); + ADD_SYM(main_window_get); + ADD_SYM(main_window_popup); + + syl_plugin_add_symbol("main_window_menu_factory", + mainwin->menu_factory); + syl_plugin_add_symbol("main_window_statusbar", mainwin->statusbar); + + ADD_SYM(folderview_get); + ADD_SYM(folderview_add_sub_widget); + ADD_SYM(folderview_select); + ADD_SYM(folderview_unselect); + ADD_SYM(folderview_select_next_unread); + ADD_SYM(folderview_get_selected_item); + ADD_SYM(folderview_check_new); + ADD_SYM(folderview_check_new_item); + ADD_SYM(folderview_check_new_all); + ADD_SYM(folderview_update_item); + ADD_SYM(folderview_update_item_foreach); + ADD_SYM(folderview_update_all_updated); + ADD_SYM(folderview_check_new_selected); + + syl_plugin_add_symbol("folderview_mail_popup_factory", + mainwin->folderview->mail_factory); + syl_plugin_add_symbol("folderview_imap_popup_factory", + mainwin->folderview->imap_factory); + syl_plugin_add_symbol("folderview_news_popup_factory", + mainwin->folderview->news_factory); + + syl_plugin_add_symbol("summaryview", mainwin->summaryview); + syl_plugin_add_symbol("summaryview_popup_factory", + mainwin->summaryview->popupfactory); + + ADD_SYM(summary_select_by_msgnum); + ADD_SYM(summary_select_by_msginfo); + ADD_SYM(summary_lock); + ADD_SYM(summary_unlock); + ADD_SYM(summary_is_locked); + ADD_SYM(summary_is_read_locked); + ADD_SYM(summary_write_lock); + ADD_SYM(summary_write_unlock); + ADD_SYM(summary_is_write_locked); + ADD_SYM(summary_get_current_folder); + ADD_SYM(summary_get_selection_type); + ADD_SYM(summary_get_selected_msg_list); + ADD_SYM(summary_get_msg_list); + ADD_SYM(summary_show_queued_msgs); + ADD_SYM(summary_redisplay_msg); + ADD_SYM(summary_open_msg); + ADD_SYM(summary_view_source); + ADD_SYM(summary_reedit); + ADD_SYM(summary_update_selected_rows); + ADD_SYM(summary_update_by_msgnum); + + ADD_SYM(messageview_create_with_new_window); + ADD_SYM(messageview_show); + + ADD_SYM(compose_new); + ADD_SYM(compose_entry_set); + ADD_SYM(compose_entry_append); + ADD_SYM(compose_entry_get_text); + ADD_SYM(compose_lock); + ADD_SYM(compose_unlock); + + ADD_SYM(foldersel_folder_sel); + ADD_SYM(foldersel_folder_sel_full); + + ADD_SYM(input_dialog); + ADD_SYM(input_dialog_with_invisible); + + ADD_SYM(manage_window_set_transient); + ADD_SYM(manage_window_signals_connect); + ADD_SYM(manage_window_get_focus_window); + + ADD_SYM(inc_mail); + ADD_SYM(inc_is_active); + ADD_SYM(inc_lock); + ADD_SYM(inc_unlock); + + #if USE_UPDATE_CHECK + ADD_SYM(update_check); + ADD_SYM(update_check_set_check_url); + ADD_SYM(update_check_get_check_url); + ADD_SYM(update_check_set_download_url); + ADD_SYM(update_check_get_download_url); + ADD_SYM(update_check_set_jump_url); + ADD_SYM(update_check_get_jump_url); + #ifdef USE_UPDATE_CHECK_PLUGIN + ADD_SYM(update_check_set_check_plugin_url); + ADD_SYM(update_check_get_check_plugin_url); + ADD_SYM(update_check_set_jump_plugin_url); + ADD_SYM(update_check_get_jump_plugin_url); + #endif /* USE_UPDATE_CHECK_PLUGIN */ + #endif + + ADD_SYM(alertpanel_full); + ADD_SYM(alertpanel); + ADD_SYM(alertpanel_message); + ADD_SYM(alertpanel_message_with_disable); + + ADD_SYM(send_message); + ADD_SYM(send_message_queue_all); + ADD_SYM(send_message_set_reply_flag); + ADD_SYM(send_message_set_forward_flags); + + syl_plugin_signal_connect("plugin-load", G_CALLBACK(load_cb), NULL); + + /* loading plug-ins from user plug-in directory */ + path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PLUGIN_DIR, NULL); + syl_plugin_load_all(path); + g_free(path); + + /* loading plug-ins from system plug-in directory */ + #ifdef G_OS_WIN32 + path = g_strconcat(get_startup_dir(), G_DIR_SEPARATOR_S, PLUGIN_DIR, + NULL); + syl_plugin_load_all(path); + g_free(path); + #else + syl_plugin_load_all(PLUGINDIR); + #endif + + STATUSBAR_POP(mainwin); + } + + static gchar *get_socket_name(void) + { + static gchar *filename = NULL; + + if (filename == NULL) { + filename = g_strdup_printf("%s%c%s-%d", + g_get_tmp_dir(), G_DIR_SEPARATOR, + instance_id ? instance_id : "sylpheed", + #if HAVE_GETUID + getuid()); + #else + 0); + #endif + } + + return filename; + } + + static gint prohibit_duplicate_launch(void) + { + gint sock; + + #ifdef G_OS_WIN32 + HANDLE hmutex; + const gchar *ins_id = instance_id ? instance_id : "Sylpheed"; + gushort port = cmd.ipcport ? cmd.ipcport : REMOTE_CMD_PORT; + + debug_print("prohibit_duplicate_launch: checking mutex: %s\n", ins_id); + hmutex = CreateMutexA(NULL, FALSE, ins_id); + if (!hmutex) { + g_warning("cannot create Mutex: %s\n", ins_id); + return -1; + } + if (GetLastError() != ERROR_ALREADY_EXISTS) { + debug_print("prohibit_duplicate_launch: creating socket: port %d\n", port); + sock = fd_open_inet(port); + if (sock < 0) + return 0; + return sock; + } + + debug_print("prohibit_duplicate_launch: connecting to socket: port %d\n", port); + sock = fd_connect_inet(port); + if (sock < 0) + return -1; + #else + gchar *path; + + path = get_socket_name(); + debug_print("prohibit_duplicate_launch: checking socket: %s\n", path); + sock = fd_connect_unix(path); + if (sock < 0) { + debug_print("prohibit_duplicate_launch: creating socket: %s\n", path); + g_unlink(path); + return fd_open_unix(path); + } + #endif + + /* remote command mode */ + + debug_print(_("another Sylpheed is already running.\n")); + + if (cmd.receive_all) + fd_write_all(sock, "receive_all\n", 12); + else if (cmd.receive) + fd_write_all(sock, "receive\n", 8); + else if (cmd.compose && cmd.attach_files) { + gchar *str, *compose_str; + gint i; + + if (cmd.compose_mailto) + compose_str = g_strdup_printf("compose_attach %s\n", + cmd.compose_mailto); + else + compose_str = g_strdup("compose_attach\n"); + + fd_write_all(sock, compose_str, strlen(compose_str)); + g_free(compose_str); + + for (i = 0; i < cmd.attach_files->len; i++) { + str = g_ptr_array_index(cmd.attach_files, i); + fd_write_all(sock, str, strlen(str)); + fd_write_all(sock, "\n", 1); + } + + fd_write_all(sock, ".\n", 2); + } else if (cmd.compose) { + gchar *compose_str; + + if (cmd.compose_mailto) + compose_str = g_strdup_printf + ("compose %s\n", cmd.compose_mailto); + else + compose_str = g_strdup("compose\n"); + + fd_write_all(sock, compose_str, strlen(compose_str)); + g_free(compose_str); + } else if (cmd.send) { + fd_write_all(sock, "send\n", 5); + } else if (cmd.status || cmd.status_full) { + gchar buf[BUFFSIZE]; + gint i; + const gchar *command; + GPtrArray *folders; + gchar *folder; + + command = cmd.status_full ? "status-full\n" : "status\n"; + folders = cmd.status_full ? cmd.status_full_folders : + cmd.status_folders; + + fd_write_all(sock, command, strlen(command)); + for (i = 0; folders && i < folders->len; ++i) { + folder = g_ptr_array_index(folders, i); + fd_write_all(sock, folder, strlen(folder)); + fd_write_all(sock, "\n", 1); + } + fd_write_all(sock, ".\n", 2); + for (;;) { + fd_gets(sock, buf, sizeof(buf)); + if (!strncmp(buf, ".\n", 2)) break; + fputs(buf, stdout); + } + } else if (cmd.open_msg) { + gchar *str; + + str = g_strdup_printf("open %s\n", cmd.open_msg); + fd_write_all(sock, str, strlen(str)); + g_free(str); + } else if (cmd.exit) { + fd_write_all(sock, "exit\n", 5); + } else { + #ifdef G_OS_WIN32 + HWND hwnd; + + fd_write_all(sock, "popup\n", 6); + if (fd_read(sock, (gchar *)&hwnd, sizeof(hwnd)) == sizeof(hwnd)) + SetForegroundWindow(hwnd); + #else + fd_write_all(sock, "popup\n", 6); + #endif + } + + fd_close(sock); + return -1; + } + + static gint lock_socket_remove(void) + { + #ifndef G_OS_WIN32 + gchar *filename; + #endif + + if (lock_socket < 0) return -1; + + if (lock_socket_tag > 0) + g_source_remove(lock_socket_tag); + if (lock_ch) { + g_io_channel_shutdown(lock_ch, FALSE, NULL); + g_io_channel_unref(lock_ch); + lock_ch = NULL; + } + + #ifndef G_OS_WIN32 + filename = get_socket_name(); + debug_print("lock_socket_remove: removing socket: %s\n", filename); + g_unlink(filename); + #endif + + return 0; + } + + static GPtrArray *get_folder_item_list(gint sock) + { + gchar buf[BUFFSIZE]; + FolderItem *item; + GPtrArray *folders = NULL; + + for (;;) { + fd_gets(sock, buf, sizeof(buf)); + if (!strncmp(buf, ".\n", 2)) break; + strretchomp(buf); + if (!folders) folders = g_ptr_array_new(); + item = folder_find_item_from_identifier(buf); + if (item) + g_ptr_array_add(folders, item); + else + g_warning("no such folder: %s\n", buf); + } + + return folders; + } + + static gboolean lock_socket_input_cb(GIOChannel *source, GIOCondition condition, + gpointer data) + { + MainWindow *mainwin = (MainWindow *)data; + gint fd, sock; + gchar buf[BUFFSIZE]; + + #if USE_THREADS + gdk_threads_enter(); + #endif + + fd = g_io_channel_unix_get_fd(source); + sock = fd_accept(fd); + fd_gets(sock, buf, sizeof(buf)); + + if (!strncmp(buf, "popup", 5)) { + #ifdef G_OS_WIN32 + HWND hwnd; + + hwnd = (HWND)gdk_win32_drawable_get_handle + (GDK_DRAWABLE(mainwin->window->window)); + fd_write(sock, (gchar *)&hwnd, sizeof(hwnd)); + if (mainwin->window_hidden) + main_window_popup(mainwin); + #else + main_window_popup(mainwin); + #endif + } else if (!strncmp(buf, "receive_all", 11)) { + main_window_popup(mainwin); + if (!gtkut_window_modal_exist()) + inc_all_account_mail(mainwin, FALSE); + } else if (!strncmp(buf, "receive", 7)) { + main_window_popup(mainwin); + if (!gtkut_window_modal_exist()) + inc_mail(mainwin); + } else if (!strncmp(buf, "compose_attach", 14)) { + GPtrArray *files; + gchar *mailto; + + mailto = g_strdup(buf + strlen("compose_attach") + 1); + files = g_ptr_array_new(); + while (fd_gets(sock, buf, sizeof(buf)) > 0) { + if (buf[0] == '.' && buf[1] == '\n') break; + strretchomp(buf); + g_ptr_array_add(files, g_strdup(buf)); + } + open_compose_new(mailto, files); + ptr_array_free_strings(files); + g_ptr_array_free(files, TRUE); + g_free(mailto); + } else if (!strncmp(buf, "compose", 7)) { + open_compose_new(buf + strlen("compose") + 1, NULL); + } else if (!strncmp(buf, "send", 4)) { + send_queue(); + } else if (!strncmp(buf, "status-full", 11) || + !strncmp(buf, "status", 6)) { + gchar *status; + GPtrArray *folders; + + folders = get_folder_item_list(sock); + status = folder_get_status + (folders, !strncmp(buf, "status-full", 11)); + fd_write_all(sock, status, strlen(status)); + fd_write_all(sock, ".\n", 2); + g_free(status); + if (folders) g_ptr_array_free(folders, TRUE); + } else if (!strncmp(buf, "open", 4)) { + strretchomp(buf); + if (strlen(buf) < 6 || buf[4] != ' ') { + fd_close(sock); + #if USE_THREADS + gdk_threads_leave(); + #endif + return TRUE; + } + open_message(buf + 5); + } else if (!strncmp(buf, "exit", 4)) { + fd_close(sock); + app_will_exit(TRUE); + } + + fd_close(sock); + + #if USE_THREADS + gdk_threads_leave(); + #endif + + return TRUE; + } + + static void remote_command_exec(void) + { + MainWindow *mainwin; + + mainwin = main_window_get(); + + if (prefs_common.open_inbox_on_startup) { + FolderItem *item; + PrefsAccount *ac; + + ac = account_get_default(); + if (!ac) + ac = cur_account; + item = ac && ac->inbox + ? folder_find_item_from_identifier(ac->inbox) + : folder_get_default_inbox(); + folderview_select(mainwin->folderview, item); + } + + if (!gtkut_window_modal_exist()) { + if (cmd.receive_all) + inc_all_account_mail(mainwin, FALSE); + else if (prefs_common.chk_on_startup) + inc_all_account_mail(mainwin, TRUE); + else if (cmd.receive) + inc_mail(mainwin); + + if (cmd.compose) + open_compose_new(cmd.compose_mailto, cmd.attach_files); + + if (cmd.send) + send_queue(); + + if (cmd.open_msg) + open_message(cmd.open_msg); + } + + if (cmd.attach_files) { + ptr_array_free_strings(cmd.attach_files); + g_ptr_array_free(cmd.attach_files, TRUE); + cmd.attach_files = NULL; + } + if (cmd.status_folders) { + g_ptr_array_free(cmd.status_folders, TRUE); + cmd.status_folders = NULL; + } + if (cmd.status_full_folders) { + g_ptr_array_free(cmd.status_full_folders, TRUE); + cmd.status_full_folders = NULL; + } + if (cmd.open_msg) { + g_free(cmd.open_msg); + cmd.open_msg = NULL; + } + if (cmd.exit) { + app_will_exit(TRUE); + } + } + + static void migrate_old_config(void) + { + GDir *dir; + const gchar *dir_name; + GPatternSpec *pspec; + + if (alertpanel(_("Migration of configuration"), + _("The previous version of configuration found.\n" + "Do you want to migrate it?"), + GTK_STOCK_YES, GTK_STOCK_NO, NULL) != G_ALERTDEFAULT) + return; + + debug_print("Migrating old configuration...\n"); + + #define COPY_FILE(rcfile) \ + if (is_file_exist(OLD_RC_DIR G_DIR_SEPARATOR_S rcfile)) { \ + conv_copy_file(OLD_RC_DIR G_DIR_SEPARATOR_S rcfile, \ + RC_DIR G_DIR_SEPARATOR_S rcfile, \ + conv_get_locale_charset_str()); \ + } + + COPY_FILE(ACCOUNT_RC); + COPY_FILE(ACTIONS_RC); + COPY_FILE(COMMON_RC); + COPY_FILE(CUSTOM_HEADER_RC); + COPY_FILE(DISPLAY_HEADER_RC); + COPY_FILE(FILTER_HEADER_RC); + COPY_FILE(COMMAND_HISTORY); + + #undef COPY_FILE + + if (is_file_exist(OLD_RC_DIR G_DIR_SEPARATOR_S FILTER_LIST)) + copy_file(OLD_RC_DIR G_DIR_SEPARATOR_S FILTER_LIST, + RC_DIR G_DIR_SEPARATOR_S FILTER_LIST, FALSE); + if (is_file_exist(OLD_RC_DIR G_DIR_SEPARATOR_S FOLDER_LIST)) + copy_file(OLD_RC_DIR G_DIR_SEPARATOR_S FOLDER_LIST, + RC_DIR G_DIR_SEPARATOR_S FOLDER_LIST, FALSE); + if (is_file_exist(OLD_RC_DIR G_DIR_SEPARATOR_S "mime.types")) + copy_file(OLD_RC_DIR G_DIR_SEPARATOR_S "mime.types", + RC_DIR G_DIR_SEPARATOR_S "mime.types", FALSE); + + if (is_dir_exist(OLD_RC_DIR G_DIR_SEPARATOR_S TEMPLATE_DIR)) + conv_copy_dir(OLD_RC_DIR G_DIR_SEPARATOR_S TEMPLATE_DIR, + RC_DIR G_DIR_SEPARATOR_S TEMPLATE_DIR, + conv_get_locale_charset_str()); + if (is_dir_exist(OLD_RC_DIR G_DIR_SEPARATOR_S UIDL_DIR)) + copy_dir(OLD_RC_DIR G_DIR_SEPARATOR_S UIDL_DIR, + RC_DIR G_DIR_SEPARATOR_S UIDL_DIR); + + if (!is_file_exist(OLD_RC_DIR G_DIR_SEPARATOR_S ADDRESSBOOK_INDEX_FILE)) + return; + + if ((dir = g_dir_open(OLD_RC_DIR, 0, NULL)) == NULL) { + g_warning("failed to open directory: %s\n", OLD_RC_DIR); + return; + } + + pspec = g_pattern_spec_new("addrbook-*.xml"); + + while ((dir_name = g_dir_read_name(dir)) != NULL) { + if (g_pattern_match_string(pspec, dir_name)) { + gchar *old_file; + gchar *new_file; + + old_file = g_strconcat(OLD_RC_DIR G_DIR_SEPARATOR_S, + dir_name, NULL); + new_file = g_strconcat(RC_DIR G_DIR_SEPARATOR_S, + dir_name, NULL); + copy_file(old_file, new_file, FALSE); + g_free(new_file); + g_free(old_file); + } + } + + g_pattern_spec_free(pspec); + g_dir_close(dir); + } + + static void open_compose_new(const gchar *address, GPtrArray *attach_files) + { + gchar *utf8addr = NULL; + #ifdef G_OS_WIN32 + GPtrArray *utf8files = NULL; + #endif + + if (gtkut_window_modal_exist()) + return; + + if (address) { + utf8addr = g_locale_to_utf8(address, -1, NULL, NULL, NULL); + if (utf8addr) + g_strstrip(utf8addr); + } + + #ifdef G_OS_WIN32 + if (attach_files) { + gint i; + gchar *file, *utf8file; + + utf8files = g_ptr_array_new(); + for (i = 0; i < attach_files->len; i++) { + file = g_ptr_array_index(attach_files, i); + utf8file = g_locale_to_utf8(file, -1, NULL, NULL, NULL); + if (utf8file) + g_ptr_array_add(utf8files, utf8file); + } + } + + compose_new(NULL, NULL, utf8addr, utf8files); + if (utf8files) { + ptr_array_free_strings(utf8files); + g_ptr_array_free(utf8files, TRUE); + } + #else + compose_new(NULL, NULL, utf8addr, attach_files); + #endif + + g_free(utf8addr); + } + + static void open_message(const gchar *path) + { + gchar *id; + gchar *msg; + gint num; + FolderItem *item; + MsgInfo *msginfo; + MessageView *msgview; + + if (gtkut_window_modal_exist()) + return; + + id = g_path_get_dirname(path); + msg = g_path_get_basename(path); + num = to_number(msg); + item = folder_find_item_from_identifier(id); + debug_print("open folder id: %s (msg %d)\n", id, num); + + if (num > 0 && item) { + msginfo = folder_item_get_msginfo(item, num); + if (msginfo) { + msgview = messageview_create_with_new_window(); + messageview_show(msgview, msginfo, FALSE); + procmsg_msginfo_free(msginfo); + } else + debug_print("message %d not found\n", num); + } + + g_free(msg); + g_free(id); + } + + static void send_queue(void) + { + GList *list; + + if (gtkut_window_modal_exist()) + return; + if (!main_window_toggle_online_if_offline(main_window_get())) + return; + + for (list = folder_get_list(); list != NULL; list = list->next) { + Folder *folder = list->data; + + if (folder->queue) { + gint ret; + + ret = send_message_queue_all(folder->queue, + prefs_common.savemsg, + prefs_common.filter_sent); + statusbar_pop_all(); + if (ret > 0) + folder_item_scan(folder->queue); + } + } + + folderview_update_all_updated(TRUE); + main_window_set_menu_sensitive(main_window_get()); + main_window_set_toolbar_sensitive(main_window_get()); + } diff --git a/src/test/resources/unparser/diff/error01.txt b/src/test/resources/unparser/diff/error01.txt new file mode 100644 index 00000000..cf326c3f --- /dev/null +++ b/src/test/resources/unparser/diff/error01.txt @@ -0,0 +1,5364 @@ +textDiff: + /* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read a list of people who contributed. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + #include "vim.h" + + #ifdef HAVE_FCNTL_H + # include /* for chdir() */ + #endif + + static int path_is_url __ARGS((char_u *p)); + #if defined(FEAT_WINDOWS) || defined(PROTO) + static int win_split_ins __ARGS((int size, int flags, win_T *newwin, int dir)); + static int win_comp_pos __ARGS((void)); + static void frame_comp_pos __ARGS((frame_T *topfrp, int *row, int *col)); + static void frame_setheight __ARGS((frame_T *curfrp, int height)); + #ifdef FEAT_VERTSPLIT + static void frame_setwidth __ARGS((frame_T *curfrp, int width)); + #endif + static void win_exchange __ARGS((long)); + static void win_rotate __ARGS((int, int)); + static void win_totop __ARGS((int size, int flags)); + static void win_equal_rec __ARGS((win_T *next_curwin, int current, frame_T *topfr, int dir, int col, int row, int width, int height)); + static win_T *win_free_mem __ARGS((win_T *win, int *dirp)); + static win_T *winframe_remove __ARGS((win_T *win, int *dirp)); + static frame_T *win_altframe __ARGS((win_T *win)); ++static tabpage_T *alt_tabpage __ARGS((void)); + static win_T *frame2win __ARGS((frame_T *frp)); + static int frame_has_win __ARGS((frame_T *frp, win_T *wp)); + static void frame_new_height __ARGS((frame_T *topfrp, int height, int topfirst, int wfh)); + static int frame_fixed_height __ARGS((frame_T *frp)); + #ifdef FEAT_VERTSPLIT + static void frame_add_statusline __ARGS((frame_T *frp)); + static void frame_new_width __ARGS((frame_T *topfrp, int width, int leftfirst)); + static void frame_add_vsep __ARGS((frame_T *frp)); + static int frame_minwidth __ARGS((frame_T *topfrp, win_T *next_curwin)); + static void frame_fix_width __ARGS((win_T *wp)); + #endif ++#endif ++static int win_alloc_firstwin __ARGS((void)); ++#if defined(FEAT_WINDOWS) || defined(PROTO) ++static tabpage_T *current_tabpage __ARGS((void)); ++static void leave_tabpage __ARGS((tabpage_T *tp)); ++static void enter_tabpage __ARGS((tabpage_T *tp, buf_T *old_curbuf)); + static void frame_fix_height __ARGS((win_T *wp)); + static int frame_minheight __ARGS((frame_T *topfrp, win_T *next_curwin)); + static void win_enter_ext __ARGS((win_T *wp, int undo_sync, int no_curwin)); + static void win_free __ARGS((win_T *wp)); + static void win_append __ARGS((win_T *, win_T *)); + static void win_remove __ARGS((win_T *)); + static void frame_append __ARGS((frame_T *after, frame_T *frp)); + static void frame_insert __ARGS((frame_T *before, frame_T *frp)); + static void frame_remove __ARGS((frame_T *frp)); + #ifdef FEAT_VERTSPLIT + static void win_new_width __ARGS((win_T *wp, int width)); + static void win_goto_ver __ARGS((int up, long count)); + static void win_goto_hor __ARGS((int left, long count)); + #endif + static void frame_add_height __ARGS((frame_T *frp, int n)); + static void last_status_rec __ARGS((frame_T *fr, int statusline)); + + static void make_snapshot __ARGS((void)); + static void make_snapshot_rec __ARGS((frame_T *fr, frame_T **frp)); + static void clear_snapshot __ARGS((void)); + static void clear_snapshot_rec __ARGS((frame_T *fr)); + static void restore_snapshot __ARGS((int close_curwin)); + static int check_snapshot_rec __ARGS((frame_T *sn, frame_T *fr)); + static win_T *restore_snapshot_rec __ARGS((frame_T *sn, frame_T *fr)); + + #endif /* FEAT_WINDOWS */ + static win_T *win_alloc __ARGS((win_T *after)); + static void win_new_height __ARGS((win_T *, int)); + + #define URL_SLASH 1 /* path_is_url() has found "://" */ + #define URL_BACKSLASH 2 /* path_is_url() has found ":\\" */ + + #define NOWIN (win_T *)-1 /* non-exisiting window */ + + #ifdef FEAT_WINDOWS + static long p_ch_used = 1L; /* value of 'cmdheight' when frame + size was set */ ++# define ROWS_AVAIL (Rows - p_ch - tabpageline_height()) ++#else ++# define ROWS_AVAIL (Rows - p_ch) + #endif + + #if defined(FEAT_WINDOWS) || defined(PROTO) + /* + * all CTRL-W window commands are handled here, called from normal_cmd(). + */ + void + do_window(nchar, Prenum, xchar) + int nchar; + long Prenum; + int xchar; /* extra char from ":wincmd gx" or NUL */ + { + long Prenum1; + win_T *wp; + #if defined(FEAT_SEARCHPATH) || defined(FEAT_FIND_ID) + char_u *ptr; + #endif + #ifdef FEAT_FIND_ID + int type = FIND_DEFINE; + int len; + #endif + char_u cbuf[40]; + + if (Prenum == 0) + Prenum1 = 1; + else + Prenum1 = Prenum; + + #ifdef FEAT_CMDWIN + # define CHECK_CMDWIN if (cmdwin_type != 0) { EMSG(_(e_cmdwin)); break; } + #else + # define CHECK_CMDWIN + #endif + + switch (nchar) + { + /* split current window in two parts, horizontally */ + case 'S': + case Ctrl_S: + case 's': + CHECK_CMDWIN + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + #ifdef FEAT_QUICKFIX + /* When splitting the quickfix window open a new buffer in it, + * don't replicate the quickfix buffer. */ + if (bt_quickfix(curbuf)) + goto newwindow; + #endif + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_split((int)Prenum, 0); + break; + + #ifdef FEAT_VERTSPLIT + /* split current window in two parts, vertically */ + case Ctrl_V: + case 'v': + CHECK_CMDWIN + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_split((int)Prenum, WSP_VERT); + break; + #endif + + /* split current window and edit alternate file */ + case Ctrl_HAT: + case '^': + CHECK_CMDWIN + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + STRCPY(cbuf, "split #"); + if (Prenum) + sprintf((char *)cbuf + 7, "%ld", Prenum); + do_cmdline_cmd(cbuf); + break; + + /* open new window */ + case Ctrl_N: + case 'n': + CHECK_CMDWIN + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + #ifdef FEAT_QUICKFIX + newwindow: + #endif + if (Prenum) + sprintf((char *)cbuf, "%ld", Prenum); /* window height */ + else + cbuf[0] = NUL; + STRCAT(cbuf, "new"); + do_cmdline_cmd(cbuf); + break; + + /* quit current window */ + case Ctrl_Q: + case 'q': + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + do_cmdline_cmd((char_u *)"quit"); + break; + + /* close current window */ + case Ctrl_C: + case 'c': + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + do_cmdline_cmd((char_u *)"close"); + break; + + #if defined(FEAT_WINDOWS) && defined(FEAT_QUICKFIX) + /* close preview window */ + case Ctrl_Z: + case 'z': + CHECK_CMDWIN + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + do_cmdline_cmd((char_u *)"pclose"); + break; + + /* cursor to preview window */ + case 'P': + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_p_pvw) + break; + if (wp == NULL) + EMSG(_("E441: There is no preview window")); + else + win_goto(wp); + break; + #endif + + /* close all but current window */ + case Ctrl_O: + case 'o': + CHECK_CMDWIN + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + do_cmdline_cmd((char_u *)"only"); + break; + + /* cursor to next window with wrap around */ + case Ctrl_W: + case 'w': + /* cursor to previous window with wrap around */ + case 'W': + CHECK_CMDWIN + if (lastwin == firstwin && Prenum != 1) /* just one window */ + beep_flush(); + else + { + if (Prenum) /* go to specified window */ + { + for (wp = firstwin; --Prenum > 0; ) + { + if (wp->w_next == NULL) + break; + else + wp = wp->w_next; + } + } + else + { + if (nchar == 'W') /* go to previous window */ + { + wp = curwin->w_prev; + if (wp == NULL) + wp = lastwin; /* wrap around */ + } + else /* go to next window */ + { + wp = curwin->w_next; + if (wp == NULL) + wp = firstwin; /* wrap around */ + } + } + win_goto(wp); + } + break; + + /* cursor to window below */ + case 'j': + case K_DOWN: + case Ctrl_J: + CHECK_CMDWIN + #ifdef FEAT_VERTSPLIT + win_goto_ver(FALSE, Prenum1); + #else + for (wp = curwin; wp->w_next != NULL && Prenum1-- > 0; + wp = wp->w_next) + ; + win_goto(wp); + #endif + break; + + /* cursor to window above */ + case 'k': + case K_UP: + case Ctrl_K: + CHECK_CMDWIN + #ifdef FEAT_VERTSPLIT + win_goto_ver(TRUE, Prenum1); + #else + for (wp = curwin; wp->w_prev != NULL && Prenum1-- > 0; + wp = wp->w_prev) + ; + win_goto(wp); + #endif + break; + + #ifdef FEAT_VERTSPLIT + /* cursor to left window */ + case 'h': + case K_LEFT: + case Ctrl_H: + case K_BS: + CHECK_CMDWIN + win_goto_hor(TRUE, Prenum1); + break; + + /* cursor to right window */ + case 'l': + case K_RIGHT: + case Ctrl_L: + CHECK_CMDWIN + win_goto_hor(FALSE, Prenum1); + break; + #endif + + /* cursor to top-left window */ + case 't': + case Ctrl_T: + win_goto(firstwin); + break; + + /* cursor to bottom-right window */ + case 'b': + case Ctrl_B: + win_goto(lastwin); + break; + + /* cursor to last accessed (previous) window */ + case 'p': + case Ctrl_P: + if (prevwin == NULL) + beep_flush(); + else + win_goto(prevwin); + break; + + /* exchange current and next window */ + case 'x': + case Ctrl_X: + CHECK_CMDWIN + win_exchange(Prenum); + break; + + /* rotate windows downwards */ + case Ctrl_R: + case 'r': + CHECK_CMDWIN + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + win_rotate(FALSE, (int)Prenum1); /* downwards */ + break; + + /* rotate windows upwards */ + case 'R': + CHECK_CMDWIN + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + win_rotate(TRUE, (int)Prenum1); /* upwards */ + break; + + /* move window to the very top/bottom/left/right */ + case 'K': + case 'J': + #ifdef FEAT_VERTSPLIT + case 'H': + case 'L': + #endif + CHECK_CMDWIN + win_totop((int)Prenum, + ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0) + | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT)); + break; + + /* make all windows the same height */ + case '=': + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_equal(NULL, FALSE, 'b'); + break; + + /* increase current window height */ + case '+': + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_setheight(curwin->w_height + (int)Prenum1); + break; + + /* decrease current window height */ + case '-': + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_setheight(curwin->w_height - (int)Prenum1); + break; + + /* set current window height */ + case Ctrl__: + case '_': + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_setheight(Prenum ? (int)Prenum : 9999); + break; + + #ifdef FEAT_VERTSPLIT + /* increase current window width */ + case '>': + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_setwidth(curwin->w_width + (int)Prenum1); + break; + + /* decrease current window width */ + case '<': + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_setwidth(curwin->w_width - (int)Prenum1); + break; + + /* set current window width */ + case '|': + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_setwidth(Prenum != 0 ? (int)Prenum : 9999); + break; + #endif + + /* jump to tag and split window if tag exists (in preview window) */ + #if defined(FEAT_QUICKFIX) + case '}': + CHECK_CMDWIN + if (Prenum) + g_do_tagpreview = Prenum; + else + g_do_tagpreview = p_pvh; + /*FALLTHROUGH*/ + #endif + case ']': + case Ctrl_RSB: + CHECK_CMDWIN + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + if (Prenum) + postponed_split = Prenum; + else + postponed_split = -1; + + /* Execute the command right here, required when + * "wincmd ]" was used in a function. */ + do_nv_ident(Ctrl_RSB, NUL); + break; + + #ifdef FEAT_SEARCHPATH + /* edit file name under cursor in a new window */ + case 'f': + case Ctrl_F: + CHECK_CMDWIN + + ptr = grab_file_name(Prenum1); + if (ptr != NULL) + { + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + setpcmark(); + if (win_split(0, 0) == OK) + { + # ifdef FEAT_SCROLLBIND + curwin->w_p_scb = FALSE; + # endif + (void)do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, + ECMD_HIDE); + } + vim_free(ptr); + } + break; + #endif + + #ifdef FEAT_FIND_ID + /* Go to the first occurence of the identifier under cursor along path in a + * new window -- webb + */ + case 'i': /* Go to any match */ + case Ctrl_I: + type = FIND_ANY; + /* FALLTHROUGH */ + case 'd': /* Go to definition, using 'define' */ + case Ctrl_D: + CHECK_CMDWIN + if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) + break; + find_pattern_in_path(ptr, 0, len, TRUE, + Prenum == 0 ? TRUE : FALSE, type, + Prenum1, ACTION_SPLIT, (linenr_T)1, (linenr_T)MAXLNUM); + curwin->w_set_curswant = TRUE; + break; + #endif + + case K_KENTER: + case CAR: + #if defined(FEAT_QUICKFIX) + /* + * In a quickfix window a jumps to the error under the + * cursor in a new window. + */ + if (bt_quickfix(curbuf)) + { + sprintf((char *)cbuf, "split +%ld%s", + (long)curwin->w_cursor.lnum, + (curwin->w_llist_ref == NULL) ? "cc" : "ll"); + do_cmdline_cmd(cbuf); + } + #endif + break; + + + /* CTRL-W g extended commands */ + case 'g': + case Ctrl_G: + CHECK_CMDWIN + #ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; /* disallow scrolling here */ + #endif + ++no_mapping; + ++allow_keys; /* no mapping for xchar, but allow key codes */ + if (xchar == NUL) + xchar = safe_vgetc(); + #ifdef FEAT_LANGMAP + LANGMAP_ADJUST(xchar, TRUE); + #endif + --no_mapping; + --allow_keys; + #ifdef FEAT_CMDL_INFO + (void)add_to_showcmd(xchar); + #endif + switch (xchar) + { + #if defined(FEAT_QUICKFIX) + case '}': + xchar = Ctrl_RSB; + if (Prenum) + g_do_tagpreview = Prenum; + else + g_do_tagpreview = p_pvh; + /*FALLTHROUGH*/ + #endif + case ']': + case Ctrl_RSB: + #ifdef FEAT_VISUAL + reset_VIsual_and_resel(); /* stop Visual mode */ + #endif + if (Prenum) + postponed_split = Prenum; + else + postponed_split = -1; + + /* Execute the command right here, required when + * "wincmd g}" was used in a function. */ + do_nv_ident('g', xchar); + break; + + default: + beep_flush(); + break; + } + break; + + default: beep_flush(); + break; + } + } + + /* + * split the current window, implements CTRL-W s and :split + * + * "size" is the height or width for the new window, 0 to use half of current + * height or width. + * + * "flags": + * WSP_ROOM: require enough room for new window + * WSP_VERT: vertical split. + * WSP_TOP: open window at the top-left of the shell (help window). + * WSP_BOT: open window at the bottom-right of the shell (quickfix window). + * WSP_HELP: creating the help window, keep layout snapshot + * + * return FAIL for failure, OK otherwise + */ + int + win_split(size, flags) + int size; + int flags; + { + /* Add flags from ":vertical", ":topleft" and ":botright". */ + flags |= cmdmod.split; + if ((flags & WSP_TOP) && (flags & WSP_BOT)) + { + EMSG(_("E442: Can't split topleft and botright at the same time")); + return FAIL; + } + + /* When creating the help window make a snapshot of the window layout. + * Otherwise clear the snapshot, it's now invalid. */ + if (flags & WSP_HELP) + make_snapshot(); + else + clear_snapshot(); + + return win_split_ins(size, flags, NULL, 0); + } + + /* + * When "newwin" is NULL: split a window in two. + * When "newwin" is not NULL: insert this window at the far + * top/left/right/bottom. + * return FAIL for failure, OK otherwise + */ + static int + win_split_ins(size, flags, newwin, dir) + int size; + int flags; + win_T *newwin; + int dir; + { + win_T *wp = newwin; + win_T *oldwin; + int new_size = size; + int i; + int need_status = 0; + int do_equal = FALSE; + int needed; + int available; + int oldwin_height = 0; + int layout; + frame_T *frp, *curfrp; + int before; + + if (flags & WSP_TOP) + oldwin = firstwin; + else if (flags & WSP_BOT) + oldwin = lastwin; + else + oldwin = curwin; + + /* add a status line when p_ls == 1 and splitting the first window */ + if (lastwin == firstwin && p_ls == 1 && oldwin->w_status_height == 0) + { + if (oldwin->w_height <= p_wmh && newwin == NULL) + { + EMSG(_(e_noroom)); + return FAIL; + } + need_status = STATUS_HEIGHT; + } + + #ifdef FEAT_VERTSPLIT + if (flags & WSP_VERT) + { + layout = FR_ROW; + do_equal = (p_ea && new_size == 0 && *p_ead != 'v'); + + /* + * Check if we are able to split the current window and compute its + * width. + */ + needed = p_wmw + 1; + if (flags & WSP_ROOM) + needed += p_wiw - p_wmw; + if (p_ea || (flags & (WSP_BOT | WSP_TOP))) + { + available = topframe->fr_width; + needed += frame_minwidth(topframe, NULL); + } + else + available = oldwin->w_width; + if (available < needed && newwin == NULL) + { + EMSG(_(e_noroom)); + return FAIL; + } + if (new_size == 0) + new_size = oldwin->w_width / 2; + if (new_size > oldwin->w_width - p_wmw - 1) + new_size = oldwin->w_width - p_wmw - 1; + if (new_size < p_wmw) + new_size = p_wmw; + + /* if it doesn't fit in the current window, need win_equal() */ + if (oldwin->w_width - new_size - 1 < p_wmw) + do_equal = TRUE; + } + else + #endif + { + layout = FR_COL; + do_equal = (p_ea && new_size == 0 + #ifdef FEAT_VERTSPLIT + && *p_ead != 'h' + #endif + ); + + /* + * Check if we are able to split the current window and compute its + * height. + */ + needed = p_wmh + STATUS_HEIGHT + need_status; + if (flags & WSP_ROOM) + needed += p_wh - p_wmh; + if (p_ea || (flags & (WSP_BOT | WSP_TOP))) + { + available = topframe->fr_height; + needed += frame_minheight(topframe, NULL); + } + else + { + available = oldwin->w_height; + needed += p_wmh; + } + if (available < needed && newwin == NULL) + { + EMSG(_(e_noroom)); + return FAIL; + } + oldwin_height = oldwin->w_height; + if (need_status) + { + oldwin->w_status_height = STATUS_HEIGHT; + oldwin_height -= STATUS_HEIGHT; + } + if (new_size == 0) + new_size = oldwin_height / 2; + + if (new_size > oldwin_height - p_wmh - STATUS_HEIGHT) + new_size = oldwin_height - p_wmh - STATUS_HEIGHT; + if (new_size < p_wmh) + new_size = p_wmh; + + /* if it doesn't fit in the current window, need win_equal() */ + if (oldwin_height - new_size - STATUS_HEIGHT < p_wmh) + do_equal = TRUE; + + /* We don't like to take lines for the new window from a + * 'winfixheight' window. Take them from a window above or below + * instead, if possible. */ + if (oldwin->w_p_wfh) + { + win_setheight_win(oldwin->w_height + new_size + STATUS_HEIGHT, + oldwin); + oldwin_height = oldwin->w_height; + if (need_status) + oldwin_height -= STATUS_HEIGHT; + } + } + + /* + * allocate new window structure and link it in the window list + */ + if ((flags & WSP_TOP) == 0 + && ((flags & WSP_BOT) + || (flags & WSP_BELOW) + || (!(flags & WSP_ABOVE) + && ( + #ifdef FEAT_VERTSPLIT + (flags & WSP_VERT) ? p_spr : + #endif + p_sb)))) + { + /* new window below/right of current one */ + if (newwin == NULL) + wp = win_alloc(oldwin); + else + win_append(oldwin, wp); + } + else + { + if (newwin == NULL) + wp = win_alloc(oldwin->w_prev); + else + win_append(oldwin->w_prev, wp); + } + + if (newwin == NULL) + { + if (wp == NULL) + return FAIL; + + /* + * make the contents of the new window the same as the current one + */ + wp->w_buffer = curbuf; + curbuf->b_nwindows++; + wp->w_cursor = curwin->w_cursor; + wp->w_valid = 0; + wp->w_curswant = curwin->w_curswant; + wp->w_set_curswant = curwin->w_set_curswant; + wp->w_topline = curwin->w_topline; + #ifdef FEAT_DIFF + wp->w_topfill = curwin->w_topfill; + #endif + wp->w_leftcol = curwin->w_leftcol; + wp->w_pcmark = curwin->w_pcmark; + wp->w_prev_pcmark = curwin->w_prev_pcmark; + wp->w_alt_fnum = curwin->w_alt_fnum; + wp->w_fraction = curwin->w_fraction; + wp->w_prev_fraction_row = curwin->w_prev_fraction_row; + #ifdef FEAT_JUMPLIST + copy_jumplist(curwin, wp); + #endif + #ifdef FEAT_QUICKFIX + copy_loclist(curwin, wp); + #endif + if (curwin->w_localdir != NULL) + wp->w_localdir = vim_strsave(curwin->w_localdir); + + /* Use the same argument list. */ + wp->w_alist = curwin->w_alist; + ++wp->w_alist->al_refcount; + wp->w_arg_idx = curwin->w_arg_idx; + + /* + * copy tagstack and options from existing window + */ + for (i = 0; i < curwin->w_tagstacklen; i++) + { + wp->w_tagstack[i] = curwin->w_tagstack[i]; + if (wp->w_tagstack[i].tagname != NULL) + wp->w_tagstack[i].tagname = + vim_strsave(wp->w_tagstack[i].tagname); + } + wp->w_tagstackidx = curwin->w_tagstackidx; + wp->w_tagstacklen = curwin->w_tagstacklen; + win_copy_options(curwin, wp); + #ifdef FEAT_FOLDING + copyFoldingState(curwin, wp); + #endif + } + + /* + * Reorganise the tree of frames to insert the new window. + */ + if (flags & (WSP_TOP | WSP_BOT)) + { + #ifdef FEAT_VERTSPLIT + if ((topframe->fr_layout == FR_COL && (flags & WSP_VERT) == 0) + || (topframe->fr_layout == FR_ROW && (flags & WSP_VERT) != 0)) + #else + if (topframe->fr_layout == FR_COL) + #endif + { + curfrp = topframe->fr_child; + if (flags & WSP_BOT) + while (curfrp->fr_next != NULL) + curfrp = curfrp->fr_next; + } + else + curfrp = topframe; + before = (flags & WSP_TOP); + } + else + { + curfrp = oldwin->w_frame; + if (flags & WSP_BELOW) + before = FALSE; + else if (flags & WSP_ABOVE) + before = TRUE; + else + #ifdef FEAT_VERTSPLIT + if (flags & WSP_VERT) + before = !p_spr; + else + #endif + before = !p_sb; + } + if (curfrp->fr_parent == NULL || curfrp->fr_parent->fr_layout != layout) + { + /* Need to create a new frame in the tree to make a branch. */ + frp = (frame_T *)alloc_clear((unsigned)sizeof(frame_T)); + *frp = *curfrp; + curfrp->fr_layout = layout; + frp->fr_parent = curfrp; + frp->fr_next = NULL; + frp->fr_prev = NULL; + curfrp->fr_child = frp; + curfrp->fr_win = NULL; + curfrp = frp; + if (frp->fr_win != NULL) + oldwin->w_frame = frp; + else + for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) + frp->fr_parent = curfrp; + } + + if (newwin == NULL) + { + /* Create a frame for the new window. */ + frp = (frame_T *)alloc_clear((unsigned)sizeof(frame_T)); + frp->fr_layout = FR_LEAF; + frp->fr_win = wp; + wp->w_frame = frp; + } + else + frp = newwin->w_frame; + frp->fr_parent = curfrp->fr_parent; + + /* Insert the new frame at the right place in the frame list. */ + if (before) + frame_insert(curfrp, frp); + else + frame_append(curfrp, frp); + + #ifdef FEAT_VERTSPLIT + if (flags & WSP_VERT) + { + wp->w_p_scr = curwin->w_p_scr; + if (need_status) + { + --oldwin->w_height; + oldwin->w_status_height = need_status; + } + if (flags & (WSP_TOP | WSP_BOT)) + { + /* set height and row of new window to full height */ +- wp->w_winrow = 0; ++ wp->w_winrow = tabpageline_height(); + wp->w_height = curfrp->fr_height - (p_ls > 0); + wp->w_status_height = (p_ls > 0); + } + else + { + /* height and row of new window is same as current window */ + wp->w_winrow = oldwin->w_winrow; + wp->w_height = oldwin->w_height; + wp->w_status_height = oldwin->w_status_height; + } + frp->fr_height = curfrp->fr_height; + + /* "new_size" of the current window goes to the new window, use + * one column for the vertical separator */ + wp->w_width = new_size; + if (before) + wp->w_vsep_width = 1; + else + { + wp->w_vsep_width = oldwin->w_vsep_width; + oldwin->w_vsep_width = 1; + } + if (flags & (WSP_TOP | WSP_BOT)) + { + if (flags & WSP_BOT) + frame_add_vsep(curfrp); + /* Set width of neighbor frame */ + frame_new_width(curfrp, curfrp->fr_width + - (new_size + ((flags & WSP_TOP) != 0)), flags & WSP_TOP); + } + else + oldwin->w_width -= new_size + 1; + if (before) /* new window left of current one */ + { + wp->w_wincol = oldwin->w_wincol; + oldwin->w_wincol += new_size + 1; + } + else /* new window right of current one */ + wp->w_wincol = oldwin->w_wincol + oldwin->w_width + 1; + frame_fix_width(oldwin); + frame_fix_width(wp); + } + else + #endif + { + /* width and column of new window is same as current window */ + #ifdef FEAT_VERTSPLIT + if (flags & (WSP_TOP | WSP_BOT)) + { + wp->w_wincol = 0; + wp->w_width = Columns; + wp->w_vsep_width = 0; + } + else + { + wp->w_wincol = oldwin->w_wincol; + wp->w_width = oldwin->w_width; + wp->w_vsep_width = oldwin->w_vsep_width; + } + frp->fr_width = curfrp->fr_width; + #endif + + /* "new_size" of the current window goes to the new window, use + * one row for the status line */ + win_new_height(wp, new_size); + if (flags & (WSP_TOP | WSP_BOT)) + frame_new_height(curfrp, curfrp->fr_height + - (new_size + STATUS_HEIGHT), flags & WSP_TOP, FALSE); + else + win_new_height(oldwin, oldwin_height - (new_size + STATUS_HEIGHT)); + if (before) /* new window above current one */ + { + wp->w_winrow = oldwin->w_winrow; + wp->w_status_height = STATUS_HEIGHT; + oldwin->w_winrow += wp->w_height + STATUS_HEIGHT; + } + else /* new window below current one */ + { + wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT; + wp->w_status_height = oldwin->w_status_height; + oldwin->w_status_height = STATUS_HEIGHT; + } + #ifdef FEAT_VERTSPLIT + if (flags & WSP_BOT) + frame_add_statusline(curfrp); + #endif + frame_fix_height(wp); + frame_fix_height(oldwin); + } + + if (flags & (WSP_TOP | WSP_BOT)) + (void)win_comp_pos(); + + /* + * Both windows need redrawing + */ + redraw_win_later(wp, NOT_VALID); + wp->w_redr_status = TRUE; + redraw_win_later(oldwin, NOT_VALID); + oldwin->w_redr_status = TRUE; + + if (need_status) + { + msg_row = Rows - 1; + msg_col = sc_col; + msg_clr_eos_force(); /* Old command/ruler may still be there */ + comp_col(); + msg_row = Rows - 1; + msg_col = 0; /* put position back at start of line */ + } + + /* + * make the new window the current window and redraw + */ + if (do_equal || dir != 0) + win_equal(wp, TRUE, + #ifdef FEAT_VERTSPLIT + (flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h') + : dir == 'h' ? 'b' : + #endif + 'v'); + + /* Don't change the window height/width to 'winheight' / 'winwidth' if a + * size was given. */ + #ifdef FEAT_VERTSPLIT + if (flags & WSP_VERT) + { + i = p_wiw; + if (size != 0) + p_wiw = size; + + # ifdef FEAT_GUI + /* When 'guioptions' includes 'L' or 'R' may have to add scrollbars. */ + if (gui.in_use) + gui_init_which_components(NULL); + # endif + } + else + #endif + { + i = p_wh; + if (size != 0) + p_wh = size; + } + win_enter(wp, FALSE); + #ifdef FEAT_VERTSPLIT + if (flags & WSP_VERT) + p_wiw = i; + else + #endif + p_wh = i; + + return OK; + } + + #endif /* FEAT_WINDOWS */ + + #if defined(FEAT_WINDOWS) || defined(PROTO) + /* + * Check if "win" is a pointer to an existing window. + */ + int + win_valid(win) + win_T *win; + { + win_T *wp; + + if (win == NULL) + return FALSE; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp == win) + return TRUE; + return FALSE; + } + + /* + * Return the number of windows. + */ + int + win_count() + { + win_T *wp; + int count = 0; + + for (wp = firstwin; wp != NULL; wp = wp->w_next) + ++count; + return count; + } + + /* + * Make "count" windows on the screen. + * Return actual number of windows on the screen. + * Must be called when there is just one window, filling the whole screen + * (excluding the command line). + */ + /*ARGSUSED*/ + int + make_windows(count, vertical) + int count; + int vertical; /* split windows vertically if TRUE */ + { + int maxcount; + int todo; + + #ifdef FEAT_VERTSPLIT + if (vertical) + { + /* Each windows needs at least 'winminwidth' lines and a separator + * column. */ + maxcount = (curwin->w_width + curwin->w_vsep_width + - (p_wiw - p_wmw)) / (p_wmw + 1); + } + else + #endif + { + /* Each window needs at least 'winminheight' lines and a status line. */ + maxcount = (curwin->w_height + curwin->w_status_height + - (p_wh - p_wmh)) / (p_wmh + STATUS_HEIGHT); + } + + if (maxcount < 2) + maxcount = 2; + if (count > maxcount) + count = maxcount; + + /* + * add status line now, otherwise first window will be too big + */ + if (count > 1) + last_status(TRUE); + + #ifdef FEAT_AUTOCMD + /* + * Don't execute autocommands while creating the windows. Must do that + * when putting the buffers in the windows. + */ + ++autocmd_block; + #endif + + /* todo is number of windows left to create */ + for (todo = count - 1; todo > 0; --todo) + #ifdef FEAT_VERTSPLIT + if (vertical) + { + if (win_split(curwin->w_width - (curwin->w_width - todo) + / (todo + 1) - 1, WSP_VERT | WSP_ABOVE) == FAIL) + break; + } + else + #endif + { + if (win_split(curwin->w_height - (curwin->w_height - todo + * STATUS_HEIGHT) / (todo + 1) + - STATUS_HEIGHT, WSP_ABOVE) == FAIL) + break; + } + + #ifdef FEAT_AUTOCMD + --autocmd_block; + #endif + + /* return actual number of windows */ + return (count - todo); + } + + /* + * Exchange current and next window + */ + static void + win_exchange(Prenum) + long Prenum; + { + frame_T *frp; + frame_T *frp2; + win_T *wp; + win_T *wp2; + int temp; + + if (lastwin == firstwin) /* just one window */ + { + beep_flush(); + return; + } + + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + + /* + * find window to exchange with + */ + if (Prenum) + { + frp = curwin->w_frame->fr_parent->fr_child; + while (frp != NULL && --Prenum > 0) + frp = frp->fr_next; + } + else if (curwin->w_frame->fr_next != NULL) /* Swap with next */ + frp = curwin->w_frame->fr_next; + else /* Swap last window in row/col with previous */ + frp = curwin->w_frame->fr_prev; + + /* We can only exchange a window with another window, not with a frame + * containing windows. */ + if (frp == NULL || frp->fr_win == NULL || frp->fr_win == curwin) + return; + wp = frp->fr_win; + + /* + * 1. remove curwin from the list. Remember after which window it was in wp2 + * 2. insert curwin before wp in the list + * if wp != wp2 + * 3. remove wp from the list + * 4. insert wp after wp2 + * 5. exchange the status line height and vsep width. + */ + wp2 = curwin->w_prev; + frp2 = curwin->w_frame->fr_prev; + if (wp->w_prev != curwin) + { + win_remove(curwin); + frame_remove(curwin->w_frame); + win_append(wp->w_prev, curwin); + frame_insert(frp, curwin->w_frame); + } + if (wp != wp2) + { + win_remove(wp); + frame_remove(wp->w_frame); + win_append(wp2, wp); + if (frp2 == NULL) + frame_insert(wp->w_frame->fr_parent->fr_child, wp->w_frame); + else + frame_append(frp2, wp->w_frame); + } + temp = curwin->w_status_height; + curwin->w_status_height = wp->w_status_height; + wp->w_status_height = temp; + #ifdef FEAT_VERTSPLIT + temp = curwin->w_vsep_width; + curwin->w_vsep_width = wp->w_vsep_width; + wp->w_vsep_width = temp; + + /* If the windows are not in the same frame, exchange the sizes to avoid + * messing up the window layout. Otherwise fix the frame sizes. */ + if (curwin->w_frame->fr_parent != wp->w_frame->fr_parent) + { + temp = curwin->w_height; + curwin->w_height = wp->w_height; + wp->w_height = temp; + temp = curwin->w_width; + curwin->w_width = wp->w_width; + wp->w_width = temp; + } + else + { + frame_fix_height(curwin); + frame_fix_height(wp); + frame_fix_width(curwin); + frame_fix_width(wp); + } + #endif + + (void)win_comp_pos(); /* recompute window positions */ + + win_enter(wp, TRUE); + redraw_later(CLEAR); + } + + /* + * rotate windows: if upwards TRUE the second window becomes the first one + * if upwards FALSE the first window becomes the second one + */ + static void + win_rotate(upwards, count) + int upwards; + int count; + { + win_T *wp1; + win_T *wp2; + frame_T *frp; + int n; + + if (firstwin == lastwin) /* nothing to do */ + { + beep_flush(); + return; + } + + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + + #ifdef FEAT_VERTSPLIT + /* Check if all frames in this row/col have one window. */ + for (frp = curwin->w_frame->fr_parent->fr_child; frp != NULL; + frp = frp->fr_next) + if (frp->fr_win == NULL) + { + EMSG(_("E443: Cannot rotate when another window is split")); + return; + } + #endif + + while (count--) + { + if (upwards) /* first window becomes last window */ + { + /* remove first window/frame from the list */ + frp = curwin->w_frame->fr_parent->fr_child; + wp1 = frp->fr_win; + win_remove(wp1); + frame_remove(frp); + + /* find last frame and append removed window/frame after it */ + for ( ; frp->fr_next != NULL; frp = frp->fr_next) + ; + win_append(frp->fr_win, wp1); + frame_append(frp, wp1->w_frame); + + wp2 = frp->fr_win; /* previously last window */ + } + else /* last window becomes first window */ + { + /* find last window/frame in the list and remove it */ + for (frp = curwin->w_frame; frp->fr_next != NULL; + frp = frp->fr_next) + ; + wp1 = frp->fr_win; + wp2 = wp1->w_prev; /* will become last window */ + win_remove(wp1); + frame_remove(frp); + + /* append the removed window/frame before the first in the list */ + win_append(frp->fr_parent->fr_child->fr_win->w_prev, wp1); + frame_insert(frp->fr_parent->fr_child, frp); + } + + /* exchange status height and vsep width of old and new last window */ + n = wp2->w_status_height; + wp2->w_status_height = wp1->w_status_height; + wp1->w_status_height = n; + frame_fix_height(wp1); + frame_fix_height(wp2); + #ifdef FEAT_VERTSPLIT + n = wp2->w_vsep_width; + wp2->w_vsep_width = wp1->w_vsep_width; + wp1->w_vsep_width = n; + frame_fix_width(wp1); + frame_fix_width(wp2); + #endif + + /* recompute w_winrow and w_wincol for all windows */ + (void)win_comp_pos(); + } + + redraw_later(CLEAR); + } + + /* + * Move the current window to the very top/bottom/left/right of the screen. + */ + static void + win_totop(size, flags) + int size; + int flags; + { + int dir; + int height = curwin->w_height; + + if (lastwin == firstwin) + { + beep_flush(); + return; + } + + /* Remove the window and frame from the tree of frames. */ + (void)winframe_remove(curwin, &dir); + win_remove(curwin); + last_status(FALSE); /* may need to remove last status line */ + (void)win_comp_pos(); /* recompute window positions */ + + /* Split a window on the desired side and put the window there. */ + (void)win_split_ins(size, flags, curwin, dir); + if (!(flags & WSP_VERT)) + { + win_setheight(height); + if (p_ea) + win_equal(curwin, TRUE, 'v'); + } + + #if defined(FEAT_GUI) && defined(FEAT_VERTSPLIT) + /* When 'guioptions' includes 'L' or 'R' may have to remove or add + * scrollbars. Have to update them anyway. */ + if (gui.in_use) + { + out_flush(); + gui_init_which_components(NULL); + gui_update_scrollbars(TRUE); + } + need_mouse_correct = TRUE; + #endif + + } + + /* + * Move window "win1" to below/right of "win2" and make "win1" the current + * window. Only works within the same frame! + */ + void + win_move_after(win1, win2) + win_T *win1, *win2; + { + int height; + + /* check if the arguments are reasonable */ + if (win1 == win2) + return; + + /* check if there is something to do */ + if (win2->w_next != win1) + { + /* may need move the status line/vertical separator of the last window + * */ + if (win1 == lastwin) + { + height = win1->w_prev->w_status_height; + win1->w_prev->w_status_height = win1->w_status_height; + win1->w_status_height = height; + #ifdef FEAT_VERTSPLIT + win1->w_prev->w_vsep_width = 0; + win1->w_vsep_width = 1; + #endif + } + else if (win2 == lastwin) + { + height = win1->w_status_height; + win1->w_status_height = win2->w_status_height; + win2->w_status_height = height; + #ifdef FEAT_VERTSPLIT + win2->w_vsep_width = 1; + win1->w_vsep_width = 0; + #endif + } + win_remove(win1); + frame_remove(win1->w_frame); + win_append(win2, win1); + frame_append(win2->w_frame, win1->w_frame); + + (void)win_comp_pos(); /* recompute w_winrow for all windows */ + redraw_later(NOT_VALID); + } + win_enter(win1, FALSE); + } + + /* + * Make all windows the same height. + * 'next_curwin' will soon be the current window, make sure it has enough + * rows. + */ + void + win_equal(next_curwin, current, dir) + win_T *next_curwin; /* pointer to current window to be or NULL */ + int current; /* do only frame with current window */ + int dir; /* 'v' for vertically, 'h' for horizontally, + 'b' for both, 0 for using p_ead */ + { + if (dir == 0) + #ifdef FEAT_VERTSPLIT + dir = *p_ead; + #else + dir = 'b'; + #endif + win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current, +- topframe, dir, 0, 0, (int)Columns, topframe->fr_height); ++ topframe, dir, 0, tabpageline_height(), ++ (int)Columns, topframe->fr_height); + } + + /* + * Set a frame to a new position and height, spreading the available room + * equally over contained frames. + * The window "next_curwin" (if not NULL) should at least get the size from + * 'winheight' and 'winwidth' if possible. + */ + static void + win_equal_rec(next_curwin, current, topfr, dir, col, row, width, height) + win_T *next_curwin; /* pointer to current window to be or NULL */ + int current; /* do only frame with current window */ + frame_T *topfr; /* frame to set size off */ + int dir; /* 'v', 'h' or 'b', see win_equal() */ + int col; /* horizontal position for frame */ + int row; /* vertical position for frame */ + int width; /* new width of frame */ + int height; /* new height of frame */ + { + int n, m; + int extra_sep = 0; + int wincount, totwincount = 0; + frame_T *fr; + int next_curwin_size = 0; + int room = 0; + int new_size; + int has_next_curwin = 0; + int hnc; + + if (topfr->fr_layout == FR_LEAF) + { + /* Set the width/height of this frame. + * Redraw when size or position changes */ + if (topfr->fr_height != height || topfr->fr_win->w_winrow != row + #ifdef FEAT_VERTSPLIT + || topfr->fr_width != width || topfr->fr_win->w_wincol != col + #endif + ) + { + topfr->fr_win->w_winrow = row; + frame_new_height(topfr, height, FALSE, FALSE); + #ifdef FEAT_VERTSPLIT + topfr->fr_win->w_wincol = col; + frame_new_width(topfr, width, FALSE); + #endif + redraw_all_later(CLEAR); + } + } + #ifdef FEAT_VERTSPLIT + else if (topfr->fr_layout == FR_ROW) + { + topfr->fr_width = width; + topfr->fr_height = height; + + if (dir != 'v') /* equalize frame widths */ + { + /* Compute the maximum number of windows horizontally in this + * frame. */ + n = frame_minwidth(topfr, NOWIN); + /* add one for the rightmost window, it doesn't have a separator */ + if (col + width == Columns) + extra_sep = 1; + else + extra_sep = 0; + totwincount = (n + extra_sep) / (p_wmw + 1); + + /* Compute room available for windows other than "next_curwin" */ + m = frame_minwidth(topfr, next_curwin); + room = width - m; + if (room < 0) + { + next_curwin_size = p_wiw + room; + room = 0; + } + else if (n == m) /* doesn't contain curwin */ + next_curwin_size = 0; + else + { + next_curwin_size = (room + p_wiw + (totwincount - 1) * p_wmw + + (totwincount - 1)) / totwincount; + if (next_curwin_size > p_wiw) + room -= next_curwin_size - p_wiw; + else + next_curwin_size = p_wiw; + } + if (n != m) + --totwincount; /* don't count curwin */ + } + + for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) + { + n = m = 0; + wincount = 1; + if (fr->fr_next == NULL) + /* last frame gets all that remains (avoid roundoff error) */ + new_size = width; + else if (dir == 'v') + new_size = fr->fr_width; + else + { + /* Compute the maximum number of windows horiz. in "fr". */ + n = frame_minwidth(fr, NOWIN); + wincount = (n + (fr->fr_next == NULL ? extra_sep : 0)) + / (p_wmw + 1); + m = frame_minwidth(fr, next_curwin); + if (n != m) /* don't count next_curwin */ + --wincount; + new_size = (wincount * room + ((unsigned)totwincount >> 1)) + / totwincount; + if (n != m) /* add next_curwin size */ + { + next_curwin_size -= p_wiw - (m - n); + new_size += next_curwin_size; + } + } + + /* Skip frame that is full height when splitting or closing a + * window, unless equalizing all frames. */ + if (!current || dir != 'v' || topfr->fr_parent != NULL + || (new_size != fr->fr_width) + || frame_has_win(fr, next_curwin)) + win_equal_rec(next_curwin, current, fr, dir, col, row, + new_size + n, height); + col += new_size + n; + width -= new_size + n; + if (n != m) /* contains curwin */ + room -= new_size - next_curwin_size; + else + room -= new_size; + totwincount -= wincount; + } + } + #endif + else /* topfr->fr_layout == FR_COL */ + { + #ifdef FEAT_VERTSPLIT + topfr->fr_width = width; + #endif + topfr->fr_height = height; + + if (dir != 'h') /* equalize frame heights */ + { + /* Compute maximum number of windows vertically in this frame. */ + n = frame_minheight(topfr, NOWIN); + /* add one for the bottom window if it doesn't have a statusline */ + if (row + height == cmdline_row && p_ls == 0) + extra_sep = 1; + else + extra_sep = 0; + totwincount = (n + extra_sep) / (p_wmh + 1); + has_next_curwin = frame_has_win(topfr, next_curwin); + + /* + * Compute height for "next_curwin" window and room available for + * other windows. + * "m" is the minimal height when counting p_wh for "next_curwin". + */ + m = frame_minheight(topfr, next_curwin); + room = height - m; + if (room < 0) + { + /* The room is less then 'winheight', use all space for the + * current window. */ + next_curwin_size = p_wh + room; + room = 0; + } + else + { + next_curwin_size = -1; + for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) + { + /* If 'winfixheight' set keep the window height if + * possible. + * Watch out for this window being the next_curwin. */ + if (frame_fixed_height(fr)) + { + n = frame_minheight(fr, NOWIN); + new_size = fr->fr_height; + if (frame_has_win(fr, next_curwin)) + { + room += p_wh - p_wmh; + next_curwin_size = 0; + if (new_size < p_wh) + new_size = p_wh; + } + else + /* These windows don't use up room. */ + totwincount -= (n + (fr->fr_next == NULL + ? extra_sep : 0)) / (p_wmh + 1); + room -= new_size - n; + if (room < 0) + { + new_size += room; + room = 0; + } + fr->fr_newheight = new_size; + } + } + if (next_curwin_size == -1) + { + if (!has_next_curwin) + next_curwin_size = 0; + else if (totwincount > 1 + && (room + (totwincount - 2)) + / (totwincount - 1) > p_wh) + { + next_curwin_size = (room + p_wh + totwincount * p_wmh + + (totwincount - 1)) / totwincount; + room -= next_curwin_size - p_wh; + } + else + next_curwin_size = p_wh; + } + } + + if (has_next_curwin) + --totwincount; /* don't count curwin */ + } + + for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) + { + n = m = 0; + wincount = 1; + if (fr->fr_next == NULL) + /* last frame gets all that remains (avoid roundoff error) */ + new_size = height; + else if (dir == 'h') + new_size = fr->fr_height; + else if (frame_fixed_height(fr)) + { + new_size = fr->fr_newheight; + wincount = 0; /* doesn't count as a sizeable window */ + } + else + { + /* Compute the maximum number of windows vert. in "fr". */ + n = frame_minheight(fr, NOWIN); + wincount = (n + (fr->fr_next == NULL ? extra_sep : 0)) + / (p_wmh + 1); + m = frame_minheight(fr, next_curwin); + if (has_next_curwin) + hnc = frame_has_win(fr, next_curwin); + else + hnc = FALSE; + if (hnc) /* don't count next_curwin */ + --wincount; + if (totwincount == 0) + new_size = room; + else + new_size = (wincount * room + ((unsigned)totwincount >> 1)) + / totwincount; + if (hnc) /* add next_curwin size */ + { + next_curwin_size -= p_wh - (m - n); + new_size += next_curwin_size; + room -= new_size - next_curwin_size; + } + else + room -= new_size; + new_size += n; + } + /* Skip frame that is full width when splitting or closing a + * window, unless equalizing all frames. */ + if (!current || dir != 'h' || topfr->fr_parent != NULL + || (new_size != fr->fr_height) + || frame_has_win(fr, next_curwin)) + win_equal_rec(next_curwin, current, fr, dir, col, row, + width, new_size); + row += new_size; + height -= new_size; + totwincount -= wincount; + } + } + } + + /* + * close all windows for buffer 'buf' + */ + void + close_windows(buf) + buf_T *buf; + { + win_T *win; + + ++RedrawingDisabled; + for (win = firstwin; win != NULL && lastwin != firstwin; ) + { + if (win->w_buffer == buf) + { + win_close(win, FALSE); + win = firstwin; /* go back to the start */ + } + else + win = win->w_next; + } + --RedrawingDisabled; + } + + /* ++ * Return TRUE if the current window is the only window that exists. ++ * Returns FALSE if there is a window in another tab page. ++ */ ++ int ++last_window() ++{ ++ return (lastwin == firstwin && first_tabpage->tp_next == NULL); ++} ++ ++/* + * close window "win" + * If "free_buf" is TRUE related buffer may be unloaded. + * + * called by :quit, :close, :xit, :wq and findtag() + */ + void + win_close(win, free_buf) + win_T *win; + int free_buf; + { + win_T *wp; ++ buf_T *old_curbuf = curbuf; + #ifdef FEAT_AUTOCMD + int other_buffer = FALSE; + #endif + int close_curwin = FALSE; + int dir; + int help_window = FALSE; + +- if (lastwin == firstwin) ++ if (last_window()) + { + EMSG(_("E444: Cannot close last window")); + return; + } + + /* When closing the help window, try restoring a snapshot after closing + * the window. Otherwise clear the snapshot, it's now invalid. */ + if (win->w_buffer->b_help) + help_window = TRUE; + else + clear_snapshot(); + + #ifdef FEAT_AUTOCMD + if (win == curwin) + { + /* + * Guess which window is going to be the new current window. + * This may change because of the autocommands (sigh). + */ + wp = frame2win(win_altframe(win)); + + /* + * Be careful: If autocommands delete the window, return now. + */ + if (wp->w_buffer != curbuf) + { + other_buffer = TRUE; + apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf); +- if (!win_valid(win) || firstwin == lastwin) ++ if (!win_valid(win) || last_window()) + return; + } + apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE, curbuf); +- if (!win_valid(win) || firstwin == lastwin) ++ if (!win_valid(win) || last_window()) + return; + # ifdef FEAT_EVAL + /* autocmds may abort script processing */ + if (aborting()) + return; + # endif + } + #endif + + /* + * Close the link to the buffer. + */ + close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0); + /* Autocommands may have closed the window already, or closed the only + * other window. */ +- if (!win_valid(win) || firstwin == lastwin) ++ if (!win_valid(win) || last_window()) + return; + + /* Free the memory used for the window. */ + wp = win_free_mem(win, &dir); + ++ /* When closing the last window in a tab page go to another tab page. */ ++ if (wp == NULL) ++ { ++ tabpage_T *ptp = NULL; ++ tabpage_T *tp; ++ tabpage_T *atp = alt_tabpage(); ++ ++ for (tp = first_tabpage; tp->tp_topframe != topframe; tp = tp->tp_next) ++ ptp = tp; ++ if (tp == NULL) ++ { ++ EMSG2(_(e_intern2), "win_close()"); ++ return; ++ } ++ if (ptp == NULL) ++ first_tabpage = tp->tp_next; ++ else ++ ptp->tp_next = tp->tp_next; ++ vim_free(tp); ++ ++ /* We don't do the window resizing stuff, let enter_tabpage() take ++ * care of entering a window in another tab page. */ ++ enter_tabpage(atp, old_curbuf); ++ return; ++ } ++ + /* Make sure curwin isn't invalid. It can cause severe trouble when + * printing an error message. For win_equal() curbuf needs to be valid + * too. */ +- if (win == curwin) ++ else if (win == curwin) + { + curwin = wp; + #ifdef FEAT_QUICKFIX + if (wp->w_p_pvw || bt_quickfix(wp->w_buffer)) + { + /* + * The cursor goes to the preview or the quickfix window, try + * finding another window to go to. + */ + for (;;) + { + if (wp->w_next == NULL) + wp = firstwin; + else + wp = wp->w_next; + if (wp == curwin) + break; + if (!wp->w_p_pvw && !bt_quickfix(wp->w_buffer)) + { + curwin = wp; + break; + } + } + } + #endif + curbuf = curwin->w_buffer; + close_curwin = TRUE; + } + if (p_ea + #ifdef FEAT_VERTSPLIT + && (*p_ead == 'b' || *p_ead == dir) + #endif + ) + win_equal(curwin, TRUE, + #ifdef FEAT_VERTSPLIT + dir + #else + 0 + #endif + ); + else + win_comp_pos(); + if (close_curwin) + { + win_enter_ext(wp, FALSE, TRUE); + #ifdef FEAT_AUTOCMD + if (other_buffer) + /* careful: after this wp and win may be invalid! */ + apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); + #endif + } + + /* +- * if last window has a status line now and we don't want one, +- * remove the status line ++ * If last window has a status line now and we don't want one, ++ * remove the status line. + */ + last_status(FALSE); + + /* After closing the help window, try restoring the window layout from + * before it was opened. */ + if (help_window) + restore_snapshot(close_curwin); + + #if defined(FEAT_GUI) && defined(FEAT_VERTSPLIT) + /* When 'guioptions' includes 'L' or 'R' may have to remove scrollbars. */ + if (gui.in_use && !win_hasvertsplit()) + gui_init_which_components(NULL); + #endif + + redraw_all_later(NOT_VALID); + } + + /* + * Free the memory used for a window. + * Returns a pointer to the window that got the freed up space. + */ + static win_T * + win_free_mem(win, dirp) + win_T *win; + int *dirp; /* set to 'v' or 'h' for direction if 'ea' */ + { + frame_T *frp; + win_T *wp; + + #ifdef FEAT_FOLDING + clearFolding(win); + #endif + + /* reduce the reference count to the argument list. */ + alist_unlink(win->w_alist); + +- /* remove the window and its frame from the tree of frames. */ ++ /* Remove the window and its frame from the tree of frames. */ + frp = win->w_frame; +- wp = winframe_remove(win, dirp); ++ if (firstwin == lastwin) ++ /* Last window in a tab page. */ ++ wp = NULL; ++ else ++ wp = winframe_remove(win, dirp); + vim_free(frp); + win_free(win); + + return wp; + } + + #if defined(EXITFREE) || defined(PROTO) + void + win_free_all() + { + int dummy; + + while (firstwin != NULL) + (void)win_free_mem(firstwin, &dummy); + } + #endif + + /* + * Remove a window and its frame from the tree of frames. + * Returns a pointer to the window that got the freed up space. + */ + /*ARGSUSED*/ + static win_T * + winframe_remove(win, dirp) + win_T *win; + int *dirp; /* set to 'v' or 'h' for direction if 'ea' */ + { + frame_T *frp, *frp2, *frp3; + frame_T *frp_close = win->w_frame; + win_T *wp; + int old_height = 0; + + /* + * Remove the window from its frame. + */ + frp2 = win_altframe(win); + if (frp2 == NULL) + return NULL; /* deleted the last frame */ + + wp = frame2win(frp2); + + /* Remove this frame from the list of frames. */ + frame_remove(frp_close); + + #ifdef FEAT_VERTSPLIT + if (frp_close->fr_parent->fr_layout == FR_COL) + { + #endif + /* When 'winfixheight' is set, remember its old size and restore + * it later (it's a simplistic solution...). Don't do this if the + * window will occupy the full height of the screen. */ + if (frp2->fr_win != NULL + && (frp2->fr_next != NULL || frp2->fr_prev != NULL) + && frp2->fr_win->w_p_wfh) + old_height = frp2->fr_win->w_height; + frame_new_height(frp2, frp2->fr_height + frp_close->fr_height, + frp2 == frp_close->fr_next ? TRUE : FALSE, FALSE); + if (old_height != 0) + win_setheight_win(old_height, frp2->fr_win); + #ifdef FEAT_VERTSPLIT + *dirp = 'v'; + } + else + { + frame_new_width(frp2, frp2->fr_width + frp_close->fr_width, + frp2 == frp_close->fr_next ? TRUE : FALSE); + *dirp = 'h'; + } + #endif + + /* If rows/columns go to a window below/right its positions need to be + * updated. Can only be done after the sizes have been updated. */ + if (frp2 == frp_close->fr_next) + { + int row = win->w_winrow; + int col = W_WINCOL(win); + + frame_comp_pos(frp2, &row, &col); + } + + if (frp2->fr_next == NULL && frp2->fr_prev == NULL) + { + /* There is no other frame in this list, move its info to the parent + * and remove it. */ + frp2->fr_parent->fr_layout = frp2->fr_layout; + frp2->fr_parent->fr_child = frp2->fr_child; + for (frp = frp2->fr_child; frp != NULL; frp = frp->fr_next) + frp->fr_parent = frp2->fr_parent; + frp2->fr_parent->fr_win = frp2->fr_win; + if (frp2->fr_win != NULL) + frp2->fr_win->w_frame = frp2->fr_parent; + frp = frp2->fr_parent; + vim_free(frp2); + + frp2 = frp->fr_parent; + if (frp2 != NULL && frp2->fr_layout == frp->fr_layout) + { + /* The frame above the parent has the same layout, have to merge + * the frames into this list. */ + if (frp2->fr_child == frp) + frp2->fr_child = frp->fr_child; + frp->fr_child->fr_prev = frp->fr_prev; + if (frp->fr_prev != NULL) + frp->fr_prev->fr_next = frp->fr_child; + for (frp3 = frp->fr_child; ; frp3 = frp3->fr_next) + { + frp3->fr_parent = frp2; + if (frp3->fr_next == NULL) + { + frp3->fr_next = frp->fr_next; + if (frp->fr_next != NULL) + frp->fr_next->fr_prev = frp3; + break; + } + } + vim_free(frp); + } + } + + return wp; + } + + /* + * Find out which frame is going to get the freed up space when "win" is + * closed. + * if 'splitbelow'/'splitleft' the space goes to the window above/left. + * if 'nosplitbelow'/'nosplitleft' the space goes to the window below/right. + * This makes opening a window and closing it immediately keep the same window + * layout. + */ + static frame_T * + win_altframe(win) + win_T *win; + { + frame_T *frp; + int b; + ++ if (firstwin == lastwin) ++ /* Last window in this tab page, will go to next tab page. */ ++ return alt_tabpage()->tp_curwin->w_frame; ++ + frp = win->w_frame; + #ifdef FEAT_VERTSPLIT + if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW) + b = p_spr; + else + #endif + b = p_sb; + if ((!b && frp->fr_next != NULL) || frp->fr_prev == NULL) + return frp->fr_next; + return frp->fr_prev; + } + + /* ++ * Return the tabpage that will be used if the current one is closed. ++ */ ++ static tabpage_T * ++alt_tabpage() ++{ ++ tabpage_T *tp = current_tabpage(); ++ ++ if (tp != NULL) ++ { ++ /* Use the next tab page if it exists. */ ++ if (tp->tp_next != NULL) ++ return tp->tp_next; ++ ++ /* Find the previous tab page. */ ++ for (tp = first_tabpage; tp->tp_next != NULL; tp = tp->tp_next) ++ if (tp->tp_next == current_tabpage()) ++ return tp; ++ } ++ return first_tabpage; ++} ++ ++/* + * Find the left-upper window in frame "frp". + */ + static win_T * + frame2win(frp) + frame_T *frp; + { + while (frp->fr_win == NULL) + frp = frp->fr_child; + return frp->fr_win; + } + + /* + * Return TRUE if frame "frp" contains window "wp". + */ + static int + frame_has_win(frp, wp) + frame_T *frp; + win_T *wp; + { + frame_T *p; + + if (frp->fr_layout == FR_LEAF) + return frp->fr_win == wp; + + for (p = frp->fr_child; p != NULL; p = p->fr_next) + if (frame_has_win(p, wp)) + return TRUE; + return FALSE; + } + + /* + * Set a new height for a frame. Recursively sets the height for contained + * frames and windows. Caller must take care of positions. + */ + static void + frame_new_height(topfrp, height, topfirst, wfh) + frame_T *topfrp; + int height; + int topfirst; /* resize topmost contained frame first */ + int wfh; /* obey 'winfixheight' when there is a choice; + may cause the height not to be set */ + { + frame_T *frp; + int extra_lines; + int h; + + if (topfrp->fr_win != NULL) + { + /* Simple case: just one window. */ + win_new_height(topfrp->fr_win, + height - topfrp->fr_win->w_status_height); + } + #ifdef FEAT_VERTSPLIT + else if (topfrp->fr_layout == FR_ROW) + { + do + { + /* All frames in this row get the same new height. */ + for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) + { + frame_new_height(frp, height, topfirst, wfh); + if (frp->fr_height > height) + { + /* Could not fit the windows, make the whole row higher. */ + height = frp->fr_height; + break; + } + } + } + while (frp != NULL); + } + #endif + else + { + /* Complicated case: Resize a column of frames. Resize the bottom + * frame first, frames above that when needed. */ + + frp = topfrp->fr_child; + if (wfh) + /* Advance past frames with one window with 'wfh' set. */ + while (frame_fixed_height(frp)) + { + frp = frp->fr_next; + if (frp == NULL) + return; /* no frame without 'wfh', give up */ + } + if (!topfirst) + { + /* Find the bottom frame of this column */ + while (frp->fr_next != NULL) + frp = frp->fr_next; + if (wfh) + /* Advance back for frames with one window with 'wfh' set. */ + while (frame_fixed_height(frp)) + frp = frp->fr_prev; + } + + extra_lines = height - topfrp->fr_height; + if (extra_lines < 0) + { + /* reduce height of contained frames, bottom or top frame first */ + while (frp != NULL) + { + h = frame_minheight(frp, NULL); + if (frp->fr_height + extra_lines < h) + { + extra_lines += frp->fr_height - h; + frame_new_height(frp, h, topfirst, wfh); + } + else + { + frame_new_height(frp, frp->fr_height + extra_lines, + topfirst, wfh); + break; + } + if (topfirst) + { + do + frp = frp->fr_next; + while (wfh && frp != NULL && frame_fixed_height(frp)); + } + else + { + do + frp = frp->fr_prev; + while (wfh && frp != NULL && frame_fixed_height(frp)); + } + /* Increase "height" if we could not reduce enough frames. */ + if (frp == NULL) + height -= extra_lines; + } + } + else if (extra_lines > 0) + { + /* increase height of bottom or top frame */ + frame_new_height(frp, frp->fr_height + extra_lines, topfirst, wfh); + } + } + topfrp->fr_height = height; + } + + /* + * Return TRUE if height of frame "frp" should not be changed because of + * the 'winfixheight' option. + */ + static int + frame_fixed_height(frp) + frame_T *frp; + { + /* frame with one window: fixed height if 'winfixheight' set. */ + if (frp->fr_win != NULL) + return frp->fr_win->w_p_wfh; + + if (frp->fr_layout == FR_ROW) + { + /* The frame is fixed height if one of the frames in the row is fixed + * height. */ + for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) + if (frame_fixed_height(frp)) + return TRUE; + return FALSE; + } + + /* frp->fr_layout == FR_COL: The frame is fixed height if all of the + * frames in the row are fixed height. */ + for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) + if (!frame_fixed_height(frp)) + return FALSE; + return TRUE; + } + + #ifdef FEAT_VERTSPLIT + /* + * Add a status line to windows at the bottom of "frp". + * Note: Does not check if there is room! + */ + static void + frame_add_statusline(frp) + frame_T *frp; + { + win_T *wp; + + if (frp->fr_layout == FR_LEAF) + { + wp = frp->fr_win; + if (wp->w_status_height == 0) + { + if (wp->w_height > 0) /* don't make it negative */ + --wp->w_height; + wp->w_status_height = STATUS_HEIGHT; + } + } + else if (frp->fr_layout == FR_ROW) + { + /* Handle all the frames in the row. */ + for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) + frame_add_statusline(frp); + } + else /* frp->fr_layout == FR_COL */ + { + /* Only need to handle the last frame in the column. */ + for (frp = frp->fr_child; frp->fr_next != NULL; frp = frp->fr_next) + ; + frame_add_statusline(frp); + } + } + + /* + * Set width of a frame. Handles recursively going through contained frames. + * May remove separator line for windows at the right side (for win_close()). + */ + static void + frame_new_width(topfrp, width, leftfirst) + frame_T *topfrp; + int width; + int leftfirst; /* resize leftmost contained frame first */ + { + frame_T *frp; + int extra_cols; + int w; + win_T *wp; + + if (topfrp->fr_layout == FR_LEAF) + { + /* Simple case: just one window. */ + wp = topfrp->fr_win; + /* Find out if there are any windows right of this one. */ + for (frp = topfrp; frp->fr_parent != NULL; frp = frp->fr_parent) + if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_next != NULL) + break; + if (frp->fr_parent == NULL) + wp->w_vsep_width = 0; + win_new_width(wp, width - wp->w_vsep_width); + } + else if (topfrp->fr_layout == FR_COL) + { + /* All frames in this column get the same new width. */ + for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) + frame_new_width(frp, width, leftfirst); + } + else /* fr_layout == FR_ROW */ + { + /* Complicated case: Resize a row of frames. Resize the rightmost + * frame first, frames left of it when needed. */ + + /* Find the rightmost frame of this row */ + frp = topfrp->fr_child; + if (!leftfirst) + while (frp->fr_next != NULL) + frp = frp->fr_next; + + extra_cols = width - topfrp->fr_width; + if (extra_cols < 0) + { + /* reduce frame width, rightmost frame first */ + while (frp != NULL) + { + w = frame_minwidth(frp, NULL); + if (frp->fr_width + extra_cols < w) + { + extra_cols += frp->fr_width - w; + frame_new_width(frp, w, leftfirst); + } + else + { + frame_new_width(frp, frp->fr_width + extra_cols, leftfirst); + break; + } + if (leftfirst) + frp = frp->fr_next; + else + frp = frp->fr_prev; + } + } + else if (extra_cols > 0) + { + /* increase width of rightmost frame */ + frame_new_width(frp, frp->fr_width + extra_cols, leftfirst); + } + } + topfrp->fr_width = width; + } + + /* + * Add the vertical separator to windows at the right side of "frp". + * Note: Does not check if there is room! + */ + static void + frame_add_vsep(frp) + frame_T *frp; + { + win_T *wp; + + if (frp->fr_layout == FR_LEAF) + { + wp = frp->fr_win; + if (wp->w_vsep_width == 0) + { + if (wp->w_width > 0) /* don't make it negative */ + --wp->w_width; + wp->w_vsep_width = 1; + } + } + else if (frp->fr_layout == FR_COL) + { + /* Handle all the frames in the column. */ + for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) + frame_add_vsep(frp); + } + else /* frp->fr_layout == FR_ROW */ + { + /* Only need to handle the last frame in the row. */ + frp = frp->fr_child; + while (frp->fr_next != NULL) + frp = frp->fr_next; + frame_add_vsep(frp); + } + } + + /* + * Set frame width from the window it contains. + */ + static void + frame_fix_width(wp) + win_T *wp; + { + wp->w_frame->fr_width = wp->w_width + wp->w_vsep_width; + } + #endif + + /* + * Set frame height from the window it contains. + */ + static void + frame_fix_height(wp) + win_T *wp; + { + wp->w_frame->fr_height = wp->w_height + wp->w_status_height; + } + + /* + * Compute the minimal height for frame "topfrp". + * Uses the 'winminheight' option. + * When "next_curwin" isn't NULL, use p_wh for this window. + * When "next_curwin" is NOWIN, don't use at least one line for the current + * window. + */ + static int + frame_minheight(topfrp, next_curwin) + frame_T *topfrp; + win_T *next_curwin; + { + frame_T *frp; + int m; + #ifdef FEAT_VERTSPLIT + int n; + #endif + + if (topfrp->fr_win != NULL) + { + if (topfrp->fr_win == next_curwin) + m = p_wh + topfrp->fr_win->w_status_height; + else + { + /* window: minimal height of the window plus status line */ + m = p_wmh + topfrp->fr_win->w_status_height; + /* Current window is minimal one line high */ + if (p_wmh == 0 && topfrp->fr_win == curwin && next_curwin == NULL) + ++m; + } + } + #ifdef FEAT_VERTSPLIT + else if (topfrp->fr_layout == FR_ROW) + { + /* get the minimal height from each frame in this row */ + m = 0; + for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) + { + n = frame_minheight(frp, next_curwin); + if (n > m) + m = n; + } + } + #endif + else + { + /* Add up the minimal heights for all frames in this column. */ + m = 0; + for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) + m += frame_minheight(frp, next_curwin); + } + + return m; + } + + #ifdef FEAT_VERTSPLIT + /* + * Compute the minimal width for frame "topfrp". + * When "next_curwin" isn't NULL, use p_wiw for this window. + * When "next_curwin" is NOWIN, don't use at least one column for the current + * window. + */ + static int + frame_minwidth(topfrp, next_curwin) + frame_T *topfrp; + win_T *next_curwin; /* use p_wh and p_wiw for next_curwin */ + { + frame_T *frp; + int m, n; + + if (topfrp->fr_win != NULL) + { + if (topfrp->fr_win == next_curwin) + m = p_wiw + topfrp->fr_win->w_vsep_width; + else + { + /* window: minimal width of the window plus separator column */ + m = p_wmw + topfrp->fr_win->w_vsep_width; + /* Current window is minimal one column wide */ + if (p_wmw == 0 && topfrp->fr_win == curwin && next_curwin == NULL) + ++m; + } + } + else if (topfrp->fr_layout == FR_COL) + { + /* get the minimal width from each frame in this column */ + m = 0; + for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) + { + n = frame_minwidth(frp, next_curwin); + if (n > m) + m = n; + } + } + else + { + /* Add up the minimal widths for all frames in this row. */ + m = 0; + for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) + m += frame_minwidth(frp, next_curwin); + } + + return m; + } + #endif + + + /* + * Try to close all windows except current one. + * Buffers in the other windows become hidden if 'hidden' is set, or '!' is + * used and the buffer was modified. + * + * Used by ":bdel" and ":only". + */ + void + close_others(message, forceit) + int message; + int forceit; /* always hide all other windows */ + { + win_T *wp; + win_T *nextwp; + int r; + + if (lastwin == firstwin) + { + if (message + #ifdef FEAT_AUTOCMD + && !autocmd_busy + #endif + ) + MSG(_("Already only one window")); + return; + } + + /* Be very careful here: autocommands may change the window layout. */ + for (wp = firstwin; win_valid(wp); wp = nextwp) + { + nextwp = wp->w_next; + if (wp != curwin) /* don't close current window */ + { + + /* Check if it's allowed to abandon this window */ + r = can_abandon(wp->w_buffer, forceit); + #ifdef FEAT_AUTOCMD + if (!win_valid(wp)) /* autocommands messed wp up */ + { + nextwp = firstwin; + continue; + } + #endif + if (!r) + { + #if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + if (message && (p_confirm || cmdmod.confirm) && p_write) + { + dialog_changed(wp->w_buffer, FALSE); + # ifdef FEAT_AUTOCMD + if (!win_valid(wp)) /* autocommands messed wp up */ + { + nextwp = firstwin; + continue; + } + # endif + } + if (bufIsChanged(wp->w_buffer)) + #endif + continue; + } + win_close(wp, !P_HID(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); + } + } + +- /* +- * If current window has a status line and we don't want one, +- * remove the status line. +- */ +- if (lastwin != firstwin) ++ if (message && lastwin != firstwin) + EMSG(_("E445: Other window contains changes")); + } + + #endif /* FEAT_WINDOWS */ + + /* + * init the cursor in the window + * + * called when a new file is being edited + */ + void + win_init(wp) + win_T *wp; + { + redraw_win_later(wp, NOT_VALID); + wp->w_lines_valid = 0; + wp->w_cursor.lnum = 1; + wp->w_curswant = wp->w_cursor.col = 0; + #ifdef FEAT_VIRTUALEDIT + wp->w_cursor.coladd = 0; + #endif + wp->w_pcmark.lnum = 1; /* pcmark not cleared but set to line 1 */ + wp->w_pcmark.col = 0; + wp->w_prev_pcmark.lnum = 0; + wp->w_prev_pcmark.col = 0; + wp->w_topline = 1; + #ifdef FEAT_DIFF + wp->w_topfill = 0; + #endif + wp->w_botline = 2; + #ifdef FEAT_FKMAP + if (curwin->w_p_rl) + wp->w_farsi = W_CONV + W_R_L; + else + wp->w_farsi = W_CONV; + #endif + } + + /* + * Allocate the first window and put an empty buffer in it. + * Called from main(). +- * When this fails we can't do anything: exit. ++ * Return FAIL when something goes wrong (out of memory). + */ +- void ++ int + win_alloc_first() + { ++ if (win_alloc_firstwin() == FAIL) ++ return FAIL; ++ ++#ifdef FEAT_WINDOWS ++ first_tabpage = (tabpage_T *)alloc((unsigned)sizeof(tabpage_T)); ++ if (first_tabpage == NULL) ++ return FAIL; ++ first_tabpage->tp_topframe = topframe; ++ first_tabpage->tp_next = NULL; ++#endif ++ return OK; ++} ++ ++/* ++ * Allocate one window and put an empty buffer in it. ++ * Called to create the first window in a new tab page. ++ * Return FAIL when something goes wrong (out of memory). ++ */ ++ static int ++win_alloc_firstwin() ++{ + curwin = win_alloc(NULL); + curbuf = buflist_new(NULL, NULL, 1L, BLN_LISTED); + if (curwin == NULL || curbuf == NULL) +- mch_exit(0); ++ return FAIL; + curwin->w_buffer = curbuf; + curbuf->b_nwindows = 1; /* there is one window */ + #ifdef FEAT_WINDOWS + curwin->w_alist = &global_alist; + #endif + win_init(curwin); /* init current window */ + + topframe = (frame_T *)alloc_clear((unsigned)sizeof(frame_T)); + if (topframe == NULL) +- mch_exit(0); ++ return FAIL; + topframe->fr_layout = FR_LEAF; + #ifdef FEAT_VERTSPLIT + topframe->fr_width = Columns; + #endif + topframe->fr_height = Rows - p_ch; + #ifdef FEAT_WINDOWS + p_ch_used = p_ch; + #endif + topframe->fr_win = curwin; + curwin->w_frame = topframe; ++ ++ return OK; ++} ++ ++/* ++ * Initialize the window and frame size to the maximum. ++ */ ++ void ++win_init_size() ++{ ++ firstwin->w_height = ROWS_AVAIL; ++ topframe->fr_height = ROWS_AVAIL; ++#ifdef FEAT_VERTSPLIT ++ firstwin->w_width = Columns; ++ topframe->fr_width = Columns; ++#endif + } + + #if defined(FEAT_WINDOWS) || defined(PROTO) ++/* ++ * Create a new Tab page with one empty window. ++ * Put it just after the current Tab page. ++ * Return FAIL or OK. ++ */ ++ int ++win_new_tabpage() ++{ ++ tabpage_T *tp; ++ tabpage_T *newtp; ++ ++ newtp = (tabpage_T *)alloc((unsigned)sizeof(tabpage_T)); ++ if (newtp == NULL) ++ return FAIL; ++ ++ tp = current_tabpage(); ++ ++ /* Remember the current windows in this Tab page. */ ++ leave_tabpage(tp); ++ ++ /* Create a new empty window. */ ++ if (win_alloc_firstwin() == OK) ++ { ++ /* copy options from previous to new curwin */ ++ win_copy_options(tp->tp_curwin, curwin); ++ ++ /* Make the new Tab page the new topframe. */ ++ newtp->tp_next = tp->tp_next; ++ tp->tp_next = newtp; ++ win_init_size(); ++ firstwin->w_winrow = tabpageline_height(); ++ ++ newtp->tp_topframe = topframe; ++ redraw_all_later(CLEAR); ++ return OK; ++ } ++ ++ /* Failed, get back the previous Tab page */ ++ topframe = tp->tp_topframe; ++ curwin = tp->tp_curwin; ++ firstwin = tp->tp_firstwin; ++ lastwin = tp->tp_lastwin; ++ return FAIL; ++} ++ ++/* ++ * Return a pointer to the current tab page. ++ */ ++ static tabpage_T * ++current_tabpage() ++{ ++ tabpage_T *tp; ++ ++ for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) ++ if (tp->tp_topframe == topframe) ++ break; ++ if (tp == NULL) ++ EMSG2(_(e_intern2), "current_tabpage()"); ++ return tp; ++} ++ ++/* ++ * Prepare for leaving the current tab page "tp". ++ */ ++ static void ++leave_tabpage(tp) ++ tabpage_T *tp; ++{ ++ tp->tp_curwin = curwin; ++ tp->tp_firstwin = firstwin; ++ tp->tp_lastwin = lastwin; ++ firstwin = NULL; ++ lastwin = NULL; ++} ++ ++/* ++ * Start using tab page "tp". ++ */ ++/*ARGSUSED*/ ++ static void ++enter_tabpage(tp, old_curbuf) ++ tabpage_T *tp; ++ buf_T *old_curbuf; ++{ ++ firstwin = tp->tp_firstwin; ++ lastwin = tp->tp_lastwin; ++ topframe = tp->tp_topframe; ++ win_enter_ext(tp->tp_curwin, FALSE, TRUE); ++ ++#ifdef FEAT_AUTOCMD ++ if (old_curbuf != curbuf) ++ apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); ++#endif ++ ++ /* status line may appear or disappear */ ++ last_status(FALSE); ++ ++#if defined(FEAT_GUI) && defined(FEAT_VERTSPLIT) ++ /* When 'guioptions' includes 'L' or 'R' may have to add or remove ++ * scrollbars. */ ++ if (gui.in_use && !win_hasvertsplit()) ++ gui_init_which_components(NULL); ++#endif ++ ++ redraw_all_later(CLEAR); ++} ++ ++/* ++ * Go to tab page "n". For ":tab N" and "Ngt". ++ */ ++ void ++goto_tabpage(n) ++ int n; ++{ ++ tabpage_T *otp = current_tabpage(); ++ tabpage_T *tp; ++ int i; ++ ++ if (otp == NULL) ++ return; ++ ++ if (n == 0) ++ { ++ /* No count, go to next tab page, wrap around end. */ ++ if (otp->tp_next == NULL) ++ tp = first_tabpage; ++ else ++ tp = otp->tp_next; ++ } ++ else ++ { ++ /* Go to tab page "n". */ ++ i = 0; ++ for (tp = first_tabpage; ++i != n; tp = tp->tp_next) ++ if (tp == NULL) ++ { ++ beep_flush(); ++ return; ++ } ++ } ++ ++ leave_tabpage(otp); ++ enter_tabpage(tp, curbuf); ++} + + /* + * Go to another window. + * When jumping to another buffer, stop Visual mode. Do this before + * changing windows so we can yank the selection into the '*' register. + * When jumping to another window on the same buffer, adjust its cursor + * position to keep the same Visual area. + */ + void + win_goto(wp) + win_T *wp; + { + if (text_locked()) + { + beep_flush(); + text_locked_msg(); + return; + } + + #ifdef FEAT_VISUAL + if (wp->w_buffer != curbuf) + reset_VIsual_and_resel(); + else if (VIsual_active) + wp->w_cursor = curwin->w_cursor; + #endif + + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_enter(wp, TRUE); + } + + #if defined(FEAT_PERL) || defined(PROTO) + /* + * Find window number "winnr" (counting top to bottom). + */ + win_T * + win_find_nr(winnr) + int winnr; + { + win_T *wp; + + # ifdef FEAT_WINDOWS + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (--winnr == 0) + break; + return wp; + # else + return curwin; + # endif + } + #endif + + #ifdef FEAT_VERTSPLIT + /* + * Move to window above or below "count" times. + */ + static void + win_goto_ver(up, count) + int up; /* TRUE to go to win above */ + long count; + { + frame_T *fr; + frame_T *nfr; + frame_T *foundfr; + + foundfr = curwin->w_frame; + while (count--) + { + /* + * First go upwards in the tree of frames until we find a upwards or + * downwards neighbor. + */ + fr = foundfr; + for (;;) + { + if (fr == topframe) + goto end; + if (up) + nfr = fr->fr_prev; + else + nfr = fr->fr_next; + if (fr->fr_parent->fr_layout == FR_COL && nfr != NULL) + break; + fr = fr->fr_parent; + } + + /* + * Now go downwards to find the bottom or top frame in it. + */ + for (;;) + { + if (nfr->fr_layout == FR_LEAF) + { + foundfr = nfr; + break; + } + fr = nfr->fr_child; + if (nfr->fr_layout == FR_ROW) + { + /* Find the frame at the cursor row. */ + while (fr->fr_next != NULL + && frame2win(fr)->w_wincol + fr->fr_width + <= curwin->w_wincol + curwin->w_wcol) + fr = fr->fr_next; + } + if (nfr->fr_layout == FR_COL && up) + while (fr->fr_next != NULL) + fr = fr->fr_next; + nfr = fr; + } + } + end: + if (foundfr != NULL) + win_goto(foundfr->fr_win); + } + + /* + * Move to left or right window. + */ + static void + win_goto_hor(left, count) + int left; /* TRUE to go to left win */ + long count; + { + frame_T *fr; + frame_T *nfr; + frame_T *foundfr; + + foundfr = curwin->w_frame; + while (count--) + { + /* + * First go upwards in the tree of frames until we find a left or + * right neighbor. + */ + fr = foundfr; + for (;;) + { + if (fr == topframe) + goto end; + if (left) + nfr = fr->fr_prev; + else + nfr = fr->fr_next; + if (fr->fr_parent->fr_layout == FR_ROW && nfr != NULL) + break; + fr = fr->fr_parent; + } + + /* + * Now go downwards to find the leftmost or rightmost frame in it. + */ + for (;;) + { + if (nfr->fr_layout == FR_LEAF) + { + foundfr = nfr; + break; + } + fr = nfr->fr_child; + if (nfr->fr_layout == FR_COL) + { + /* Find the frame at the cursor row. */ + while (fr->fr_next != NULL + && frame2win(fr)->w_winrow + fr->fr_height + <= curwin->w_winrow + curwin->w_wrow) + fr = fr->fr_next; + } + if (nfr->fr_layout == FR_ROW && left) + while (fr->fr_next != NULL) + fr = fr->fr_next; + nfr = fr; + } + } + end: + if (foundfr != NULL) + win_goto(foundfr->fr_win); + } + #endif + + /* + * Make window "wp" the current window. + */ + void + win_enter(wp, undo_sync) + win_T *wp; + int undo_sync; + { + win_enter_ext(wp, undo_sync, FALSE); + } + + /* + * Make window wp the current window. + * Can be called with "curwin_invalid" TRUE, which means that curwin has just + * been closed and isn't valid. + */ + static void + win_enter_ext(wp, undo_sync, curwin_invalid) + win_T *wp; + int undo_sync; + int curwin_invalid; + { + #ifdef FEAT_AUTOCMD + int other_buffer = FALSE; + #endif + + if (wp == curwin && !curwin_invalid) /* nothing to do */ + return; + + #ifdef FEAT_AUTOCMD + if (!curwin_invalid) + { + /* + * Be careful: If autocommands delete the window, return now. + */ + if (wp->w_buffer != curbuf) + { + apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf); + other_buffer = TRUE; + if (!win_valid(wp)) + return; + } + apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE, curbuf); + if (!win_valid(wp)) + return; + # ifdef FEAT_EVAL + /* autocmds may abort script processing */ + if (aborting()) + return; + # endif + } + #endif + + /* sync undo before leaving the current buffer */ + if (undo_sync && curbuf != wp->w_buffer) + u_sync(); + /* may have to copy the buffer options when 'cpo' contains 'S' */ + if (wp->w_buffer != curbuf) + buf_copy_options(wp->w_buffer, BCO_ENTER | BCO_NOHELP); + if (!curwin_invalid) + { + prevwin = curwin; /* remember for CTRL-W p */ + curwin->w_redr_status = TRUE; + } + curwin = wp; + curbuf = wp->w_buffer; + check_cursor(); + #ifdef FEAT_VIRTUALEDIT + if (!virtual_active()) + curwin->w_cursor.coladd = 0; + #endif + changed_line_abv_curs(); /* assume cursor position needs updating */ + + if (curwin->w_localdir != NULL) + { + /* Window has a local directory: Save current directory as global + * directory (unless that was done already) and change to the local + * directory. */ + if (globaldir == NULL) + { + char_u cwd[MAXPATHL]; + + if (mch_dirname(cwd, MAXPATHL) == OK) + globaldir = vim_strsave(cwd); + } + mch_chdir((char *)curwin->w_localdir); + shorten_fnames(TRUE); + } + else if (globaldir != NULL) + { + /* Window doesn't have a local directory and we are not in the global + * directory: Change to the global directory. */ + mch_chdir((char *)globaldir); + vim_free(globaldir); + globaldir = NULL; + shorten_fnames(TRUE); + } + + #ifdef FEAT_AUTOCMD + apply_autocmds(EVENT_WINENTER, NULL, NULL, FALSE, curbuf); + if (other_buffer) + apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); + #endif + + #ifdef FEAT_TITLE + maketitle(); + #endif + curwin->w_redr_status = TRUE; ++ redraw_tabpage = TRUE; + if (restart_edit) + redraw_later(VALID); /* causes status line redraw */ + + /* set window height to desired minimal value */ + if (curwin->w_height < p_wh && !curwin->w_p_wfh) + win_setheight((int)p_wh); + else if (curwin->w_height == 0) + win_setheight(1); + + #ifdef FEAT_VERTSPLIT + /* set window width to desired minimal value */ + if (curwin->w_width < p_wiw) + win_setwidth((int)p_wiw); + #endif + + #ifdef FEAT_MOUSE + setmouse(); /* in case jumped to/from help buffer */ + #endif + + #if defined(FEAT_NETBEANS_INTG) || defined(FEAT_SUN_WORKSHOP) + /* Change directories when the acd option is set on and after + * switching windows. */ + if (p_acd && curbuf->b_ffname != NULL + && vim_chdirfile(curbuf->b_ffname) == OK) + shorten_fnames(TRUE); + #endif + } + + #endif /* FEAT_WINDOWS */ + + #if defined(FEAT_WINDOWS) || defined(FEAT_SIGNS) || defined(PROTO) + /* + * Jump to the first open window that contains buffer buf if one exists + * TODO: Alternatively jump to last open window? Dependent from 'splitbelow'? + * Returns pointer to window if it exists, otherwise NULL. + */ + win_T * + buf_jump_open_win(buf) + buf_T *buf; + { + # ifdef FEAT_WINDOWS + win_T *wp; + + for (wp = firstwin; wp; wp = wp->w_next) + if (wp->w_buffer == buf) + break; + if (wp != NULL) + win_enter(wp, FALSE); + return wp; + # else + if (curwin->w_buffer == buf) + return curwin; + return NULL; + # endif + } + #endif + + /* + * allocate a window structure and link it in the window list + */ + /*ARGSUSED*/ + static win_T * + win_alloc(after) + win_T *after; + { + win_T *newwin; + + /* + * allocate window structure and linesizes arrays + */ + newwin = (win_T *)alloc_clear((unsigned)sizeof(win_T)); + if (newwin != NULL && win_alloc_lines(newwin) == FAIL) + { + vim_free(newwin); + newwin = NULL; + } + + if (newwin != NULL) + { + /* + * link the window in the window list + */ + #ifdef FEAT_WINDOWS + win_append(after, newwin); + #endif + #ifdef FEAT_VERTSPLIT + newwin->w_wincol = 0; + newwin->w_width = Columns; + #endif + + /* position the display and the cursor at the top of the file. */ + newwin->w_topline = 1; + #ifdef FEAT_DIFF + newwin->w_topfill = 0; + #endif + newwin->w_botline = 2; + newwin->w_cursor.lnum = 1; + #ifdef FEAT_SCROLLBIND + newwin->w_scbind_pos = 1; + #endif + + /* We won't calculate w_fraction until resizing the window */ + newwin->w_fraction = 0; + newwin->w_prev_fraction_row = -1; + + #ifdef FEAT_GUI + if (gui.in_use) + { + out_flush(); + gui_create_scrollbar(&newwin->w_scrollbars[SBAR_LEFT], + SBAR_LEFT, newwin); + gui_create_scrollbar(&newwin->w_scrollbars[SBAR_RIGHT], + SBAR_RIGHT, newwin); + } + #endif + #ifdef FEAT_EVAL + /* init w: variables */ + init_var_dict(&newwin->w_vars, &newwin->w_winvar); + #endif + #ifdef FEAT_FOLDING + foldInitWin(newwin); + #endif + } + return newwin; + } + + #if defined(FEAT_WINDOWS) || defined(PROTO) + + /* + * remove window 'wp' from the window list and free the structure + */ + static void + win_free(wp) + win_T *wp; + { + int i; + + #ifdef FEAT_MZSCHEME + mzscheme_window_free(wp); + #endif + + #ifdef FEAT_PERL + perl_win_free(wp); + #endif + + #ifdef FEAT_PYTHON + python_window_free(wp); + #endif + + #ifdef FEAT_TCL + tcl_window_free(wp); + #endif + + #ifdef FEAT_RUBY + ruby_window_free(wp); + #endif + + clear_winopt(&wp->w_onebuf_opt); + clear_winopt(&wp->w_allbuf_opt); + + #ifdef FEAT_EVAL + vars_clear(&wp->w_vars.dv_hashtab); /* free all w: variables */ + #endif + + if (prevwin == wp) + prevwin = NULL; + win_free_lsize(wp); + + for (i = 0; i < wp->w_tagstacklen; ++i) + vim_free(wp->w_tagstack[i].tagname); + + vim_free(wp->w_localdir); + #ifdef FEAT_SEARCH_EXTRA + vim_free(wp->w_match.regprog); + #endif + #ifdef FEAT_JUMPLIST + free_jumplist(wp); + #endif + + #ifdef FEAT_QUICKFIX + qf_free_all(wp); + #endif + + #ifdef FEAT_GUI + if (gui.in_use) + { + out_flush(); + gui_mch_destroy_scrollbar(&wp->w_scrollbars[SBAR_LEFT]); + gui_mch_destroy_scrollbar(&wp->w_scrollbars[SBAR_RIGHT]); + } + #endif /* FEAT_GUI */ + + win_remove(wp); + vim_free(wp); + } + + /* + * Append window "wp" in the window list after window "after". + */ + static void + win_append(after, wp) + win_T *after, *wp; + { + win_T *before; + + if (after == NULL) /* after NULL is in front of the first */ + before = firstwin; + else + before = after->w_next; + + wp->w_next = before; + wp->w_prev = after; + if (after == NULL) + firstwin = wp; + else + after->w_next = wp; + if (before == NULL) + lastwin = wp; + else + before->w_prev = wp; + } + + /* + * Remove a window from the window list. + */ + static void + win_remove(wp) + win_T *wp; + { + if (wp->w_prev != NULL) + wp->w_prev->w_next = wp->w_next; + else + firstwin = wp->w_next; + if (wp->w_next != NULL) + wp->w_next->w_prev = wp->w_prev; + else + lastwin = wp->w_prev; + } + + /* + * Append frame "frp" in a frame list after frame "after". + */ + static void + frame_append(after, frp) + frame_T *after, *frp; + { + frp->fr_next = after->fr_next; + after->fr_next = frp; + if (frp->fr_next != NULL) + frp->fr_next->fr_prev = frp; + frp->fr_prev = after; + } + + /* + * Insert frame "frp" in a frame list before frame "before". + */ + static void + frame_insert(before, frp) + frame_T *before, *frp; + { + frp->fr_next = before; + frp->fr_prev = before->fr_prev; + before->fr_prev = frp; + if (frp->fr_prev != NULL) + frp->fr_prev->fr_next = frp; + else + frp->fr_parent->fr_child = frp; + } + + /* + * Remove a frame from a frame list. + */ + static void + frame_remove(frp) + frame_T *frp; + { + if (frp->fr_prev != NULL) + frp->fr_prev->fr_next = frp->fr_next; + else + frp->fr_parent->fr_child = frp->fr_next; + if (frp->fr_next != NULL) + frp->fr_next->fr_prev = frp->fr_prev; + } + + #endif /* FEAT_WINDOWS */ + + /* + * Allocate w_lines[] for window "wp". + * Return FAIL for failure, OK for success. + */ + int + win_alloc_lines(wp) + win_T *wp; + { + wp->w_lines_valid = 0; + wp->w_lines = (wline_T *)alloc((unsigned)(Rows * sizeof(wline_T))); + if (wp->w_lines == NULL) + return FAIL; + return OK; + } + + /* + * free lsize arrays for a window + */ + void + win_free_lsize(wp) + win_T *wp; + { + vim_free(wp->w_lines); + wp->w_lines = NULL; + } + + /* + * Called from win_new_shellsize() after Rows changed. + */ + void + shell_new_rows() + { +- int h = (int)(Rows - p_ch); ++ int h = (int)ROWS_AVAIL; + + if (firstwin == NULL) /* not initialized yet */ + return; + #ifdef FEAT_WINDOWS + if (h < frame_minheight(topframe, NULL)) + h = frame_minheight(topframe, NULL); + /* First try setting the heights of windows without 'winfixheight'. If + * that doesn't result in the right height, forget about that option. */ + frame_new_height(topframe, h, FALSE, TRUE); + if (topframe->fr_height != h) + frame_new_height(topframe, h, FALSE, FALSE); + + (void)win_comp_pos(); /* recompute w_winrow and w_wincol */ + #else + if (h < 1) + h = 1; + win_new_height(firstwin, h); + #endif + compute_cmdrow(); + #ifdef FEAT_WINDOWS + p_ch_used = p_ch; + #endif + + #if 0 + /* Disabled: don't want making the screen smaller make a window larger. */ + if (p_ea) + win_equal(curwin, FALSE, 'v'); + #endif + } + + #if defined(FEAT_VERTSPLIT) || defined(PROTO) + /* + * Called from win_new_shellsize() after Columns changed. + */ + void + shell_new_columns() + { + if (firstwin == NULL) /* not initialized yet */ + return; + frame_new_width(topframe, (int)Columns, FALSE); + (void)win_comp_pos(); /* recompute w_winrow and w_wincol */ + #if 0 + /* Disabled: don't want making the screen smaller make a window larger. */ + if (p_ea) + win_equal(curwin, FALSE, 'h'); + #endif + } + #endif + + #if defined(FEAT_CMDWIN) || defined(PROTO) + /* + * Save the size of all windows in "gap". + */ + void + win_size_save(gap) + garray_T *gap; + + { + win_T *wp; + + ga_init2(gap, (int)sizeof(int), 1); + if (ga_grow(gap, win_count() * 2) == OK) + for (wp = firstwin; wp != NULL; wp = wp->w_next) + { + ((int *)gap->ga_data)[gap->ga_len++] = + wp->w_width + wp->w_vsep_width; + ((int *)gap->ga_data)[gap->ga_len++] = wp->w_height; + } + } + + /* + * Restore window sizes, but only if the number of windows is still the same. + * Does not free the growarray. + */ + void + win_size_restore(gap) + garray_T *gap; + { + win_T *wp; + int i; + + if (win_count() * 2 == gap->ga_len) + { + i = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + { + frame_setwidth(wp->w_frame, ((int *)gap->ga_data)[i++]); + win_setheight_win(((int *)gap->ga_data)[i++], wp); + } + /* recompute the window positions */ + (void)win_comp_pos(); + } + } + #endif /* FEAT_CMDWIN */ + + #if defined(FEAT_WINDOWS) || defined(PROTO) + /* + * Update the position for all windows, using the width and height of the + * frames. + * Returns the row just after the last window. + */ + static int + win_comp_pos() + { +- int row = 0; ++ int row = tabpageline_height(); + int col = 0; + + frame_comp_pos(topframe, &row, &col); + return row; + } + + /* + * Update the position of the windows in frame "topfrp", using the width and + * height of the frames. + * "*row" and "*col" are the top-left position of the frame. They are updated + * to the bottom-right position plus one. + */ + static void + frame_comp_pos(topfrp, row, col) + frame_T *topfrp; + int *row; + int *col; + { + win_T *wp; + frame_T *frp; + #ifdef FEAT_VERTSPLIT + int startcol; + int startrow; + #endif + + wp = topfrp->fr_win; + if (wp != NULL) + { + if (wp->w_winrow != *row + #ifdef FEAT_VERTSPLIT + || wp->w_wincol != *col + #endif + ) + { + /* position changed, redraw */ + wp->w_winrow = *row; + #ifdef FEAT_VERTSPLIT + wp->w_wincol = *col; + #endif + redraw_win_later(wp, NOT_VALID); + wp->w_redr_status = TRUE; + } + *row += wp->w_height + wp->w_status_height; + #ifdef FEAT_VERTSPLIT + *col += wp->w_width + wp->w_vsep_width; + #endif + } + else + { + #ifdef FEAT_VERTSPLIT + startrow = *row; + startcol = *col; + #endif + for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) + { + #ifdef FEAT_VERTSPLIT + if (topfrp->fr_layout == FR_ROW) + *row = startrow; /* all frames are at the same row */ + else + *col = startcol; /* all frames are at the same col */ + #endif + frame_comp_pos(frp, row, col); + } + } + } + + #endif /* FEAT_WINDOWS */ + + /* + * Set current window height and take care of repositioning other windows to + * fit around it. + */ + void + win_setheight(height) + int height; + { + win_setheight_win(height, curwin); + } + + /* + * Set the window height of window "win" and take care of repositioning other + * windows to fit around it. + */ + void + win_setheight_win(height, win) + int height; + win_T *win; + { + int row; + + if (win == curwin) + { + /* Always keep current window at least one line high, even when + * 'winminheight' is zero. */ + #ifdef FEAT_WINDOWS + if (height < p_wmh) + height = p_wmh; + #endif + if (height == 0) + height = 1; + } + + #ifdef FEAT_WINDOWS + frame_setheight(win->w_frame, height + win->w_status_height); + + /* recompute the window positions */ + row = win_comp_pos(); + #else + if (height > topframe->fr_height) + height = topframe->fr_height; + win->w_height = height; + row = height; + #endif + + /* + * If there is extra space created between the last window and the command + * line, clear it. + */ + if (full_screen && msg_scrolled == 0 && row < cmdline_row) + screen_fill(row, cmdline_row, 0, (int)Columns, ' ', ' ', 0); + cmdline_row = row; + msg_row = row; + msg_col = 0; + + redraw_all_later(NOT_VALID); + } + + #if defined(FEAT_WINDOWS) || defined(PROTO) + + /* + * Set the height of a frame to "height" and take care that all frames and + * windows inside it are resized. Also resize frames on the left and right if + * the are in the same FR_ROW frame. + * + * Strategy: + * If the frame is part of a FR_COL frame, try fitting the frame in that + * frame. If that doesn't work (the FR_COL frame is too small), recursively + * go to containing frames to resize them and make room. + * If the frame is part of a FR_ROW frame, all frames must be resized as well. + * Check for the minimal height of the FR_ROW frame. + * At the top level we can also use change the command line height. + */ + static void + frame_setheight(curfrp, height) + frame_T *curfrp; + int height; + { + int room; /* total number of lines available */ + int take; /* number of lines taken from other windows */ + int room_cmdline; /* lines available from cmdline */ + int run; + frame_T *frp; + int h; + int room_reserved; + + /* If the height already is the desired value, nothing to do. */ + if (curfrp->fr_height == height) + return; + + if (curfrp->fr_parent == NULL) + { + /* topframe: can only change the command line */ +- if (height > Rows - p_ch) +- height = Rows - p_ch; ++ if (height > ROWS_AVAIL) ++ height = ROWS_AVAIL; + if (height > 0) + frame_new_height(curfrp, height, FALSE, FALSE); + } + else if (curfrp->fr_parent->fr_layout == FR_ROW) + { + /* Row of frames: Also need to resize frames left and right of this + * one. First check for the minimal height of these. */ + h = frame_minheight(curfrp->fr_parent, NULL); + if (height < h) + height = h; + frame_setheight(curfrp->fr_parent, height); + } + else + { + /* + * Column of frames: try to change only frames in this column. + */ + #ifdef FEAT_VERTSPLIT + /* + * Do this twice: + * 1: compute room available, if it's not enough try resizing the + * containing frame. + * 2: compute the room available and adjust the height to it. + * Try not to reduce the height of a window with 'winfixheight' set. + */ + for (run = 1; run <= 2; ++run) + #else + for (;;) + #endif + { + room = 0; + room_reserved = 0; + for (frp = curfrp->fr_parent->fr_child; frp != NULL; + frp = frp->fr_next) + { + if (frp != curfrp + && frp->fr_win != NULL + && frp->fr_win->w_p_wfh) + room_reserved += frp->fr_height; + room += frp->fr_height; + if (frp != curfrp) + room -= frame_minheight(frp, NULL); + } + #ifdef FEAT_VERTSPLIT + if (curfrp->fr_width != Columns) + room_cmdline = 0; + else + #endif + { + room_cmdline = Rows - p_ch - (lastwin->w_winrow + + lastwin->w_height + lastwin->w_status_height); + if (room_cmdline < 0) + room_cmdline = 0; + } + + if (height <= room + room_cmdline) + break; + #ifdef FEAT_VERTSPLIT + if (run == 2 || curfrp->fr_width == Columns) + #endif + { + if (height > room + room_cmdline) + height = room + room_cmdline; + break; + } + #ifdef FEAT_VERTSPLIT + frame_setheight(curfrp->fr_parent, height + + frame_minheight(curfrp->fr_parent, NOWIN) - (int)p_wmh - 1); + #endif + /*NOTREACHED*/ + } + + /* + * Compute the number of lines we will take from others frames (can be + * negative!). + */ + take = height - curfrp->fr_height; + + /* If there is not enough room, also reduce the height of a window + * with 'winfixheight' set. */ + if (height > room + room_cmdline - room_reserved) + room_reserved = room + room_cmdline - height; + /* If there is only a 'winfixheight' window and making the + * window smaller, need to make the other window taller. */ + if (take < 0 && room - curfrp->fr_height < room_reserved) + room_reserved = 0; + + if (take > 0 && room_cmdline > 0) + { + /* use lines from cmdline first */ + if (take < room_cmdline) + room_cmdline = take; + take -= room_cmdline; + topframe->fr_height += room_cmdline; + } + + /* + * set the current frame to the new height + */ + frame_new_height(curfrp, height, FALSE, FALSE); + + /* + * First take lines from the frames after the current frame. If + * that is not enough, takes lines from frames above the current + * frame. + */ + for (run = 0; run < 2; ++run) + { + if (run == 0) + frp = curfrp->fr_next; /* 1st run: start with next window */ + else + frp = curfrp->fr_prev; /* 2nd run: start with prev window */ + while (frp != NULL && take != 0) + { + h = frame_minheight(frp, NULL); + if (room_reserved > 0 + && frp->fr_win != NULL + && frp->fr_win->w_p_wfh) + { + if (room_reserved >= frp->fr_height) + room_reserved -= frp->fr_height; + else + { + if (frp->fr_height - room_reserved > take) + room_reserved = frp->fr_height - take; + take -= frp->fr_height - room_reserved; + frame_new_height(frp, room_reserved, FALSE, FALSE); + room_reserved = 0; + } + } + else + { + if (frp->fr_height - take < h) + { + take -= frp->fr_height - h; + frame_new_height(frp, h, FALSE, FALSE); + } + else + { + frame_new_height(frp, frp->fr_height - take, + FALSE, FALSE); + take = 0; + } + } + if (run == 0) + frp = frp->fr_next; + else + frp = frp->fr_prev; + } + } + } + } + + #if defined(FEAT_VERTSPLIT) || defined(PROTO) + /* + * Set current window width and take care of repositioning other windows to + * fit around it. + */ + void + win_setwidth(width) + int width; + { + win_setwidth_win(width, curwin); + } + + void + win_setwidth_win(width, wp) + int width; + win_T *wp; + { + /* Always keep current window at least one column wide, even when + * 'winminwidth' is zero. */ + if (wp == curwin) + { + if (width < p_wmw) + width = p_wmw; + if (width == 0) + width = 1; + } + + frame_setwidth(wp->w_frame, width + wp->w_vsep_width); + + /* recompute the window positions */ + (void)win_comp_pos(); + + redraw_all_later(NOT_VALID); + } + + /* + * Set the width of a frame to "width" and take care that all frames and + * windows inside it are resized. Also resize frames above and below if the + * are in the same FR_ROW frame. + * + * Strategy is similar to frame_setheight(). + */ + static void + frame_setwidth(curfrp, width) + frame_T *curfrp; + int width; + { + int room; /* total number of lines available */ + int take; /* number of lines taken from other windows */ + int run; + frame_T *frp; + int w; + + /* If the width already is the desired value, nothing to do. */ + if (curfrp->fr_width == width) + return; + + if (curfrp->fr_parent == NULL) + /* topframe: can't change width */ + return; + + if (curfrp->fr_parent->fr_layout == FR_COL) + { + /* Column of frames: Also need to resize frames above and below of + * this one. First check for the minimal width of these. */ + w = frame_minwidth(curfrp->fr_parent, NULL); + if (width < w) + width = w; + frame_setwidth(curfrp->fr_parent, width); + } + else + { + /* + * Row of frames: try to change only frames in this row. + * + * Do this twice: + * 1: compute room available, if it's not enough try resizing the + * containing frame. + * 2: compute the room available and adjust the width to it. + */ + for (run = 1; run <= 2; ++run) + { + room = 0; + for (frp = curfrp->fr_parent->fr_child; frp != NULL; + frp = frp->fr_next) + { + room += frp->fr_width; + if (frp != curfrp) + room -= frame_minwidth(frp, NULL); + } + + if (width <= room) + break; +- if (run == 2 || curfrp->fr_height >= Rows - p_ch) ++ if (run == 2 || curfrp->fr_height >= ROWS_AVAIL) + { + if (width > room) + width = room; + break; + } + frame_setwidth(curfrp->fr_parent, width + + frame_minwidth(curfrp->fr_parent, NOWIN) - (int)p_wmw - 1); + } + + + /* + * Compute the number of lines we will take from others frames (can be + * negative!). + */ + take = width - curfrp->fr_width; + + /* + * set the current frame to the new width + */ + frame_new_width(curfrp, width, FALSE); + + /* + * First take lines from the frames right of the current frame. If + * that is not enough, takes lines from frames left of the current + * frame. + */ + for (run = 0; run < 2; ++run) + { + if (run == 0) + frp = curfrp->fr_next; /* 1st run: start with next window */ + else + frp = curfrp->fr_prev; /* 2nd run: start with prev window */ + while (frp != NULL && take != 0) + { + w = frame_minwidth(frp, NULL); + if (frp->fr_width - take < w) + { + take -= frp->fr_width - w; + frame_new_width(frp, w, FALSE); + } + else + { + frame_new_width(frp, frp->fr_width - take, FALSE); + take = 0; + } + if (run == 0) + frp = frp->fr_next; + else + frp = frp->fr_prev; + } + } + } + } + #endif /* FEAT_VERTSPLIT */ + + /* + * Check 'winminheight' for a valid value. + */ + void + win_setminheight() + { + int room; + int first = TRUE; + win_T *wp; + + /* loop until there is a 'winminheight' that is possible */ + while (p_wmh > 0) + { + /* TODO: handle vertical splits */ + room = -p_wh; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + room += wp->w_height - p_wmh; + if (room >= 0) + break; + --p_wmh; + if (first) + { + EMSG(_(e_noroom)); + first = FALSE; + } + } + } + + #ifdef FEAT_MOUSE + + /* + * Status line of dragwin is dragged "offset" lines down (negative is up). + */ + void + win_drag_status_line(dragwin, offset) + win_T *dragwin; + int offset; + { + frame_T *curfr; + frame_T *fr; + int room; + int row; + int up; /* if TRUE, drag status line up, otherwise down */ + int n; + + fr = dragwin->w_frame; + curfr = fr; + if (fr != topframe) /* more than one window */ + { + fr = fr->fr_parent; + /* When the parent frame is not a column of frames, its parent should + * be. */ + if (fr->fr_layout != FR_COL) + { + curfr = fr; + if (fr != topframe) /* only a row of windows, may drag statusline */ + fr = fr->fr_parent; + } + } + + /* If this is the last frame in a column, may want to resize the parent + * frame instead (go two up to skip a row of frames). */ + while (curfr != topframe && curfr->fr_next == NULL) + { + if (fr != topframe) + fr = fr->fr_parent; + curfr = fr; + if (fr != topframe) + fr = fr->fr_parent; + } + + if (offset < 0) /* drag up */ + { + up = TRUE; + offset = -offset; + /* sum up the room of the current frame and above it */ + if (fr == curfr) + { + /* only one window */ + room = fr->fr_height - frame_minheight(fr, NULL); + } + else + { + room = 0; + for (fr = fr->fr_child; ; fr = fr->fr_next) + { + room += fr->fr_height - frame_minheight(fr, NULL); + if (fr == curfr) + break; + } + } + fr = curfr->fr_next; /* put fr at frame that grows */ + } + else /* drag down */ + { + up = FALSE; + /* + * Only dragging the last status line can reduce p_ch. + */ + room = Rows - cmdline_row; + if (curfr->fr_next == NULL) + room -= 1; + else + room -= p_ch; + if (room < 0) + room = 0; + /* sum up the room of frames below of the current one */ + for (fr = curfr->fr_next; fr != NULL; fr = fr->fr_next) + room += fr->fr_height - frame_minheight(fr, NULL); + fr = curfr; /* put fr at window that grows */ + } + + if (room < offset) /* Not enough room */ + offset = room; /* Move as far as we can */ + if (offset <= 0) + return; + + /* + * Grow frame fr by "offset" lines. + * Doesn't happen when dragging the last status line up. + */ + if (fr != NULL) + frame_new_height(fr, fr->fr_height + offset, up, FALSE); + + if (up) + fr = curfr; /* current frame gets smaller */ + else + fr = curfr->fr_next; /* next frame gets smaller */ + + /* + * Now make the other frames smaller. + */ + while (fr != NULL && offset > 0) + { + n = frame_minheight(fr, NULL); + if (fr->fr_height - offset <= n) + { + offset -= fr->fr_height - n; + frame_new_height(fr, n, !up, FALSE); + } + else + { + frame_new_height(fr, fr->fr_height - offset, !up, FALSE); + break; + } + if (up) + fr = fr->fr_prev; + else + fr = fr->fr_next; + } + row = win_comp_pos(); + screen_fill(row, cmdline_row, 0, (int)Columns, ' ', ' ', 0); + cmdline_row = row; + p_ch = Rows - cmdline_row; + if (p_ch < 1) + p_ch = 1; + redraw_all_later(NOT_VALID); + showmode(); + } + + #ifdef FEAT_VERTSPLIT + /* + * Separator line of dragwin is dragged "offset" lines right (negative is left). + */ + void + win_drag_vsep_line(dragwin, offset) + win_T *dragwin; + int offset; + { + frame_T *curfr; + frame_T *fr; + int room; + int left; /* if TRUE, drag separator line left, otherwise right */ + int n; + + fr = dragwin->w_frame; + if (fr == topframe) /* only one window (cannot happe?) */ + return; + curfr = fr; + fr = fr->fr_parent; + /* When the parent frame is not a row of frames, its parent should be. */ + if (fr->fr_layout != FR_ROW) + { + if (fr == topframe) /* only a column of windows (cannot happen?) */ + return; + curfr = fr; + fr = fr->fr_parent; + } + + /* If this is the last frame in a row, may want to resize a parent + * frame instead. */ + while (curfr->fr_next == NULL) + { + if (fr == topframe) + break; + curfr = fr; + fr = fr->fr_parent; + if (fr != topframe) + { + curfr = fr; + fr = fr->fr_parent; + } + } + + if (offset < 0) /* drag left */ + { + left = TRUE; + offset = -offset; + /* sum up the room of the current frame and left of it */ + room = 0; + for (fr = fr->fr_child; ; fr = fr->fr_next) + { + room += fr->fr_width - frame_minwidth(fr, NULL); + if (fr == curfr) + break; + } + fr = curfr->fr_next; /* put fr at frame that grows */ + } + else /* drag right */ + { + left = FALSE; + /* sum up the room of frames right of the current one */ + room = 0; + for (fr = curfr->fr_next; fr != NULL; fr = fr->fr_next) + room += fr->fr_width - frame_minwidth(fr, NULL); + fr = curfr; /* put fr at window that grows */ + } + + if (room < offset) /* Not enough room */ + offset = room; /* Move as far as we can */ + if (offset <= 0) /* No room at all, quit. */ + return; + + /* grow frame fr by offset lines */ + frame_new_width(fr, fr->fr_width + offset, left); + + /* shrink other frames: current and at the left or at the right */ + if (left) + fr = curfr; /* current frame gets smaller */ + else + fr = curfr->fr_next; /* next frame gets smaller */ + + while (fr != NULL && offset > 0) + { + n = frame_minwidth(fr, NULL); + if (fr->fr_width - offset <= n) + { + offset -= fr->fr_width - n; + frame_new_width(fr, n, !left); + } + else + { + frame_new_width(fr, fr->fr_width - offset, !left); + break; + } + if (left) + fr = fr->fr_prev; + else + fr = fr->fr_next; + } + (void)win_comp_pos(); + redraw_all_later(NOT_VALID); + } + #endif /* FEAT_VERTSPLIT */ + #endif /* FEAT_MOUSE */ + + #endif /* FEAT_WINDOWS */ + + /* + * Set the height of a window. + * This takes care of the things inside the window, not what happens to the + * window position, the frame or to other windows. + */ + static void + win_new_height(wp, height) + win_T *wp; + int height; + { + linenr_T lnum; + linenr_T bot; + int sline, line_size; + int space; + int did_below = FALSE; + #define FRACTION_MULT 16384L + + /* Don't want a negative height. Happens when splitting a tiny window. + * Will equalize heights soon to fix it. */ + if (height < 0) + height = 0; + + if (wp->w_wrow != wp->w_prev_fraction_row && wp->w_height > 0) + wp->w_fraction = ((long)wp->w_wrow * FRACTION_MULT + + FRACTION_MULT / 2) / (long)wp->w_height; + + wp->w_height = height; + wp->w_skipcol = 0; + + /* Don't change w_topline when height is zero. Don't set w_topline when + * 'scrollbind' is set and this isn't the current window. */ + if (height > 0 + #ifdef FEAT_SCROLLBIND + && (!wp->w_p_scb || wp == curwin) + #endif + ) + { + /* + * Find a value for w_topline that shows the cursor at the same + * relative position in the window as before (more or less). + */ + lnum = wp->w_cursor.lnum; + if (lnum < 1) /* can happen when starting up */ + lnum = 1; + wp->w_wrow = ((long)wp->w_fraction * (long)height - 1L) / FRACTION_MULT; + line_size = plines_win_col(wp, lnum, (long)(wp->w_cursor.col)) - 1; + sline = wp->w_wrow - line_size; + if (sline < 0) + { + /* + * Cursor line would go off top of screen if w_wrow was this high. + */ + wp->w_wrow = line_size; + } + else + { + space = height; + while (lnum > 1) + { + space -= line_size; + if (space > 0 && sline <= 0 && !did_below) + { + /* Try to use "~" lines below the text to avoid that text + * is above the window while there are empty lines. + * Subtract the rows below the cursor from "space" and + * give the rest to "sline". */ + did_below = TRUE; + bot = wp->w_cursor.lnum; + while (space > 0) + { + if (wp->w_buffer->b_ml.ml_line_count - bot >= space) + space = 0; + else + { + #ifdef FEAT_FOLDING + hasFoldingWin(wp, bot, NULL, &bot, TRUE, NULL); + #endif + if (bot >= wp->w_buffer->b_ml.ml_line_count) + break; + ++bot; + space -= plines_win(wp, bot, TRUE); + } + } + if (bot == wp->w_buffer->b_ml.ml_line_count && space > 0) + sline += space; + } + if (sline <= 0) + break; + + #ifdef FEAT_FOLDING + hasFoldingWin(wp, lnum, &lnum, NULL, TRUE, NULL); + if (lnum == 1) + { + /* first line in buffer is folded */ + line_size = 1; + --sline; + break; + } + #endif + --lnum; + #ifdef FEAT_DIFF + if (lnum == wp->w_topline) + line_size = plines_win_nofill(wp, lnum, TRUE) + + wp->w_topfill; + else + #endif + line_size = plines_win(wp, lnum, TRUE); + sline -= line_size; + } + + if (sline < 0) + { + /* + * Line we want at top would go off top of screen. Use next + * line instead. + */ + #ifdef FEAT_FOLDING + hasFoldingWin(wp, lnum, NULL, &lnum, TRUE, NULL); + #endif + lnum++; + wp->w_wrow -= line_size + sline; + } + else if (sline > 0) + { + /* First line of file reached, use that as topline. */ + lnum = 1; + wp->w_wrow -= sline; + } + } + set_topline(wp, lnum); + } + + if (wp == curwin) + { + if (p_so) + update_topline(); + curs_columns(FALSE); /* validate w_wrow */ + } + wp->w_prev_fraction_row = wp->w_wrow; + + win_comp_scroll(wp); + redraw_win_later(wp, NOT_VALID); + #ifdef FEAT_WINDOWS + wp->w_redr_status = TRUE; + #endif + invalidate_botline_win(wp); + } + + #ifdef FEAT_VERTSPLIT + /* + * Set the width of a window. + */ + static void + win_new_width(wp, width) + win_T *wp; + int width; + { + wp->w_width = width; + wp->w_lines_valid = 0; + changed_line_abv_curs_win(wp); + invalidate_botline_win(wp); + if (wp == curwin) + { + update_topline(); + curs_columns(TRUE); /* validate w_wrow */ + } + redraw_win_later(wp, NOT_VALID); + wp->w_redr_status = TRUE; + } + #endif + + void + win_comp_scroll(wp) + win_T *wp; + { + wp->w_p_scr = ((unsigned)wp->w_height >> 1); + if (wp->w_p_scr == 0) + wp->w_p_scr = 1; + } + + /* + * command_height: called whenever p_ch has been changed + */ + void + command_height(old_p_ch) + long old_p_ch; + { + #ifdef FEAT_WINDOWS + int h; + frame_T *frp; + + /* When passed a negative value use the value of p_ch that we remembered. + * This is needed for when the GUI starts up, we can't be sure in what + * order things happen. */ + if (old_p_ch < 0) + old_p_ch = p_ch_used; + p_ch_used = p_ch; + + /* Find bottom frame with width of screen. */ + frp = lastwin->w_frame; + # ifdef FEAT_VERTSPLIT + while (frp->fr_width != Columns && frp->fr_parent != NULL) + frp = frp->fr_parent; + # endif + + /* Avoid changing the height of a window with 'winfixheight' set. */ + while (frp->fr_prev != NULL && frp->fr_layout == FR_LEAF + && frp->fr_win->w_p_wfh) + frp = frp->fr_prev; + + if (starting != NO_SCREEN) + { + cmdline_row = Rows - p_ch; + + if (p_ch > old_p_ch) /* p_ch got bigger */ + { + while (p_ch > old_p_ch) + { + if (frp == NULL) + { + EMSG(_(e_noroom)); + p_ch = old_p_ch; + cmdline_row = Rows - p_ch; + break; + } + h = frp->fr_height - frame_minheight(frp, NULL); + if (h > p_ch - old_p_ch) + h = p_ch - old_p_ch; + old_p_ch += h; + frame_add_height(frp, -h); + frp = frp->fr_prev; + } + + /* Recompute window positions. */ + (void)win_comp_pos(); + + /* clear the lines added to cmdline */ + if (full_screen) + screen_fill((int)(cmdline_row), (int)Rows, 0, + (int)Columns, ' ', ' ', 0); + msg_row = cmdline_row; + redraw_cmdline = TRUE; + return; + } + + if (msg_row < cmdline_row) + msg_row = cmdline_row; + redraw_cmdline = TRUE; + } + frame_add_height(frp, (int)(old_p_ch - p_ch)); + + /* Recompute window positions. */ + if (frp != lastwin->w_frame) + (void)win_comp_pos(); + #else + win_setheight((int)(firstwin->w_height + old_p_ch - p_ch)); + cmdline_row = Rows - p_ch; + #endif + } + + #if defined(FEAT_WINDOWS) || defined(PROTO) + /* + * Resize frame "frp" to be "n" lines higher (negative for less high). + * Also resize the frames it is contained in. + */ + static void + frame_add_height(frp, n) + frame_T *frp; + int n; + { + frame_new_height(frp, frp->fr_height + n, FALSE, FALSE); + for (;;) + { + frp = frp->fr_parent; + if (frp == NULL) + break; + frp->fr_height += n; + } + } + + /* + * Add or remove a status line for the bottom window(s), according to the + * value of 'laststatus'. + */ + void + last_status(morewin) + int morewin; /* pretend there are two or more windows */ + { + /* Don't make a difference between horizontal or vertical split. */ + last_status_rec(topframe, (p_ls == 2 + || (p_ls == 1 && (morewin || lastwin != firstwin)))); + } + + static void + last_status_rec(fr, statusline) + frame_T *fr; + int statusline; + { + frame_T *fp; + win_T *wp; + + if (fr->fr_layout == FR_LEAF) + { + wp = fr->fr_win; + if (wp->w_status_height != 0 && !statusline) + { + /* remove status line */ + win_new_height(wp, wp->w_height + 1); + wp->w_status_height = 0; + comp_col(); + } + else if (wp->w_status_height == 0 && statusline) + { + /* Find a frame to take a line from. */ + fp = fr; + while (fp->fr_height <= frame_minheight(fp, NULL)) + { + if (fp == topframe) + { + EMSG(_(e_noroom)); + return; + } + /* In a column of frames: go to frame above. If already at + * the top or in a row of frames: go to parent. */ + if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) + fp = fp->fr_prev; + else + fp = fp->fr_parent; + } + wp->w_status_height = 1; + if (fp != fr) + { + frame_new_height(fp, fp->fr_height - 1, FALSE, FALSE); + frame_fix_height(wp); + (void)win_comp_pos(); + } + else + win_new_height(wp, wp->w_height - 1); + comp_col(); + redraw_all_later(NOT_VALID); + } + } + #ifdef FEAT_VERTSPLIT + else if (fr->fr_layout == FR_ROW) + { + /* vertically split windows, set status line for each one */ + for (fp = fr->fr_child; fp != NULL; fp = fp->fr_next) + last_status_rec(fp, statusline); + } + #endif + else + { + /* horizontally split window, set status line for last one */ + for (fp = fr->fr_child; fp->fr_next != NULL; fp = fp->fr_next) + ; + last_status_rec(fp, statusline); + } + } + ++/* ++ * Return TRUE if the tab page line is to be drawn. ++ */ ++ int ++tabpageline_height() ++{ ++ /* TODO: option to tell when to show the tabs. */ ++ if (first_tabpage->tp_next == NULL) ++ return 0; ++ return 1; ++} ++ + #endif /* FEAT_WINDOWS */ + + #if defined(FEAT_SEARCHPATH) || defined(PROTO) + /* + * Get the file name at the cursor. + * If Visual mode is active, use the selected text if it's in one line. + * Returns the name in allocated memory, NULL for failure. + */ + char_u * + grab_file_name(count) + long count; + { + # ifdef FEAT_VISUAL + if (VIsual_active) + { + int len; + char_u *ptr; + + if (get_visual_text(NULL, &ptr, &len) == FAIL) + return NULL; + return find_file_name_in_path(ptr, len, + FNAME_MESS|FNAME_EXP|FNAME_REL, count, curbuf->b_ffname); + } + # endif + return file_name_at_cursor(FNAME_MESS|FNAME_HYP|FNAME_EXP|FNAME_REL, count); + } + + /* + * Return the file name under or after the cursor. + * + * The 'path' option is searched if the file name is not absolute. + * The string returned has been alloc'ed and should be freed by the caller. + * NULL is returned if the file name or file is not found. + * + * options: + * FNAME_MESS give error messages + * FNAME_EXP expand to path + * FNAME_HYP check for hypertext link + * FNAME_INCL apply "includeexpr" + */ + char_u * + file_name_at_cursor(options, count) + int options; + long count; + { + return file_name_in_line(ml_get_curline(), + curwin->w_cursor.col, options, count, curbuf->b_ffname); + } + + /* + * Return the name of the file under or after ptr[col]. + * Otherwise like file_name_at_cursor(). + */ + char_u * + file_name_in_line(line, col, options, count, rel_fname) + char_u *line; + int col; + int options; + long count; + char_u *rel_fname; /* file we are searching relative to */ + { + char_u *ptr; + int len; + + /* + * search forward for what could be the start of a file name + */ + ptr = line + col; + while (*ptr != NUL && !vim_isfilec(*ptr)) + mb_ptr_adv(ptr); + if (*ptr == NUL) /* nothing found */ + { + if (options & FNAME_MESS) + EMSG(_("E446: No file name under cursor")); + return NULL; + } + + /* + * Search backward for first char of the file name. + * Go one char back to ":" before "//" even when ':' is not in 'isfname'. + */ + while (ptr > line) + { + #ifdef FEAT_MBYTE + if (has_mbyte && (len = (*mb_head_off)(line, ptr - 1)) > 0) + ptr -= len + 1; + else + #endif + if (vim_isfilec(ptr[-1]) + || ((options & FNAME_HYP) && path_is_url(ptr - 1))) + --ptr; + else + break; + } + + /* + * Search forward for the last char of the file name. + * Also allow "://" when ':' is not in 'isfname'. + */ + len = 0; + while (vim_isfilec(ptr[len]) + || ((options & FNAME_HYP) && path_is_url(ptr + len))) + #ifdef FEAT_MBYTE + if (has_mbyte) + len += (*mb_ptr2len)(ptr + len); + else + #endif + ++len; + + /* + * If there is trailing punctuation, remove it. + * But don't remove "..", could be a directory name. + */ + if (len > 2 && vim_strchr((char_u *)".,:;!", ptr[len - 1]) != NULL + && ptr[len - 2] != '.') + --len; + + return find_file_name_in_path(ptr, len, options, count, rel_fname); + } + + # if defined(FEAT_FIND_ID) && defined(FEAT_EVAL) + static char_u *eval_includeexpr __ARGS((char_u *ptr, int len)); + + static char_u * + eval_includeexpr(ptr, len) + char_u *ptr; + int len; + { + char_u *res; + + set_vim_var_string(VV_FNAME, ptr, len); + res = eval_to_string_safe(curbuf->b_p_inex, NULL, + was_set_insecurely((char_u *)"includeexpr")); + set_vim_var_string(VV_FNAME, NULL, 0); + return res; + } + #endif + + /* + * Return the name of the file ptr[len] in 'path'. + * Otherwise like file_name_at_cursor(). + */ + char_u * + find_file_name_in_path(ptr, len, options, count, rel_fname) + char_u *ptr; + int len; + int options; + long count; + char_u *rel_fname; /* file we are searching relative to */ + { + char_u *file_name; + int c; + # if defined(FEAT_FIND_ID) && defined(FEAT_EVAL) + char_u *tofree = NULL; + + if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) + { + tofree = eval_includeexpr(ptr, len); + if (tofree != NULL) + { + ptr = tofree; + len = (int)STRLEN(ptr); + } + } + # endif + + if (options & FNAME_EXP) + { + file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, + TRUE, rel_fname); + + # if defined(FEAT_FIND_ID) && defined(FEAT_EVAL) + /* + * If the file could not be found in a normal way, try applying + * 'includeexpr' (unless done already). + */ + if (file_name == NULL + && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL) + { + tofree = eval_includeexpr(ptr, len); + if (tofree != NULL) + { + ptr = tofree; + len = (int)STRLEN(ptr); + file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, + TRUE, rel_fname); + } + } + # endif + if (file_name == NULL && (options & FNAME_MESS)) + { + c = ptr[len]; + ptr[len] = NUL; + EMSG2(_("E447: Can't find file \"%s\" in path"), ptr); + ptr[len] = c; + } + + /* Repeat finding the file "count" times. This matters when it + * appears several times in the path. */ + while (file_name != NULL && --count > 0) + { + vim_free(file_name); + file_name = find_file_in_path(ptr, len, options, FALSE, rel_fname); + } + } + else + file_name = vim_strnsave(ptr, len); + + # if defined(FEAT_FIND_ID) && defined(FEAT_EVAL) + vim_free(tofree); + # endif + + return file_name; + } + #endif /* FEAT_SEARCHPATH */ + + /* + * Check if the "://" of a URL is at the pointer, return URL_SLASH. + * Also check for ":\\", which MS Internet Explorer accepts, return + * URL_BACKSLASH. + */ + static int + path_is_url(p) + char_u *p; + { + if (STRNCMP(p, "://", (size_t)3) == 0) + return URL_SLASH; + else if (STRNCMP(p, ":\\\\", (size_t)3) == 0) + return URL_BACKSLASH; + return 0; + } + + /* + * Check if "fname" starts with "name://". Return URL_SLASH if it does. + * Return URL_BACKSLASH for "name:\\". + * Return zero otherwise. + */ + int + path_with_url(fname) + char_u *fname; + { + char_u *p; + + for (p = fname; isalpha(*p); ++p) + ; + return path_is_url(p); + } + + /* + * Return TRUE if "name" is a full (absolute) path name or URL. + */ + int + vim_isAbsName(name) + char_u *name; + { + return (path_with_url(name) != 0 || mch_isFullName(name)); + } + + /* + * Get absolute file name into buffer "buf[len]". + * + * return FAIL for failure, OK otherwise + */ + int + vim_FullName(fname, buf, len, force) + char_u *fname, *buf; + int len; + int force; /* force expansion even when already absolute */ + { + int retval = OK; + int url; + + *buf = NUL; + if (fname == NULL) + return FAIL; + + url = path_with_url(fname); + if (!url) + retval = mch_FullName(fname, buf, len, force); + if (url || retval == FAIL) + { + /* something failed; use the file name (truncate when too long) */ + vim_strncpy(buf, fname, len - 1); + } + #if defined(MACOS_CLASSIC) || defined(OS2) || defined(MSDOS) || defined(MSWIN) + slash_adjust(buf); + #endif + return retval; + } + + /* + * Return the minimal number of rows that is needed on the screen to display + * the current number of windows. + */ + int + min_rows() + { + int total; + + if (firstwin == NULL) /* not initialized yet */ + return MIN_LINES; + + total = 1; /* count the room for the command line */ + #ifdef FEAT_WINDOWS + total += frame_minheight(topframe, NULL); + #else + total += 1; /* at least one window should have a line! */ + #endif + return total; + } + + /* + * Return TRUE if there is only one window, not counting a help or preview + * window, unless it is the current window. + */ + int + only_one_window() + { + #ifdef FEAT_WINDOWS + int count = 0; + win_T *wp; + ++ /* If there is another tab page there always is another window. */ ++ if (first_tabpage->tp_next != NULL) ++ return FALSE; ++ + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (!((wp->w_buffer->b_help && !curbuf->b_help) + # ifdef FEAT_QUICKFIX + || wp->w_p_pvw + # endif + ) || wp == curwin) + ++count; + return (count <= 1); + #else + return TRUE; + #endif + } + + #if defined(FEAT_WINDOWS) || defined(FEAT_AUTOCMD) || defined(PROTO) + /* + * Correct the cursor line number in other windows. Used after changing the + * current buffer, and before applying autocommands. + * When "do_curwin" is TRUE, also check current window. + */ + void + check_lnums(do_curwin) + int do_curwin; + { + win_T *wp; + + #ifdef FEAT_WINDOWS + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if ((do_curwin || wp != curwin) && wp->w_buffer == curbuf) + #else + wp = curwin; + if (do_curwin) + #endif + { + if (wp->w_cursor.lnum > curbuf->b_ml.ml_line_count) + wp->w_cursor.lnum = curbuf->b_ml.ml_line_count; + if (wp->w_topline > curbuf->b_ml.ml_line_count) + wp->w_topline = curbuf->b_ml.ml_line_count; + } + } + #endif + + #if defined(FEAT_WINDOWS) || defined(PROTO) + + /* + * A snapshot of the window sizes, to restore them after closing the help + * window. + * Only these fields are used: + * fr_layout + * fr_width + * fr_height + * fr_next + * fr_child + * fr_win (only valid for the old curwin, NULL otherwise) + */ + static frame_T *snapshot = NULL; + + /* + * Create a snapshot of the current frame sizes. + */ + static void + make_snapshot() + { + clear_snapshot(); + make_snapshot_rec(topframe, &snapshot); + } + + static void + make_snapshot_rec(fr, frp) + frame_T *fr; + frame_T **frp; + { + *frp = (frame_T *)alloc_clear((unsigned)sizeof(frame_T)); + if (*frp == NULL) + return; + (*frp)->fr_layout = fr->fr_layout; + # ifdef FEAT_VERTSPLIT + (*frp)->fr_width = fr->fr_width; + # endif + (*frp)->fr_height = fr->fr_height; + if (fr->fr_next != NULL) + make_snapshot_rec(fr->fr_next, &((*frp)->fr_next)); + if (fr->fr_child != NULL) + make_snapshot_rec(fr->fr_child, &((*frp)->fr_child)); + if (fr->fr_layout == FR_LEAF && fr->fr_win == curwin) + (*frp)->fr_win = curwin; + } + + /* + * Remove any existing snapshot. + */ + static void + clear_snapshot() + { + clear_snapshot_rec(snapshot); + snapshot = NULL; + } + + static void + clear_snapshot_rec(fr) + frame_T *fr; + { + if (fr != NULL) + { + clear_snapshot_rec(fr->fr_next); + clear_snapshot_rec(fr->fr_child); + vim_free(fr); + } + } + + /* + * Restore a previously created snapshot, if there is any. + * This is only done if the screen size didn't change and the window layout is + * still the same. + */ + static void + restore_snapshot(close_curwin) + int close_curwin; /* closing current window */ + { + win_T *wp; + + if (snapshot != NULL + # ifdef FEAT_VERTSPLIT + && snapshot->fr_width == topframe->fr_width + # endif + && snapshot->fr_height == topframe->fr_height + && check_snapshot_rec(snapshot, topframe) == OK) + { + wp = restore_snapshot_rec(snapshot, topframe); + win_comp_pos(); + if (wp != NULL && close_curwin) + win_goto(wp); + redraw_all_later(CLEAR); + } + clear_snapshot(); + } + + /* + * Check if frames "sn" and "fr" have the same layout, same following frames + * and same children. + */ + static int + check_snapshot_rec(sn, fr) + frame_T *sn; + frame_T *fr; + { + if (sn->fr_layout != fr->fr_layout + || (sn->fr_next == NULL) != (fr->fr_next == NULL) + || (sn->fr_child == NULL) != (fr->fr_child == NULL) + || (sn->fr_next != NULL + && check_snapshot_rec(sn->fr_next, fr->fr_next) == FAIL) + || (sn->fr_child != NULL + && check_snapshot_rec(sn->fr_child, fr->fr_child) == FAIL)) + return FAIL; + return OK; + } + + /* + * Copy the size of snapshot frame "sn" to frame "fr". Do the same for all + * following frames and children. + * Returns a pointer to the old current window, or NULL. + */ + static win_T * + restore_snapshot_rec(sn, fr) + frame_T *sn; + frame_T *fr; + { + win_T *wp = NULL; + win_T *wp2; + + fr->fr_height = sn->fr_height; + # ifdef FEAT_VERTSPLIT + fr->fr_width = sn->fr_width; + # endif + if (fr->fr_layout == FR_LEAF) + { + frame_new_height(fr, fr->fr_height, FALSE, FALSE); + # ifdef FEAT_VERTSPLIT + frame_new_width(fr, fr->fr_width, FALSE); + # endif + wp = sn->fr_win; + } + if (sn->fr_next != NULL) + { + wp2 = restore_snapshot_rec(sn->fr_next, fr->fr_next); + if (wp2 != NULL) + wp = wp2; + } + if (sn->fr_child != NULL) + { + wp2 = restore_snapshot_rec(sn->fr_child, fr->fr_child); + if (wp2 != NULL) + wp = wp2; + } + return wp; + } + + #endif + + #if (defined(FEAT_GUI) && defined(FEAT_VERTSPLIT)) || defined(PROTO) + /* + * Return TRUE if there is any vertically split window. + */ + int + win_hasvertsplit() + { + frame_T *fr; + + if (topframe->fr_layout == FR_ROW) + return TRUE; + + if (topframe->fr_layout == FR_COL) + for (fr = topframe->fr_child; fr != NULL; fr = fr->fr_next) + if (fr->fr_layout == FR_ROW) + return TRUE; + + return FALSE; + } + #endif diff --git a/src/test/resources/unparser/diff/error010.txt b/src/test/resources/unparser/diff/error010.txt new file mode 100644 index 00000000..f3d06e07 --- /dev/null +++ b/src/test/resources/unparser/diff/error010.txt @@ -0,0 +1,3288 @@ +textDiff: + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + /* + * os_mswin.c + * + * Routines for Win32. + */ + + #include "vim.h" + + #include + #include + #include ++ ++// cproto fails on missing include files + #ifndef PROTO + # include +-#endif +- +-#undef chdir +-#ifdef __GNUC__ +-# ifndef __MINGW32__ +-# include +-# endif +-#else + # include +-#endif + +-#ifndef PROTO + # if !defined(FEAT_GUI_MSWIN) + # include + # endif + + # if defined(FEAT_PRINTER) && !defined(FEAT_POSTSCRIPT) + # include + # include + # include + # endif +- + #endif // PROTO + +-#ifdef __MINGW32__ +-# ifndef FROM_LEFT_1ST_BUTTON_PRESSED +-# define FROM_LEFT_1ST_BUTTON_PRESSED 0x0001 +-# endif +-# ifndef RIGHTMOST_BUTTON_PRESSED +-# define RIGHTMOST_BUTTON_PRESSED 0x0002 +-# endif +-# ifndef FROM_LEFT_2ND_BUTTON_PRESSED +-# define FROM_LEFT_2ND_BUTTON_PRESSED 0x0004 +-# endif +-# ifndef FROM_LEFT_3RD_BUTTON_PRESSED +-# define FROM_LEFT_3RD_BUTTON_PRESSED 0x0008 +-# endif +-# ifndef FROM_LEFT_4TH_BUTTON_PRESSED +-# define FROM_LEFT_4TH_BUTTON_PRESSED 0x0010 +-# endif +- +-/* +- * EventFlags +- */ +-# ifndef MOUSE_MOVED +-# define MOUSE_MOVED 0x0001 +-# endif +-# ifndef DOUBLE_CLICK +-# define DOUBLE_CLICK 0x0002 +-# endif +-#endif +- + /* + * When generating prototypes for Win32 on Unix, these lines make the syntax + * errors disappear. They do not need to be correct. + */ + #ifdef PROTO + # define WINAPI + # define WINBASEAPI + typedef int BOOL; + typedef int CALLBACK; + typedef int COLORREF; + typedef int CONSOLE_CURSOR_INFO; + typedef int COORD; + typedef int DWORD; + typedef int ENUMLOGFONTW; + typedef int HANDLE; + typedef int HDC; + typedef int HFONT; + typedef int HICON; + typedef int HWND; + typedef int INPUT_RECORD; + typedef int INT_PTR; + typedef int KEY_EVENT_RECORD; + typedef int LOGFONTW; + typedef int LPARAM; + typedef int LPBOOL; + typedef int LPCSTR; + typedef int LPCWSTR; + typedef int LPDWORD; + typedef int LPSTR; + typedef int LPTSTR; + typedef int LPVOID; + typedef int LPWSTR; + typedef int LRESULT; + typedef int MOUSE_EVENT_RECORD; + typedef int NEWTEXTMETRICW; + typedef int PACL; + typedef int PRINTDLGW; + typedef int PSECURITY_DESCRIPTOR; + typedef int PSID; + typedef int SECURITY_INFORMATION; + typedef int SHORT; + typedef int SMALL_RECT; + typedef int TEXTMETRIC; + typedef int UINT; + typedef int WCHAR; + typedef int WNDENUMPROC; + typedef int WORD; + typedef int WPARAM; + typedef void VOID; + #endif + + // Record all output and all keyboard & mouse input + // #define MCH_WRITE_DUMP + + #ifdef MCH_WRITE_DUMP + FILE* fdDump = NULL; + #endif + + #if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + extern char g_szOrigTitle[]; + #endif + + #ifdef FEAT_GUI + extern HWND s_hwnd; + #else + static HWND s_hwnd = 0; // console window handle, set by GetConsoleHwnd() + #endif + + #ifdef FEAT_JOB_CHANNEL + int WSInitialized = FALSE; // WinSock is initialized + #endif + +-// Don't generate prototypes here, because some systems do have these +-// functions. +-#if defined(__GNUC__) && !defined(PROTO) +-# ifndef __MINGW32__ +-int _stricoll(char *a, char *b) +-{ +- // the ANSI-ish correct way is to use strxfrm(): +- char a_buff[512], b_buff[512]; // file names, so this is enough on Win32 +- strxfrm(a_buff, a, 512); +- strxfrm(b_buff, b, 512); +- return strcoll(a_buff, b_buff); +-} +- +-char * _fullpath(char *buf, char *fname, int len) +-{ +- LPTSTR toss; +- +- return (char *)GetFullPathName(fname, len, buf, &toss); +-} +-# endif +- +-# if !defined(__MINGW32__) || (__GNUC__ < 4) +-int _chdrive(int drive) +-{ +- char temp [3] = "-:"; +- temp[0] = drive + 'A' - 1; +- return !SetCurrentDirectory(temp); +-} +-# endif +-#endif +- + + #ifndef PROTO + /* + * Save the instance handle of the exe/dll. + */ + void + SaveInst(HINSTANCE hInst) + { + g_hinst = hInst; + } + #endif + + #if defined(FEAT_GUI_MSWIN) || defined(PROTO) + /* + * GUI version of mch_exit(). + * Shut down and exit with status `r' + * Careful: mch_exit() may be called before mch_init()! + */ + void + mch_exit_g(int r) + { + exiting = TRUE; + + display_errors(); + + ml_close_all(TRUE); // remove all memfiles + + # ifdef FEAT_OLE + UninitOLE(); + # endif + # ifdef FEAT_JOB_CHANNEL + if (WSInitialized) + { + WSInitialized = FALSE; + WSACleanup(); + } + # endif + # ifdef DYNAMIC_GETTEXT + dyn_libintl_end(); + # endif + + if (gui.in_use) + gui_exit(r); + + # ifdef EXITFREE + free_all_mem(); + # endif + + exit(r); + } + + #endif // FEAT_GUI_MSWIN + + + /* + * Init the tables for toupper() and tolower(). + */ + void + mch_early_init(void) + { + int i; + + PlatformId(); + + // Init the tables for toupper() and tolower() + for (i = 0; i < 256; ++i) + toupper_tab[i] = tolower_tab[i] = i; + CharUpperBuff((LPSTR)toupper_tab, 256); + CharLowerBuff((LPSTR)tolower_tab, 256); + } + + + /* + * Return TRUE if the input comes from a terminal, FALSE otherwise. + */ + int + mch_input_isatty(void) + { + #ifdef FEAT_GUI_MSWIN + # ifdef VIMDLL + if (gui.in_use) + # endif + return TRUE; // GUI always has a tty + #endif + #if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + if (isatty(read_cmd_fd)) + return TRUE; + return FALSE; + #endif + } + + /* + * mch_settitle(): set titlebar of our window + */ + void + mch_settitle( + char_u *title, + char_u *icon UNUSED) + { + #ifdef FEAT_GUI_MSWIN + # ifdef VIMDLL + if (gui.in_use) + # endif + { + gui_mch_settitle(title, icon); + return; + } + #endif + #if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + if (title != NULL) + { + WCHAR *wp = enc_to_utf16(title, NULL); + + if (wp == NULL) + return; + + SetConsoleTitleW(wp); + vim_free(wp); + return; + } + #endif + } + + + /* + * Restore the window/icon title. + * which is one of: + * SAVE_RESTORE_TITLE: Just restore title + * SAVE_RESTORE_ICON: Just restore icon (which we don't have) + * SAVE_RESTORE_BOTH: Restore title and icon (which we don't have) + */ + void + mch_restore_title(int which UNUSED) + { + #if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + # ifdef VIMDLL + if (!gui.in_use) + # endif + SetConsoleTitle(g_szOrigTitle); + #endif + } + + + /* + * Return TRUE if we can restore the title (we can) + */ + int + mch_can_restore_title(void) + { + return TRUE; + } + + + /* + * Return TRUE if we can restore the icon title (we can't) + */ + int + mch_can_restore_icon(void) + { + return FALSE; + } + + + /* + * Get absolute file name into buffer "buf" of length "len" bytes, + * turning all '/'s into '\\'s and getting the correct case of each component + * of the file name. Append a (back)slash to a directory name. + * When 'shellslash' set do it the other way around. + * Return OK or FAIL. + */ + int + mch_FullName( + char_u *fname, + char_u *buf, + int len, + int force UNUSED) + { + int nResult = FAIL; + WCHAR *wname; + WCHAR wbuf[MAX_PATH]; + char_u *cname = NULL; + + wname = enc_to_utf16(fname, NULL); + if (wname != NULL && _wfullpath(wbuf, wname, MAX_PATH) != NULL) + { + cname = utf16_to_enc((short_u *)wbuf, NULL); + if (cname != NULL) + { + vim_strncpy(buf, cname, len - 1); + nResult = OK; + } + } + vim_free(wname); + vim_free(cname); + + #ifdef USE_FNAME_CASE + fname_case(buf, len); + #else + slash_adjust(buf); + #endif + + return nResult; + } + + + /* + * Return TRUE if "fname" does not depend on the current directory. + */ + int + mch_isFullName(char_u *fname) + { + // A name like "d:/foo" and "//server/share" is absolute. "d:foo" is not. + // Another way to check is to use mch_FullName() and see if the result is + // the same as the name or mch_FullName() fails. However, this has quite a + // bit of overhead, so let's not do that. + if (*fname == NUL) + return FALSE; + return ((ASCII_ISALPHA(fname[0]) && fname[1] == ':' + && (fname[2] == '/' || fname[2] == '\\')) + || (fname[0] == fname[1] && (fname[0] == '/' || fname[0] == '\\'))); + } + + /* + * Replace all slashes by backslashes. + * This used to be the other way around, but MS-DOS sometimes has problems + * with slashes (e.g. in a command name). We can't have mixed slashes and + * backslashes, because comparing file names will not work correctly. The + * commands that use a file name should try to avoid the need to type a + * backslash twice. + * When 'shellslash' set do it the other way around. + * When the path looks like a URL leave it unmodified. + */ + void + slash_adjust(char_u *p) + { + if (path_with_url(p)) + return; + + if (*p == '`') + { + size_t len = STRLEN(p); + + // don't replace backslash in backtick quoted strings + if (len > 2 && *(p + len - 1) == '`') + return; + } + + while (*p) + { + if (*p == psepcN) + *p = psepc; + MB_PTR_ADV(p); + } + } + + static int + read_reparse_point(const WCHAR *name, char_u *buf, DWORD *buf_len) + { + HANDLE h; + BOOL ok; + + h = CreateFileW(name, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + NULL); + if (h == INVALID_HANDLE_VALUE) + return FAIL; + + ok = DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, buf, *buf_len, + buf_len, NULL); + CloseHandle(h); + + return ok ? OK : FAIL; + } + + char_u * + resolve_appexeclink(char_u *fname) + { + DWORD attr = 0; + int idx; + WCHAR *p, *end, *wname; + // The buffer size is arbitrarily chosen to be "big enough" (TM), the + // ceiling should be around 16k. + char_u buf[4096]; + DWORD buf_len = sizeof(buf); + REPARSE_DATA_BUFFER *rb = (REPARSE_DATA_BUFFER *)buf; + + wname = enc_to_utf16(fname, NULL); + if (wname == NULL) + return NULL; + + attr = GetFileAttributesW(wname); + if (attr == INVALID_FILE_ATTRIBUTES || + (attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0) + { + vim_free(wname); + return NULL; + } + + // The applinks are similar to symlinks but with a huge difference: they can + // only be executed, any other I/O operation on them is bound to fail with + // ERROR_FILE_NOT_FOUND even though the file exists. + if (read_reparse_point(wname, buf, &buf_len) == FAIL) + { + vim_free(wname); + return NULL; + } + vim_free(wname); + + if (rb->ReparseTag != IO_REPARSE_TAG_APPEXECLINK) + return NULL; + + // The (undocumented) reparse buffer contains a set of N null-terminated + // Unicode strings, the application path is stored in the third one. + if (rb->AppExecLinkReparseBuffer.StringCount < 3) + return NULL; + + p = rb->AppExecLinkReparseBuffer.StringList; + end = p + rb->ReparseDataLength / sizeof(WCHAR); + for (idx = 0; p < end + && idx < (int)rb->AppExecLinkReparseBuffer.StringCount + && idx != 2; ) + { + if (*p++ == L'\0') + ++idx; + } + + return utf16_to_enc(p, NULL); + } + + // Use 64-bit stat functions. + #undef stat + #undef _stat + #undef _wstat + #undef _fstat + #define stat _stat64 + #define _stat _stat64 + #define _wstat _wstat64 + #define _fstat _fstat64 + + /* + * Implements lstat() and stat() that can handle symlinks properly. + */ + static int + mswin_stat_impl(const WCHAR *name, stat_T *stp, const int resolve) + { + int n; + int fd; + BOOL is_symlink = FALSE; + HANDLE hFind, h; + DWORD attr = 0; + DWORD flag = 0; + WIN32_FIND_DATAW findDataW; + + #ifdef _UCRT + if (resolve) + // Universal CRT can handle symlinks properly. + return _wstat(name, stp); + #endif + + hFind = FindFirstFileW(name, &findDataW); + if (hFind != INVALID_HANDLE_VALUE) + { + attr = findDataW.dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) + && (findDataW.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) + is_symlink = TRUE; + FindClose(hFind); + } + + // Use the plain old stat() whenever it's possible. + if (!is_symlink) + return _wstat(name, stp); + + if (!resolve && is_symlink) + flag = FILE_FLAG_OPEN_REPARSE_POINT; + if (attr & FILE_ATTRIBUTE_DIRECTORY) + flag |= FILE_FLAG_BACKUP_SEMANTICS; + + h = CreateFileW(name, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, flag, + NULL); + if (h == INVALID_HANDLE_VALUE) + return -1; + + fd = _open_osfhandle((intptr_t)h, _O_RDONLY); + n = _fstat(fd, (struct _stat *)stp); + if ((n == 0) && (attr & FILE_ATTRIBUTE_DIRECTORY)) + stp->st_mode = (stp->st_mode & ~S_IFMT) | S_IFDIR; + _close(fd); + + return n; + } + + /* + * stat() can't handle a trailing '/' or '\', remove it first. + * When 'resolve' is true behave as lstat() wrt symlinks. + */ + static int + stat_impl(const char *name, stat_T *stp, const int resolve) + { + // WinNT and later can use _MAX_PATH wide characters for a pathname, which + // means that the maximum pathname is _MAX_PATH * 3 bytes when 'enc' is + // UTF-8. + char_u buf[_MAX_PATH * 3 + 1]; + char_u *p; + WCHAR *wp; + int n; + + vim_strncpy((char_u *)buf, (char_u *)name, sizeof(buf) - 1); + p = buf + STRLEN(buf); + if (p > buf) + MB_PTR_BACK(buf, p); + + // Remove trailing '\\' except root path. + if (p > buf && (*p == '\\' || *p == '/') && p[-1] != ':') + *p = NUL; + + if ((buf[0] == '\\' && buf[1] == '\\') || (buf[0] == '/' && buf[1] == '/')) + { + // UNC root path must be followed by '\\'. + p = vim_strpbrk(buf + 2, (char_u *)"\\/"); + if (p != NULL) + { + p = vim_strpbrk(p + 1, (char_u *)"\\/"); + if (p == NULL) + STRCAT(buf, "\\"); + } + } + + wp = enc_to_utf16(buf, NULL); + if (wp == NULL) + return -1; + + n = mswin_stat_impl(wp, stp, resolve); + vim_free(wp); + return n; + } + + int + vim_lstat(const char *name, stat_T *stp) + { + return stat_impl(name, stp, FALSE); + } + + int + vim_stat(const char *name, stat_T *stp) + { + return stat_impl(name, stp, TRUE); + } + + #if (defined(FEAT_GUI_MSWIN) && !defined(VIMDLL)) || defined(PROTO) + void + mch_settmode(tmode_T tmode UNUSED) + { + // nothing to do + } + + int + mch_get_shellsize(void) + { + // never used + return OK; + } + + void + mch_set_shellsize(void) + { + // never used + } + + /* + * Rows and/or Columns has changed. + */ + void + mch_new_shellsize(void) + { + // never used + } + + #endif + + /* + * We have no job control, so fake it by starting a new shell. + */ + void + mch_suspend(void) + { + suspend_shell(); + } + + #if defined(USE_MCH_ERRMSG) || defined(PROTO) + + # ifdef display_errors + # undef display_errors + # endif + + /* + * Display the saved error message(s). + */ + void + display_errors(void) + { + # ifdef FEAT_GUI + char_u *p; + + # ifdef VIMDLL + if (gui.in_use || gui.starting) + # endif + { + if (error_ga.ga_data != NULL) + { + // avoid putting up a message box with blanks only + for (p = (char_u *)error_ga.ga_data; *p; ++p) + if (!SAFE_isspace(*p)) + { + // Only use a dialog when not using --gui-dialog-file: + // write text to a file. + if (!gui_dialog_log((char_u *)"Errors", p)) + (void)gui_mch_dialog( + gui.starting ? VIM_INFO : + VIM_ERROR, + gui.starting ? (char_u *)_("Message") : + (char_u *)_("Error"), + p, (char_u *)_("&Ok"), + 1, NULL, FALSE); + break; + } + ga_clear(&error_ga); + } + return; + } + # endif + # if !defined(FEAT_GUI) || defined(VIMDLL) + FlushFileBuffers(GetStdHandle(STD_ERROR_HANDLE)); + # endif + } + #endif + + + /* + * Return TRUE if "p" contain a wildcard that can be expanded by + * dos_expandpath(). + */ + int + mch_has_exp_wildcard(char_u *p) + { + for ( ; *p; MB_PTR_ADV(p)) + { + if (vim_strchr((char_u *)"?*[", *p) != NULL + || (*p == '~' && p[1] != NUL)) + return TRUE; + } + return FALSE; + } + + /* + * Return TRUE if "p" contain a wildcard or a "~1" kind of thing (could be a + * shortened file name). + */ + int + mch_has_wildcard(char_u *p) + { + for ( ; *p; MB_PTR_ADV(p)) + { + if (vim_strchr((char_u *) + #ifdef VIM_BACKTICK + "?*$[`" + #else + "?*$[" + #endif + , *p) != NULL + || (*p == '~' && p[1] != NUL)) + return TRUE; + } + return FALSE; + } + + + /* + * The normal _chdir() does not change the default drive. This one does. + * Returning 0 implies success; -1 implies failure. + */ + int + mch_chdir(char *path) + { + WCHAR *p; + int n; + + if (path[0] == NUL) // just checking... + return -1; + + if (p_verbose >= 5) + { + verbose_enter(); + smsg("chdir(%s)", path); + verbose_leave(); + } + if (SAFE_isalpha(path[0]) && path[1] == ':') // has a drive name + { + // If we can change to the drive, skip that part of the path. If we + // can't then the current directory may be invalid, try using chdir() + // with the whole path. + if (_chdrive(TOLOWER_ASC(path[0]) - 'a' + 1) == 0) + path += 2; + } + + if (*path == NUL) // drive name only + return 0; + + p = enc_to_utf16((char_u *)path, NULL); + if (p == NULL) + return -1; + + n = _wchdir(p); + vim_free(p); + return n; + } + + + #if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL) + /* + * return non-zero if a character is available + */ + int + mch_char_avail(void) + { + // never used + return TRUE; + } + + # if defined(FEAT_TERMINAL) || defined(PROTO) + /* + * Check for any pending input or messages. + */ + int + mch_check_messages(void) + { + // TODO: check for messages + return TRUE; + } + # endif + #endif + + + #if defined(FEAT_LIBCALL) || defined(PROTO) + /* + * Call a DLL routine which takes either a string or int param + * and returns an allocated string. + * Return OK if it worked, FAIL if not. + */ + typedef LPTSTR (*MYSTRPROCSTR)(LPTSTR); + typedef LPTSTR (*MYINTPROCSTR)(int); + typedef int (*MYSTRPROCINT)(LPTSTR); + typedef int (*MYINTPROCINT)(int); + + /* + * Check if a pointer points to a valid NUL terminated string. + * Return the length of the string, including terminating NUL. + * Returns 0 for an invalid pointer, 1 for an empty string. + */ + static size_t + check_str_len(char_u *str) + { + SYSTEM_INFO si; + MEMORY_BASIC_INFORMATION mbi; + size_t length = 0; + size_t i; + const char_u *p; + + // get page size + GetSystemInfo(&si); + + // get memory information + if (!VirtualQuery(str, &mbi, sizeof(mbi))) + return 0; + + // pre cast these (typing savers) + long_u dwStr = (long_u)str; + long_u dwBaseAddress = (long_u)mbi.BaseAddress; + + // get start address of page that str is on + long_u strPage = dwStr - (dwStr - dwBaseAddress) % si.dwPageSize; + + // get length from str to end of page + long_u pageLength = si.dwPageSize - (dwStr - strPage); + + for (p = str; !IsBadReadPtr(p, (UINT)pageLength); + p += pageLength, pageLength = si.dwPageSize) + for (i = 0; i < pageLength; ++i, ++length) + if (p[i] == NUL) + return length + 1; + + return 0; + } + + /* + * Passed to do_in_runtimepath() to load a vim.ico file. + */ + static void + mch_icon_load_cb(char_u *fname, void *cookie) + { + HANDLE *h = (HANDLE *)cookie; + + *h = LoadImage(NULL, + (LPSTR)fname, + IMAGE_ICON, + 64, + 64, + LR_LOADFROMFILE | LR_LOADMAP3DCOLORS); + } + + /* + * Try loading an icon file from 'runtimepath'. + */ + int + mch_icon_load(HANDLE *iconp) + { + return do_in_runtimepath((char_u *)"bitmaps/vim.ico", + 0, mch_icon_load_cb, iconp); + } + + int + mch_libcall( + char_u *libname, + char_u *funcname, + char_u *argstring, // NULL when using a argint + int argint, + char_u **string_result,// NULL when using number_result + int *number_result) + { + HINSTANCE hinstLib; + MYSTRPROCSTR ProcAdd; + MYINTPROCSTR ProcAddI; + char_u *retval_str = NULL; + int retval_int = 0; + size_t len; + + BOOL fRunTimeLinkSuccess = FALSE; + + // Get a handle to the DLL module. + hinstLib = vimLoadLib((char *)libname); + + // If the handle is valid, try to get the function address. + if (hinstLib != NULL) + { + # ifdef HAVE_TRY_EXCEPT + __try + { + # endif + if (argstring != NULL) + { + // Call with string argument + ProcAdd = (MYSTRPROCSTR)GetProcAddress(hinstLib, (LPCSTR)funcname); + if ((fRunTimeLinkSuccess = (ProcAdd != NULL)) != 0) + { + if (string_result == NULL) + retval_int = ((MYSTRPROCINT)ProcAdd)((LPSTR)argstring); + else + retval_str = (char_u *)(ProcAdd)((LPSTR)argstring); + } + } + else + { + // Call with number argument + ProcAddI = (MYINTPROCSTR) GetProcAddress(hinstLib, (LPCSTR)funcname); + if ((fRunTimeLinkSuccess = (ProcAddI != NULL)) != 0) + { + if (string_result == NULL) + retval_int = ((MYINTPROCINT)ProcAddI)(argint); + else + retval_str = (char_u *)(ProcAddI)(argint); + } + } + + // Save the string before we free the library. + // Assume that a "1" result is an illegal pointer. + if (string_result == NULL) + *number_result = retval_int; + else if (retval_str != NULL + && (len = check_str_len(retval_str)) > 0) + { + *string_result = alloc(len); + if (*string_result != NULL) + mch_memmove(*string_result, retval_str, len); + } + + # ifdef HAVE_TRY_EXCEPT + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + if (GetExceptionCode() == EXCEPTION_STACK_OVERFLOW) + _resetstkoflw(); + fRunTimeLinkSuccess = 0; + } + # endif + + // Free the DLL module. + (void)FreeLibrary(hinstLib); + } + + if (!fRunTimeLinkSuccess) + { + semsg(_(e_library_call_failed_for_str), funcname); + return FAIL; + } + + return OK; + } + #endif + + /* + * Debugging helper: expose the MCH_WRITE_DUMP stuff to other modules + */ + void + DumpPutS(const char *psz UNUSED) + { + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fputs(psz, fdDump); + if (psz[strlen(psz) - 1] != '\n') + fputc('\n', fdDump); + fflush(fdDump); + } + #endif + } + + #ifdef _DEBUG + + void __cdecl + Trace( + char *pszFormat, + ...) + { + CHAR szBuff[2048]; + va_list args; + + va_start(args, pszFormat); + vsprintf(szBuff, pszFormat, args); + va_end(args); + + OutputDebugString(szBuff); + } + + #endif //_DEBUG + + #if !defined(FEAT_GUI) || defined(VIMDLL) || defined(PROTO) + extern HWND g_hWnd; // This is in os_win32.c. + + /* + * Showing the printer dialog is tricky since we have no GUI + * window to parent it. The following routines are needed to + * get the window parenting and Z-order to work properly. + */ + static void + GetConsoleHwnd(void) + { + // Skip if it's already set. + if (s_hwnd != 0) + return; + + // Window handle may have been found by init code (Windows NT only) + if (g_hWnd != 0) + { + s_hwnd = g_hWnd; + return; + } + + s_hwnd = GetConsoleWindow(); + } + + /* + * Console implementation of ":winpos". + */ + int + mch_get_winpos(int *x, int *y) + { + RECT rect; + + GetConsoleHwnd(); + GetWindowRect(s_hwnd, &rect); + *x = rect.left; + *y = rect.top; + return OK; + } + + /* + * Console implementation of ":winpos x y". + */ + void + mch_set_winpos(int x, int y) + { + GetConsoleHwnd(); + SetWindowPos(s_hwnd, NULL, x, y, 0, 0, + SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); + } + #endif + + #if (defined(FEAT_PRINTER) && !defined(FEAT_POSTSCRIPT)) || defined(PROTO) + + //================================================================= + // Win32 printer stuff + + static HFONT prt_font_handles[2][2][2]; + static PRINTDLGW prt_dlg; + static const int boldface[2] = {FW_REGULAR, FW_BOLD}; + static TEXTMETRIC prt_tm; + static int prt_line_height; + static int prt_number_width; + static int prt_left_margin; + static int prt_right_margin; + static int prt_top_margin; + static char_u szAppName[] = TEXT("VIM"); + static HWND hDlgPrint; + static int *bUserAbort = NULL; + static char_u *prt_name = NULL; + + // Defines which are also in vim.rc. + # define IDC_BOX1 400 + # define IDC_PRINTTEXT1 401 + # define IDC_PRINTTEXT2 402 + # define IDC_PROGRESS 403 + + static BOOL + vimSetDlgItemText(HWND hDlg, int nIDDlgItem, char_u *s) + { + WCHAR *wp; + BOOL ret; + + wp = enc_to_utf16(s, NULL); + if (wp == NULL) + return FALSE; + + ret = SetDlgItemTextW(hDlg, nIDDlgItem, wp); + vim_free(wp); + return ret; + } + + /* + * Convert BGR to RGB for Windows GDI calls + */ + static COLORREF + swap_me(COLORREF colorref) + { + int temp; + char *ptr = (char *)&colorref; + + temp = *(ptr); + *(ptr ) = *(ptr + 2); + *(ptr + 2) = temp; + return colorref; + } + + static INT_PTR CALLBACK + PrintDlgProc( + HWND hDlg, + UINT message, + WPARAM wParam UNUSED, + LPARAM lParam UNUSED) + { + # ifdef FEAT_GETTEXT + NONCLIENTMETRICS nm; + static HFONT hfont; + # endif + + switch (message) + { + case WM_INITDIALOG: + # ifdef FEAT_GETTEXT + nm.cbSize = sizeof(NONCLIENTMETRICS); + if (SystemParametersInfo( + SPI_GETNONCLIENTMETRICS, + sizeof(NONCLIENTMETRICS), + &nm, + 0)) + { + char buff[MAX_PATH]; + int i; + + // Translate the dialog texts + hfont = CreateFontIndirect(&nm.lfMessageFont); + for (i = IDC_PRINTTEXT1; i <= IDC_PROGRESS; i++) + { + SendDlgItemMessage(hDlg, i, WM_SETFONT, (WPARAM)hfont, 1); + if (GetDlgItemText(hDlg,i, buff, sizeof(buff))) + vimSetDlgItemText(hDlg,i, (char_u *)_(buff)); + } + SendDlgItemMessage(hDlg, IDCANCEL, + WM_SETFONT, (WPARAM)hfont, 1); + if (GetDlgItemText(hDlg,IDCANCEL, buff, sizeof(buff))) + vimSetDlgItemText(hDlg,IDCANCEL, (char_u *)_(buff)); + } + # endif + SetWindowText(hDlg, (LPCSTR)szAppName); + if (prt_name != NULL) + { + vimSetDlgItemText(hDlg, IDC_PRINTTEXT2, (char_u *)prt_name); + VIM_CLEAR(prt_name); + } + EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE, MF_GRAYED); + # if !defined(FEAT_GUI) || defined(VIMDLL) + # ifdef VIMDLL + if (!gui.in_use) + # endif + BringWindowToTop(s_hwnd); + # endif + return TRUE; + + case WM_COMMAND: + *bUserAbort = TRUE; + EnableWindow(GetParent(hDlg), TRUE); + DestroyWindow(hDlg); + hDlgPrint = NULL; + # ifdef FEAT_GETTEXT + DeleteObject(hfont); + # endif + return TRUE; + } + return FALSE; + } + + static BOOL CALLBACK + AbortProc(HDC hdcPrn UNUSED, int iCode UNUSED) + { + MSG msg; + + while (!*bUserAbort && PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (!hDlgPrint || !IsDialogMessageW(hDlgPrint, &msg)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + return !*bUserAbort; + } + + # if !defined(FEAT_GUI) || defined(VIMDLL) + + static UINT_PTR CALLBACK + PrintHookProc( + HWND hDlg, // handle to dialog box + UINT uiMsg, // message identifier + WPARAM wParam UNUSED, // message parameter + LPARAM lParam // message parameter + ) + { + HWND hwndOwner; + RECT rc, rcDlg, rcOwner; + PRINTDLGW *pPD; + + if (uiMsg != WM_INITDIALOG) + return FALSE; + + // Get the owner window and dialog box rectangles. + if ((hwndOwner = GetParent(hDlg)) == NULL) + hwndOwner = GetDesktopWindow(); + + GetWindowRect(hwndOwner, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + + // Offset the owner and dialog box rectangles so that + // right and bottom values represent the width and + // height, and then offset the owner again to discard + // space taken up by the dialog box. + + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + + // The new position is the sum of half the remaining + // space and the owner's original position. + + SetWindowPos(hDlg, + HWND_TOP, + rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2), + 0, 0, // ignores size arguments + SWP_NOSIZE); + + // tackle the printdlg copiesctrl problem + pPD = (PRINTDLGW *)lParam; + pPD->nCopies = (WORD)pPD->lCustData; + SetDlgItemInt( hDlg, edt3, pPD->nCopies, FALSE ); + // Bring the window to top + BringWindowToTop(GetParent(hDlg)); + SetForegroundWindow(hDlg); + + return FALSE; + } + # endif + + void + mch_print_cleanup(void) + { + int pifItalic; + int pifBold; + int pifUnderline; + + for (pifBold = 0; pifBold <= 1; pifBold++) + for (pifItalic = 0; pifItalic <= 1; pifItalic++) + for (pifUnderline = 0; pifUnderline <= 1; pifUnderline++) + DeleteObject(prt_font_handles[pifBold][pifItalic][pifUnderline]); + + if (prt_dlg.hDC != NULL) + DeleteDC(prt_dlg.hDC); + if (!*bUserAbort) + SendMessage(hDlgPrint, WM_COMMAND, 0, 0); + } + + static int + to_device_units(int idx, int dpi, int physsize, int offset, int def_number) + { + int ret = 0; + int u; + int nr; + + u = prt_get_unit(idx); + if (u == PRT_UNIT_NONE) + { + u = PRT_UNIT_PERC; + nr = def_number; + } + else + nr = printer_opts[idx].number; + + switch (u) + { + case PRT_UNIT_PERC: + ret = (physsize * nr) / 100; + break; + case PRT_UNIT_INCH: + ret = (nr * dpi); + break; + case PRT_UNIT_MM: + ret = (nr * 10 * dpi) / 254; + break; + case PRT_UNIT_POINT: + ret = (nr * 10 * dpi) / 720; + break; + } + + if (ret < offset) + return 0; + else + return ret - offset; + } + + static int + prt_get_cpl(void) + { + int hr; + int phyw; + int dvoff; + int rev_offset; + int dpi; + + GetTextMetrics(prt_dlg.hDC, &prt_tm); + prt_line_height = prt_tm.tmHeight + prt_tm.tmExternalLeading; + + hr = GetDeviceCaps(prt_dlg.hDC, HORZRES); + phyw = GetDeviceCaps(prt_dlg.hDC, PHYSICALWIDTH); + dvoff = GetDeviceCaps(prt_dlg.hDC, PHYSICALOFFSETX); + dpi = GetDeviceCaps(prt_dlg.hDC, LOGPIXELSX); + + rev_offset = phyw - (dvoff + hr); + + prt_left_margin = to_device_units(OPT_PRINT_LEFT, dpi, phyw, dvoff, 10); + if (prt_use_number()) + { + prt_number_width = PRINT_NUMBER_WIDTH * prt_tm.tmAveCharWidth; + prt_left_margin += prt_number_width; + } + else + prt_number_width = 0; + + prt_right_margin = hr - to_device_units(OPT_PRINT_RIGHT, dpi, phyw, + rev_offset, 5); + + return (prt_right_margin - prt_left_margin) / prt_tm.tmAveCharWidth; + } + + static int + prt_get_lpp(void) + { + int vr; + int phyw; + int dvoff; + int rev_offset; + int bottom_margin; + int dpi; + + vr = GetDeviceCaps(prt_dlg.hDC, VERTRES); + phyw = GetDeviceCaps(prt_dlg.hDC, PHYSICALHEIGHT); + dvoff = GetDeviceCaps(prt_dlg.hDC, PHYSICALOFFSETY); + dpi = GetDeviceCaps(prt_dlg.hDC, LOGPIXELSY); + + rev_offset = phyw - (dvoff + vr); + + prt_top_margin = to_device_units(OPT_PRINT_TOP, dpi, phyw, dvoff, 5); + + // adjust top margin if there is a header + prt_top_margin += prt_line_height * prt_header_height(); + + bottom_margin = vr - to_device_units(OPT_PRINT_BOT, dpi, phyw, + rev_offset, 5); + + return (bottom_margin - prt_top_margin) / prt_line_height; + } + + int + mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) + { + static HGLOBAL stored_dm = NULL; + static HGLOBAL stored_devn = NULL; + static int stored_nCopies = 1; + static int stored_nFlags = 0; + + LOGFONTW fLogFont; + int pifItalic; + int pifBold; + int pifUnderline; + + DEVMODEW *mem; + DEVNAMES *devname; + int i; + DWORD err; + + bUserAbort = &(psettings->user_abort); + CLEAR_FIELD(prt_dlg); + prt_dlg.lStructSize = sizeof(PRINTDLGW); + # if !defined(FEAT_GUI) || defined(VIMDLL) + # ifdef VIMDLL + if (!gui.in_use) + # endif + GetConsoleHwnd(); // get value of s_hwnd + # endif + prt_dlg.hwndOwner = s_hwnd; + prt_dlg.Flags = PD_NOPAGENUMS | PD_NOSELECTION | PD_RETURNDC; + if (!forceit) + { + prt_dlg.hDevMode = stored_dm; + prt_dlg.hDevNames = stored_devn; + prt_dlg.lCustData = stored_nCopies; // work around bug in print dialog + # if !defined(FEAT_GUI) || defined(VIMDLL) + # ifdef VIMDLL + if (!gui.in_use) + # endif + { + /* + * Use hook to prevent console window being sent to back + */ + prt_dlg.lpfnPrintHook = PrintHookProc; + prt_dlg.Flags |= PD_ENABLEPRINTHOOK; + } + # endif + prt_dlg.Flags |= stored_nFlags; + } + + /* + * If bang present, return default printer setup with no dialog + * never show dialog if we are running over telnet + */ + if (forceit + # if !defined(FEAT_GUI) || defined(VIMDLL) + # ifdef VIMDLL + || (!gui.in_use && !term_console) + # else + || !term_console + # endif + # endif + ) + { + prt_dlg.Flags |= PD_RETURNDEFAULT; + /* + * MSDN suggests setting the first parameter to WINSPOOL for + * NT, but NULL appears to work just as well. + */ + if (*p_pdev != NUL) + prt_dlg.hDC = CreateDC(NULL, (LPCSTR)p_pdev, NULL, NULL); + else + { + prt_dlg.Flags |= PD_RETURNDEFAULT; + if (PrintDlgW(&prt_dlg) == 0) + goto init_fail_dlg; + } + } + else if (PrintDlgW(&prt_dlg) == 0) + goto init_fail_dlg; + else + { + /* + * keep the previous driver context + */ + stored_dm = prt_dlg.hDevMode; + stored_devn = prt_dlg.hDevNames; + stored_nFlags = prt_dlg.Flags; + stored_nCopies = prt_dlg.nCopies; + } + + if (prt_dlg.hDC == NULL) + { + emsg(_(e_printer_selection_failed)); + mch_print_cleanup(); + return FALSE; + } + + // Not all printer drivers report the support of color (or grey) in the + // same way. Let's set has_color if there appears to be some way to print + // more than B&W. + i = GetDeviceCaps(prt_dlg.hDC, NUMCOLORS); + psettings->has_color = (GetDeviceCaps(prt_dlg.hDC, BITSPIXEL) > 1 + || GetDeviceCaps(prt_dlg.hDC, PLANES) > 1 + || i > 2 || i == -1); + + // Ensure all font styles are baseline aligned + SetTextAlign(prt_dlg.hDC, TA_BASELINE|TA_LEFT); + + /* + * On some windows systems the nCopies parameter is not + * passed back correctly. It must be retrieved from the + * hDevMode struct. + */ + mem = (DEVMODEW *)GlobalLock(prt_dlg.hDevMode); + if (mem != NULL) + { + if (mem->dmCopies != 1) + stored_nCopies = mem->dmCopies; + if ((mem->dmFields & DM_DUPLEX) && (mem->dmDuplex & ~DMDUP_SIMPLEX)) + psettings->duplex = TRUE; + if ((mem->dmFields & DM_COLOR) && (mem->dmColor & DMCOLOR_COLOR)) + psettings->has_color = TRUE; + } + GlobalUnlock(prt_dlg.hDevMode); + + devname = (DEVNAMES *)GlobalLock(prt_dlg.hDevNames); + if (devname != 0) + { + WCHAR *wprinter_name = (WCHAR *)devname + devname->wDeviceOffset; + WCHAR *wport_name = (WCHAR *)devname + devname->wOutputOffset; + char_u *text = (char_u *)_("to %s on %s"); + char_u *printer_name = utf16_to_enc(wprinter_name, NULL); + char_u *port_name = utf16_to_enc(wport_name, NULL); + + if (printer_name != NULL && port_name != NULL) + prt_name = alloc(STRLEN(printer_name) + + STRLEN(port_name) + STRLEN(text)); + if (prt_name != NULL) + wsprintf((char *)prt_name, (const char *)text, + printer_name, port_name); + vim_free(printer_name); + vim_free(port_name); + } + GlobalUnlock(prt_dlg.hDevNames); + + /* + * Initialise the font according to 'printfont' + */ + CLEAR_FIELD(fLogFont); + if (get_logfont(&fLogFont, p_pfn, prt_dlg.hDC, TRUE) == FAIL) + { + semsg(_(e_unknown_printer_font_str), p_pfn); + mch_print_cleanup(); + return FALSE; + } + + for (pifBold = 0; pifBold <= 1; pifBold++) + for (pifItalic = 0; pifItalic <= 1; pifItalic++) + for (pifUnderline = 0; pifUnderline <= 1; pifUnderline++) + { + fLogFont.lfWeight = boldface[pifBold]; + fLogFont.lfItalic = pifItalic; + fLogFont.lfUnderline = pifUnderline; + prt_font_handles[pifBold][pifItalic][pifUnderline] + = CreateFontIndirectW(&fLogFont); + } + + SetBkMode(prt_dlg.hDC, OPAQUE); + SelectObject(prt_dlg.hDC, prt_font_handles[0][0][0]); + + /* + * Fill in the settings struct + */ + psettings->chars_per_line = prt_get_cpl(); + psettings->lines_per_page = prt_get_lpp(); + if (prt_dlg.Flags & PD_USEDEVMODECOPIESANDCOLLATE) + { + psettings->n_collated_copies = (prt_dlg.Flags & PD_COLLATE) + ? prt_dlg.nCopies : 1; + psettings->n_uncollated_copies = (prt_dlg.Flags & PD_COLLATE) + ? 1 : prt_dlg.nCopies; + + if (psettings->n_collated_copies == 0) + psettings->n_collated_copies = 1; + + if (psettings->n_uncollated_copies == 0) + psettings->n_uncollated_copies = 1; + } + else + { + psettings->n_collated_copies = 1; + psettings->n_uncollated_copies = 1; + } + + psettings->jobname = jobname; + + return TRUE; + + init_fail_dlg: + err = CommDlgExtendedError(); + if (err) + { + char_u *buf; + + // I suspect FormatMessage() doesn't work for values returned by + // CommDlgExtendedError(). What does? + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, 0, (LPTSTR)(&buf), 0, NULL); + semsg(_(e_print_error_str), + buf == NULL ? (char_u *)_("Unknown") : buf); + LocalFree((LPVOID)(buf)); + } + else + msg_clr_eos(); // Maybe canceled + + mch_print_cleanup(); + return FALSE; + } + + + int + mch_print_begin(prt_settings_T *psettings) + { + int ret = 0; + char szBuffer[300]; + WCHAR *wp; + + hDlgPrint = CreateDialog(g_hinst, TEXT("PrintDlgBox"), + prt_dlg.hwndOwner, PrintDlgProc); + SetAbortProc(prt_dlg.hDC, AbortProc); + wsprintf(szBuffer, _("Printing '%s'"), gettail(psettings->jobname)); + vimSetDlgItemText(hDlgPrint, IDC_PRINTTEXT1, (char_u *)szBuffer); + + wp = enc_to_utf16(psettings->jobname, NULL); + if (wp != NULL) + { + DOCINFOW di; + + CLEAR_FIELD(di); + di.cbSize = sizeof(di); + di.lpszDocName = wp; + ret = StartDocW(prt_dlg.hDC, &di); + vim_free(wp); + } + + # ifdef FEAT_GUI + // Give focus back to main window (when using MDI). + # ifdef VIMDLL + if (gui.in_use) + # endif + SetFocus(s_hwnd); + # endif + + return (ret > 0); + } + + void + mch_print_end(prt_settings_T *psettings UNUSED) + { + EndDoc(prt_dlg.hDC); + if (!*bUserAbort) + SendMessage(hDlgPrint, WM_COMMAND, 0, 0); + } + + int + mch_print_end_page(void) + { + return (EndPage(prt_dlg.hDC) > 0); + } + + int + mch_print_begin_page(char_u *msg) + { + if (msg != NULL) + vimSetDlgItemText(hDlgPrint, IDC_PROGRESS, msg); + return (StartPage(prt_dlg.hDC) > 0); + } + + int + mch_print_blank_page(void) + { + return (mch_print_begin_page(NULL) ? (mch_print_end_page()) : FALSE); + } + + static int prt_pos_x = 0; + static int prt_pos_y = 0; + + void + mch_print_start_line(int margin, int page_line) + { + if (margin) + prt_pos_x = -prt_number_width; + else + prt_pos_x = 0; + prt_pos_y = page_line * prt_line_height + + prt_tm.tmAscent + prt_tm.tmExternalLeading; + } + + int + mch_print_text_out(char_u *p, int len) + { + SIZE sz; + WCHAR *wp; + int wlen = len; + int ret = FALSE; + + wp = enc_to_utf16(p, &wlen); + if (wp == NULL) + return FALSE; + + TextOutW(prt_dlg.hDC, prt_pos_x + prt_left_margin, + prt_pos_y + prt_top_margin, wp, wlen); + GetTextExtentPoint32W(prt_dlg.hDC, wp, wlen, &sz); + vim_free(wp); + prt_pos_x += (sz.cx - prt_tm.tmOverhang); + // This is wrong when printing spaces for a TAB. + if (p[len] != NUL) + { + wlen = mb_ptr2len(p + len); + wp = enc_to_utf16(p + len, &wlen); + if (wp != NULL) + { + GetTextExtentPoint32W(prt_dlg.hDC, wp, 1, &sz); + ret = (prt_pos_x + prt_left_margin + sz.cx > prt_right_margin); + vim_free(wp); + } + } + return ret; + } + + void + mch_print_set_font(int iBold, int iItalic, int iUnderline) + { + SelectObject(prt_dlg.hDC, prt_font_handles[iBold][iItalic][iUnderline]); + } + + void + mch_print_set_bg(long_u bgcol) + { + SetBkColor(prt_dlg.hDC, GetNearestColor(prt_dlg.hDC, + swap_me((COLORREF)bgcol))); + /* + * With a white background we can draw characters transparent, which is + * good for italic characters that overlap to the next char cell. + */ + if (bgcol == 0xffffffUL) + SetBkMode(prt_dlg.hDC, TRANSPARENT); + else + SetBkMode(prt_dlg.hDC, OPAQUE); + } + + void + mch_print_set_fg(long_u fgcol) + { + SetTextColor(prt_dlg.hDC, GetNearestColor(prt_dlg.hDC, + swap_me((COLORREF)fgcol))); + } + + #endif // FEAT_PRINTER && !FEAT_POSTSCRIPT + + + + #if defined(FEAT_SHORTCUT) || defined(PROTO) + # ifndef PROTO + # include + # endif + + # define is_path_sep(c) ((c) == L'\\' || (c) == L'/') + + static int + is_reparse_point_included(LPCWSTR fname) + { + LPCWSTR p = fname, q; + WCHAR buf[MAX_PATH]; + DWORD attr; + + if (SAFE_isalpha(p[0]) && p[1] == L':' && is_path_sep(p[2])) + p += 3; + else if (is_path_sep(p[0]) && is_path_sep(p[1])) + p += 2; + + while (*p != L'\0') + { + q = wcspbrk(p, L"\\/"); + if (q == NULL) + p = q = fname + wcslen(fname); + else + p = q + 1; + if (q - fname >= MAX_PATH) + return FALSE; + wcsncpy(buf, fname, q - fname); + buf[q - fname] = L'\0'; + attr = GetFileAttributesW(buf); + if (attr != INVALID_FILE_ATTRIBUTES + && (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) + return TRUE; + } + return FALSE; + } + + /* + * Return the resolved file path, NULL if "fname" is an AppExecLink reparse + * point, already fully resolved, or it doesn't exists. + */ + char_u * + resolve_reparse_point(char_u *fname) + { + HANDLE h = INVALID_HANDLE_VALUE; + DWORD size; + WCHAR *p, *wp; + char_u *rfname = NULL; + WCHAR *buff = NULL; + + p = enc_to_utf16(fname, NULL); + if (p == NULL) + goto fail; + + if (!is_reparse_point_included(p)) + { + vim_free(p); + goto fail; + } + + h = CreateFileW(p, 0, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + vim_free(p); + + if (h == INVALID_HANDLE_VALUE) + goto fail; + + size = GetFinalPathNameByHandleW(h, NULL, 0, 0); + if (size == 0) + goto fail; + buff = ALLOC_MULT(WCHAR, size); + if (buff == NULL) + goto fail; + if (GetFinalPathNameByHandleW(h, buff, size, 0) == 0) + goto fail; + + if (wcsncmp(buff, L"\\\\?\\UNC\\", 8) == 0) + { + buff[6] = L'\\'; + wp = buff + 6; + } + else if (wcsncmp(buff, L"\\\\?\\", 4) == 0) + wp = buff + 4; + else + wp = buff; + + rfname = utf16_to_enc(wp, NULL); + + fail: + if (h != INVALID_HANDLE_VALUE) + CloseHandle(h); + if (buff != NULL) + vim_free(buff); + + return rfname; + } + + /* + * When "fname" is the name of a shortcut (*.lnk) resolve the file it points + * to and return that name in allocated memory. + * Otherwise NULL is returned. + */ + static char_u * + resolve_shortcut(char_u *fname) + { + HRESULT hr; + IShellLink *psl = NULL; + IPersistFile *ppf = NULL; + OLECHAR wsz[MAX_PATH]; + char_u *rfname = NULL; + int len; + IShellLinkW *pslw = NULL; + WIN32_FIND_DATAW ffdw; // we get those free of charge + + // Check if the file name ends in ".lnk". Avoid calling + // CoCreateInstance(), it's quite slow. + if (fname == NULL) + return rfname; + len = (int)STRLEN(fname); + if (len <= 4 || STRNICMP(fname + len - 4, ".lnk", 4) != 0) + return rfname; + + CoInitialize(NULL); + + // create a link manager object and request its interface + hr = CoCreateInstance( + &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + &IID_IShellLinkW, (void**)&pslw); + if (hr == S_OK) + { + WCHAR *p = enc_to_utf16(fname, NULL); + + if (p != NULL) + { + // Get a pointer to the IPersistFile interface. + hr = pslw->lpVtbl->QueryInterface( + pslw, &IID_IPersistFile, (void**)&ppf); + if (hr != S_OK) + goto shortcut_errorw; + + // "load" the name and resolve the link + hr = ppf->lpVtbl->Load(ppf, p, STGM_READ); + if (hr != S_OK) + goto shortcut_errorw; + # if 0 // This makes Vim wait a long time if the target does not exist. + hr = pslw->lpVtbl->Resolve(pslw, NULL, SLR_NO_UI); + if (hr != S_OK) + goto shortcut_errorw; + # endif + + // Get the path to the link target. + ZeroMemory(wsz, MAX_PATH * sizeof(WCHAR)); + hr = pslw->lpVtbl->GetPath(pslw, wsz, MAX_PATH, &ffdw, 0); + if (hr == S_OK && wsz[0] != NUL) + rfname = utf16_to_enc(wsz, NULL); + + shortcut_errorw: + vim_free(p); + } + } + + // Release all interface pointers (both belong to the same object) + if (ppf != NULL) + ppf->lpVtbl->Release(ppf); + if (psl != NULL) + psl->lpVtbl->Release(psl); + if (pslw != NULL) + pslw->lpVtbl->Release(pslw); + + CoUninitialize(); + return rfname; + } + + char_u * + mch_resolve_path(char_u *fname, int reparse_point) + { + char_u *path = resolve_shortcut(fname); + + if (path == NULL && reparse_point) + path = resolve_reparse_point(fname); + return path; + } + #endif + + #if (defined(FEAT_EVAL) && (!defined(FEAT_GUI) || defined(VIMDLL))) || defined(PROTO) + /* + * Bring ourselves to the foreground. Does work if the OS doesn't allow it. + */ + void + win32_set_foreground(void) + { + GetConsoleHwnd(); // get value of s_hwnd + if (s_hwnd != 0) + SetForegroundWindow(s_hwnd); + } + #endif + + #if defined(FEAT_CLIENTSERVER) || defined(PROTO) + /* + * Client-server code for Vim + * + * Originally written by Paul Moore + */ + + // In order to handle inter-process messages, we need to have a window. But + // the functions in this module can be called before the main GUI window is + // created (and may also be called in the console version, where there is no + // GUI window at all). + // + // So we create a hidden window, and arrange to destroy it on exit. + HWND message_window = 0; // window that's handling messages + + # define VIM_CLASSNAME "VIM_MESSAGES" + # define VIM_CLASSNAME_LEN (sizeof(VIM_CLASSNAME) - 1) + + // Timeout for sending a message to another Vim instance. Normally this works + // instantly, but it may hang when the other Vim instance is halted. + # define SENDMESSAGE_TIMEOUT (5 * 1000) + + // Communication is via WM_COPYDATA messages. The message type is sent in + // the dwData parameter. Types are defined here. + # define COPYDATA_KEYS 0 + # define COPYDATA_REPLY 1 + # define COPYDATA_EXPR 10 + # define COPYDATA_RESULT 11 + # define COPYDATA_ERROR_RESULT 12 + # define COPYDATA_ENCODING 20 + + // This is a structure containing a server HWND and its name. + struct server_id + { + HWND hwnd; + char_u *name; + }; + + // Last received 'encoding' that the client uses. + static char_u *client_enc = NULL; + + /* + * Tell the other side what encoding we are using. + * Return -1 if timeout happens. Other errors are ignored. + */ + static int + serverSendEnc(HWND target) + { + COPYDATASTRUCT data; + + data.dwData = COPYDATA_ENCODING; + data.cbData = (DWORD)STRLEN(p_enc) + 1; + data.lpData = p_enc; + if (SendMessageTimeout(target, WM_COPYDATA, + (WPARAM)message_window, (LPARAM)&data, + SMTO_ABORTIFHUNG, SENDMESSAGE_TIMEOUT, NULL) == 0) + return -1; + return 0; + } + + /* + * Clean up on exit. This destroys the hidden message window. + */ + static void + CleanUpMessaging(void) + { + if (message_window == 0) + return; + + DestroyWindow(message_window); + message_window = 0; + } + + static int save_reply(HWND server, char_u *reply, int expr); + + /* + * The window procedure for the hidden message window. + * It handles callback messages and notifications from servers. + * In order to process these messages, it is necessary to run a + * message loop. Code which may run before the main message loop + * is started (in the GUI) is careful to pump messages when it needs + * to. Features which require message delivery during normal use will + * not work in the console version - this basically means those + * features which allow Vim to act as a server, rather than a client. + */ + static LRESULT CALLBACK + Messaging_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) + { + if (msg == WM_COPYDATA) + { + // This is a message from another Vim. The dwData member of the + // COPYDATASTRUCT determines the type of message: + // COPYDATA_ENCODING: + // The encoding that the client uses. Following messages will + // use this encoding, convert if needed. + // COPYDATA_KEYS: + // A key sequence. We are a server, and a client wants these keys + // adding to the input queue. + // COPYDATA_REPLY: + // A reply. We are a client, and a server has sent this message + // in response to a request. (server2client()) + // COPYDATA_EXPR: + // An expression. We are a server, and a client wants us to + // evaluate this expression. + // COPYDATA_RESULT: + // A reply. We are a client, and a server has sent this message + // in response to a COPYDATA_EXPR. + // COPYDATA_ERROR_RESULT: + // A reply. We are a client, and a server has sent this message + // in response to a COPYDATA_EXPR that failed to evaluate. + COPYDATASTRUCT *data = (COPYDATASTRUCT*)lParam; + HWND sender = (HWND)wParam; + COPYDATASTRUCT reply; + char_u *res; + int retval; + DWORD_PTR dwret = 0; + char_u *str; + char_u *tofree; + + switch (data->dwData) + { + case COPYDATA_ENCODING: + // Remember the encoding that the client uses. + vim_free(client_enc); + client_enc = enc_canonize((char_u *)data->lpData); + return 1; + + case COPYDATA_KEYS: + // Remember who sent this, for + clientWindow = sender; + + // Add the received keys to the input buffer. The loop waiting + // for the user to do something should check the input buffer. + str = serverConvert(client_enc, (char_u *)data->lpData, &tofree); + server_to_input_buf(str); + vim_free(tofree); + + # ifdef FEAT_GUI + // Wake up the main GUI loop. + # ifdef VIMDLL + if (gui.in_use) + # endif + if (s_hwnd != 0) + PostMessage(s_hwnd, WM_NULL, 0, 0); + # endif + return 1; + + case COPYDATA_EXPR: + // Remember who sent this, for + clientWindow = sender; + + str = serverConvert(client_enc, (char_u *)data->lpData, &tofree); + res = eval_client_expr_to_string(str); + + if (res == NULL) + { + char *err = _(e_invalid_expression_received); + size_t len = STRLEN(str) + STRLEN(err) + 5; + + res = alloc(len); + if (res != NULL) + vim_snprintf((char *)res, len, "%s: \"%s\"", err, str); + reply.dwData = COPYDATA_ERROR_RESULT; + } + else + reply.dwData = COPYDATA_RESULT; + reply.lpData = res; + reply.cbData = (DWORD)STRLEN(res) + 1; + + if (serverSendEnc(sender) < 0) + retval = -1; + else + { + if (SendMessageTimeout(sender, WM_COPYDATA, + (WPARAM)message_window, (LPARAM)&reply, + SMTO_ABORTIFHUNG, SENDMESSAGE_TIMEOUT, &dwret) == 0) + retval = -1; + else + retval = (int)dwret; + } + vim_free(tofree); + vim_free(res); + return retval; + + case COPYDATA_REPLY: + case COPYDATA_RESULT: + case COPYDATA_ERROR_RESULT: + if (data->lpData != NULL) + { + str = serverConvert(client_enc, (char_u *)data->lpData, + &tofree); + if (tofree == NULL) + str = vim_strsave(str); + if (save_reply(sender, str, + (data->dwData == COPYDATA_REPLY ? 0 : + (data->dwData == COPYDATA_RESULT ? 1 : + 2))) == FAIL) + vim_free(str); + else if (data->dwData == COPYDATA_REPLY) + { + char_u winstr[30]; + + sprintf((char *)winstr, PRINTF_HEX_LONG_U, (long_u)sender); + apply_autocmds(EVENT_REMOTEREPLY, winstr, str, + TRUE, curbuf); + } + } + return 1; + } + + return 0; + } + + else if (msg == WM_ACTIVATE && wParam == WA_ACTIVE) + { + // When the message window is activated (brought to the foreground), + // this actually applies to the text window. + # if !defined(FEAT_GUI) || defined(VIMDLL) + # ifdef VIMDLL + if (!gui.in_use) + # endif + GetConsoleHwnd(); // get value of s_hwnd + # endif + if (s_hwnd != 0) + { + SetForegroundWindow(s_hwnd); + return 0; + } + } + + return DefWindowProc(hwnd, msg, wParam, lParam); + } + + /* + * Initialise the message handling process. This involves creating a window + * to handle messages - the window will not be visible. + */ + void + serverInitMessaging(void) + { + WNDCLASS wndclass; + + // Clean up on exit + atexit(CleanUpMessaging); + + // Register a window class - we only really care + // about the window procedure + wndclass.style = 0; + wndclass.lpfnWndProc = Messaging_WndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = g_hinst; + wndclass.hIcon = NULL; + wndclass.hCursor = NULL; + wndclass.hbrBackground = NULL; + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = VIM_CLASSNAME; + RegisterClass(&wndclass); + + // Create the message window. It will be hidden, so the details don't + // matter. Don't use WS_OVERLAPPEDWINDOW, it will make a shortcut remove + // focus from gvim. + message_window = CreateWindow(VIM_CLASSNAME, "", + WS_POPUPWINDOW | WS_CAPTION, + CW_USEDEFAULT, CW_USEDEFAULT, + 100, 100, NULL, NULL, + g_hinst, NULL); + } + + // Used by serverSendToVim() to find an alternate server name. + static char_u *altname_buf_ptr = NULL; + + /* + * Get the title of the window "hwnd", which is the Vim server name, in + * "name[namelen]" and return the length. + * Returns zero if window "hwnd" is not a Vim server. + */ + static int + getVimServerName(HWND hwnd, char *name, int namelen) + { + int len; + char buffer[VIM_CLASSNAME_LEN + 1]; + + // Ignore windows which aren't Vim message windows + len = GetClassName(hwnd, buffer, sizeof(buffer)); + if (len != VIM_CLASSNAME_LEN || STRCMP(buffer, VIM_CLASSNAME) != 0) + return 0; + + // Get the title of the window + return GetWindowText(hwnd, name, namelen); + } + + static BOOL CALLBACK + enumWindowsGetServer(HWND hwnd, LPARAM lparam) + { + struct server_id *id = (struct server_id *)lparam; + char server[MAX_PATH]; + + // Get the title of the window + if (getVimServerName(hwnd, server, sizeof(server)) == 0) + return TRUE; + + // If this is the server we're looking for, return its HWND + if (STRICMP(server, id->name) == 0) + { + id->hwnd = hwnd; + return FALSE; + } + + // If we are looking for an alternate server, remember this name. + if (altname_buf_ptr != NULL + && STRNICMP(server, id->name, STRLEN(id->name)) == 0 + && vim_isdigit(server[STRLEN(id->name)])) + { + STRCPY(altname_buf_ptr, server); + altname_buf_ptr = NULL; // don't use another name + } + + // Otherwise, keep looking + return TRUE; + } + + static BOOL CALLBACK + enumWindowsGetNames(HWND hwnd, LPARAM lparam) + { + garray_T *ga = (garray_T *)lparam; + char server[MAX_PATH]; + + // Get the title of the window + if (getVimServerName(hwnd, server, sizeof(server)) == 0) + return TRUE; + + // Add the name to the list + ga_concat(ga, (char_u *)server); + ga_concat(ga, (char_u *)"\n"); + return TRUE; + } + + struct enum_windows_s + { + WNDENUMPROC lpEnumFunc; + LPARAM lParam; + }; + + static BOOL CALLBACK + enum_windows_child(HWND hwnd, LPARAM lParam) + { + struct enum_windows_s *ew = (struct enum_windows_s *)lParam; + + return (ew->lpEnumFunc)(hwnd, ew->lParam); + } + + static BOOL CALLBACK + enum_windows_toplevel(HWND hwnd, LPARAM lParam) + { + struct enum_windows_s *ew = (struct enum_windows_s *)lParam; + + if ((ew->lpEnumFunc)(hwnd, ew->lParam)) + return TRUE; + return EnumChildWindows(hwnd, enum_windows_child, lParam); + } + + /* + * Enumerate all windows including children. + */ + static BOOL + enum_windows(WNDENUMPROC lpEnumFunc, LPARAM lParam) + { + struct enum_windows_s ew; + + ew.lpEnumFunc = lpEnumFunc; + ew.lParam = lParam; + return EnumWindows(enum_windows_toplevel, (LPARAM)&ew); + } + + static HWND + findServer(char_u *name) + { + struct server_id id; + + id.name = name; + id.hwnd = 0; + + enum_windows(enumWindowsGetServer, (LPARAM)(&id)); + + return id.hwnd; + } + + void + serverSetName(char_u *name) + { + char_u *ok_name; + HWND hwnd = 0; + int i = 0; + char_u *p; + + // Leave enough space for a 9-digit suffix to ensure uniqueness! + ok_name = alloc(STRLEN(name) + 10); + + STRCPY(ok_name, name); + p = ok_name + STRLEN(name); + + for (;;) + { + // This is inefficient - we're doing an EnumWindows loop for each + // possible name. It would be better to grab all names in one go, + // and scan the list each time... + hwnd = findServer(ok_name); + if (hwnd == 0) + break; + + ++i; + if (i >= 1000) + break; + + sprintf((char *)p, "%d", i); + } + + if (hwnd != 0) + vim_free(ok_name); + else + { + // Remember the name + serverName = ok_name; + need_maketitle = TRUE; // update Vim window title later + + // Update the message window title + SetWindowText(message_window, (LPCSTR)ok_name); + + # ifdef FEAT_EVAL + // Set the servername variable + set_vim_var_string(VV_SEND_SERVER, serverName, -1); + # endif + } + } + + char_u * + serverGetVimNames(void) + { + garray_T ga; + + ga_init2(&ga, 1, 100); + + enum_windows(enumWindowsGetNames, (LPARAM)(&ga)); + ga_append(&ga, NUL); + + return ga.ga_data; + } + + int + serverSendReply( + char_u *name, // Where to send. + char_u *reply) // What to send. + { + HWND target; + COPYDATASTRUCT data; + long_u n = 0; + DWORD_PTR dwret = 0; + + // The "name" argument is a magic cookie obtained from expand(""). + // It should be of the form 0xXXXXX - i.e. a C hex literal, which is the + // value of the client's message window HWND. + sscanf((char *)name, SCANF_HEX_LONG_U, &n); + if (n == 0) + return -1; + + target = (HWND)n; + if (!IsWindow(target)) + return -1; + + data.dwData = COPYDATA_REPLY; + data.cbData = (DWORD)STRLEN(reply) + 1; + data.lpData = reply; + + if (serverSendEnc(target) < 0) + return -1; + if (SendMessageTimeout(target, WM_COPYDATA, + (WPARAM)message_window, (LPARAM)&data, + SMTO_ABORTIFHUNG, SENDMESSAGE_TIMEOUT, &dwret) == 0) + return -1; + return dwret ? 0 : -1; + } + + int + serverSendToVim( + char_u *name, // Where to send. + char_u *cmd, // What to send. + char_u **result, // Result of eval'ed expression + void *ptarget, // HWND of server + int asExpr, // Expression or keys? + int timeout, // timeout in seconds or zero + int silent) // don't complain about no server + { + HWND target; + COPYDATASTRUCT data; + char_u *retval = NULL; + int retcode = 0; + DWORD_PTR dwret = 0; + char_u altname_buf[MAX_PATH]; + + // Execute locally if no display or target is ourselves + if (serverName != NULL && STRICMP(name, serverName) == 0) + return sendToLocalVim(cmd, asExpr, result); + + // If the server name does not end in a digit then we look for an + // alternate name. e.g. when "name" is GVIM then we may find GVIM2. + if (STRLEN(name) > 1 && !vim_isdigit(name[STRLEN(name) - 1])) + altname_buf_ptr = altname_buf; + altname_buf[0] = NUL; + target = findServer(name); + altname_buf_ptr = NULL; + if (target == 0 && altname_buf[0] != NUL) + // Use another server name we found. + target = findServer(altname_buf); + + if (target == 0) + { + if (!silent) + semsg(_(e_no_registered_server_named_str), name); + return -1; + } + + if (ptarget) + *(HWND *)ptarget = target; + + data.dwData = asExpr ? COPYDATA_EXPR : COPYDATA_KEYS; + data.cbData = (DWORD)STRLEN(cmd) + 1; + data.lpData = cmd; + + if (serverSendEnc(target) < 0) + return -1; + if (SendMessageTimeout(target, WM_COPYDATA, + (WPARAM)message_window, (LPARAM)&data, + SMTO_ABORTIFHUNG, SENDMESSAGE_TIMEOUT, &dwret) == 0) + return -1; + if (dwret == 0) + return -1; + + if (asExpr) + retval = serverGetReply(target, &retcode, TRUE, TRUE, timeout); + + if (result == NULL) + vim_free(retval); + else + *result = retval; // Caller assumes responsibility for freeing + + return retcode; + } + + /* + * Bring the server to the foreground. + */ + void + serverForeground(char_u *name) + { + HWND target = findServer(name); + + if (target != 0) + SetForegroundWindow(target); + } + + // Replies from server need to be stored until the client picks them up via + // remote_read(). So we maintain a list of server-id/reply pairs. + // Note that there could be multiple replies from one server pending if the + // client is slow picking them up. + // We just store the replies in a simple list. When we remove an entry, we + // move list entries down to fill the gap. + // The server ID is simply the HWND. + typedef struct + { + HWND server; // server window + char_u *reply; // reply string + int expr_result; // 0 for REPLY, 1 for RESULT 2 for error + } reply_T; + + static garray_T reply_list = {0, 0, sizeof(reply_T), 5, 0}; + + # define REPLY_ITEM(i) ((reply_T *)(reply_list.ga_data) + (i)) + # define REPLY_COUNT (reply_list.ga_len) + + // Flag which is used to wait for a reply + static int reply_received = 0; + + /* + * Store a reply. "reply" must be allocated memory (or NULL). + */ + static int + save_reply(HWND server, char_u *reply, int expr) + { + reply_T *rep; + + if (ga_grow(&reply_list, 1) == FAIL) + return FAIL; + + rep = REPLY_ITEM(REPLY_COUNT); + rep->server = server; + rep->reply = reply; + rep->expr_result = expr; + if (rep->reply == NULL) + return FAIL; + + ++REPLY_COUNT; + reply_received = 1; + return OK; + } + + /* + * Get a reply from server "server". + * When "expr_res" is non NULL, get the result of an expression, otherwise a + * server2client() message. + * When non NULL, point to return code. 0 => OK, -1 => ERROR + * If "remove" is TRUE, consume the message, the caller must free it then. + * if "wait" is TRUE block until a message arrives (or the server exits). + */ + char_u * + serverGetReply(HWND server, int *expr_res, int remove, int wait, int timeout) + { + int i; + char_u *reply; + reply_T *rep; + int did_process = FALSE; + time_t start; + time_t now; + + // When waiting, loop until the message waiting for is received. + time(&start); + for (;;) + { + // Reset this here, in case a message arrives while we are going + // through the already received messages. + reply_received = 0; + + for (i = 0; i < REPLY_COUNT; ++i) + { + rep = REPLY_ITEM(i); + if (rep->server == server + && ((rep->expr_result != 0) == (expr_res != NULL))) + { + // Save the values we've found for later + reply = rep->reply; + if (expr_res != NULL) + *expr_res = rep->expr_result == 1 ? 0 : -1; + + if (remove) + { + // Move the rest of the list down to fill the gap + mch_memmove(rep, rep + 1, + (REPLY_COUNT - i - 1) * sizeof(reply_T)); + --REPLY_COUNT; + } + + // Return the reply to the caller, who takes on responsibility + // for freeing it if "remove" is TRUE. + return reply; + } + } + + // If we got here, we didn't find a reply. Return immediately if the + // "wait" parameter isn't set. + if (!wait) + { + // Process pending messages once. Without this, looping on + // remote_peek() would never get the reply. + if (!did_process) + { + did_process = TRUE; + serverProcessPendingMessages(); + continue; + } + break; + } + + // We need to wait for a reply. Enter a message loop until the + // "reply_received" flag gets set. + + // Loop until we receive a reply + while (reply_received == 0) + { + # ifdef FEAT_TIMERS + // TODO: use the return value to decide how long to wait. + check_due_timer(); + # endif + time(&now); + if (timeout > 0 && (now - start) >= timeout) + break; + + // Wait for a SendMessage() call to us. This could be the reply + // we are waiting for. Use a timeout of a second, to catch the + // situation that the server died unexpectedly. + MsgWaitForMultipleObjects(0, NULL, TRUE, 1000, QS_ALLINPUT); + + // If the server has died, give up + if (!IsWindow(server)) + return NULL; + + serverProcessPendingMessages(); + } + } + + return NULL; + } + + /* + * Process any messages in the Windows message queue. + */ + void + serverProcessPendingMessages(void) + { + MSG msg; + + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + + #endif // FEAT_CLIENTSERVER + + #if defined(FEAT_GUI) || (defined(FEAT_PRINTER) && !defined(FEAT_POSTSCRIPT)) \ + || defined(PROTO) + + struct charset_pair + { + char *name; + BYTE charset; + }; + + static struct charset_pair + charset_pairs[] = + { + {"ANSI", ANSI_CHARSET}, + {"CHINESEBIG5", CHINESEBIG5_CHARSET}, + {"DEFAULT", DEFAULT_CHARSET}, + {"HANGEUL", HANGEUL_CHARSET}, + {"OEM", OEM_CHARSET}, + {"SHIFTJIS", SHIFTJIS_CHARSET}, + {"SYMBOL", SYMBOL_CHARSET}, + {"ARABIC", ARABIC_CHARSET}, + {"BALTIC", BALTIC_CHARSET}, + {"EASTEUROPE", EASTEUROPE_CHARSET}, + {"GB2312", GB2312_CHARSET}, + {"GREEK", GREEK_CHARSET}, + {"HEBREW", HEBREW_CHARSET}, + {"JOHAB", JOHAB_CHARSET}, + {"MAC", MAC_CHARSET}, + {"RUSSIAN", RUSSIAN_CHARSET}, + {"THAI", THAI_CHARSET}, + {"TURKISH", TURKISH_CHARSET}, + # ifdef VIETNAMESE_CHARSET + {"VIETNAMESE", VIETNAMESE_CHARSET}, + # endif + {NULL, 0} + }; + + struct quality_pair + { + char *name; + DWORD quality; + }; + + static struct quality_pair + quality_pairs[] = { + # ifdef CLEARTYPE_QUALITY + {"CLEARTYPE", CLEARTYPE_QUALITY}, + # endif + # ifdef ANTIALIASED_QUALITY + {"ANTIALIASED", ANTIALIASED_QUALITY}, + # endif + # ifdef NONANTIALIASED_QUALITY + {"NONANTIALIASED", NONANTIALIASED_QUALITY}, + # endif + # ifdef PROOF_QUALITY + {"PROOF", PROOF_QUALITY}, + # endif + # ifdef DRAFT_QUALITY + {"DRAFT", DRAFT_QUALITY}, + # endif + {"DEFAULT", DEFAULT_QUALITY}, + {NULL, 0} + }; + + /* + * Convert a charset ID to a name. + * Return NULL when not recognized. + */ + char * + charset_id2name(int id) + { + struct charset_pair *cp; + + for (cp = charset_pairs; cp->name != NULL; ++cp) + if ((BYTE)id == cp->charset) + break; + return cp->name; + } + + /* + * Convert a quality ID to a name. + * Return NULL when not recognized. + */ + char * + quality_id2name(DWORD id) + { + struct quality_pair *qp; + + for (qp = quality_pairs; qp->name != NULL; ++qp) + if (id == qp->quality) + break; + return qp->name; + } + + // The default font height in 100% scaling (96dpi). + // (-12 in 96dpi equates to roughly 9pt) + #define DEFAULT_FONT_HEIGHT (-12) + + static const LOGFONTW s_lfDefault = + { + DEFAULT_FONT_HEIGHT, + 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + PROOF_QUALITY, FIXED_PITCH | FF_DONTCARE, + L"" // Default font name will be set later based on current language. + }; + + // This will be initialized when set_default_logfont() is called first time. + // The value will be based on the system DPI. + int current_font_height = 0; // also used in gui_w32.c + + /* + * Convert a string representing a point size into pixels. The string should + * be a positive decimal number, with an optional decimal point (eg, "12", or + * "10.5"). The pixel value is returned, and a pointer to the next unconverted + * character is stored in *end. The flag "vertical" says whether this + * calculation is for a vertical (height) size or a horizontal (width) one. + */ + static int + points_to_pixels(WCHAR *str, WCHAR **end, int vertical, long_i pprinter_dc) + { + int pixels; + int points = 0; + int divisor = 0; + HWND hwnd = (HWND)0; + HDC hdc; + HDC printer_dc = (HDC)pprinter_dc; + + while (*str != NUL) + { + if (*str == L'.' && divisor == 0) + { + // Start keeping a divisor, for later + divisor = 1; + } + else + { + if (!VIM_ISDIGIT(*str)) + break; + + points *= 10; + points += *str - L'0'; + divisor *= 10; + } + ++str; + } + + if (divisor == 0) + divisor = 1; + + if (printer_dc == NULL) + { + hwnd = GetDesktopWindow(); + hdc = GetWindowDC(hwnd); + } + else + hdc = printer_dc; + + pixels = MulDiv(points, + GetDeviceCaps(hdc, vertical ? LOGPIXELSY : LOGPIXELSX), + 72 * divisor); + + if (printer_dc == NULL) + ReleaseDC(hwnd, hdc); + + *end = str; + return pixels; + } + + /* + * Convert pixel into point size. This is a reverse of points_to_pixels. + */ + static double + pixels_to_points(int pixels, int vertical, long_i pprinter_dc) + { + double points = 0; + HWND hwnd = (HWND)0; + HDC hdc; + HDC printer_dc = (HDC)pprinter_dc; + + if (printer_dc == NULL) + { + hwnd = GetDesktopWindow(); + hdc = GetWindowDC(hwnd); + } + else + hdc = printer_dc; + + points = pixels * 72.0 / GetDeviceCaps(hdc, vertical ? LOGPIXELSY : LOGPIXELSX); + if (printer_dc == NULL) + ReleaseDC(hwnd, hdc); + + return points; + } + + static int CALLBACK + font_enumproc( + ENUMLOGFONTW *elf, + NEWTEXTMETRICW *ntm UNUSED, + DWORD type UNUSED, + LPARAM lparam) + { + // Return value: + // 0 = terminate now (monospace & ANSI) + // 1 = continue, still no luck... + // 2 = continue, but we have an acceptable LOGFONTW + // (monospace, not ANSI) + // We use these values, as EnumFontFamilies returns 1 if the + // callback function is never called. So, we check the return as + // 0 = perfect, 2 = OK, 1 = no good... + // It's not pretty, but it works! + + LOGFONTW *lf = (LOGFONTW *)(lparam); + + # ifndef FEAT_PROPORTIONAL_FONTS + // Ignore non-monospace fonts without further ado + if ((ntm->tmPitchAndFamily & 1) != 0) + return 1; + # endif + + // Remember this LOGFONTW as a "possible" + *lf = elf->elfLogFont; + + // Terminate the scan as soon as we find an ANSI font + if (lf->lfCharSet == ANSI_CHARSET + || lf->lfCharSet == OEM_CHARSET + || lf->lfCharSet == DEFAULT_CHARSET) + return 0; + + // Continue the scan - we have a non-ANSI font + return 2; + } + + static int + init_logfont(LOGFONTW *lf) + { + int n; + HWND hwnd = GetDesktopWindow(); + HDC hdc = GetWindowDC(hwnd); + + n = EnumFontFamiliesW(hdc, + lf->lfFaceName, + (FONTENUMPROCW)font_enumproc, + (LPARAM)lf); + + ReleaseDC(hwnd, hdc); + + // If we couldn't find a usable font, return failure + if (n == 1) + return FAIL; + + // Tidy up the rest of the LOGFONTW structure. We set to a basic + // font - get_logfont() sets bold, italic, etc based on the user's + // input. + lf->lfHeight = current_font_height; + lf->lfWidth = 0; + lf->lfItalic = FALSE; + lf->lfUnderline = FALSE; + lf->lfStrikeOut = FALSE; + lf->lfWeight = FW_NORMAL; + + // Return success + return OK; + } + + /* + * Call back for EnumFontFamiliesW in expand_font_enumproc. + * + */ + static int CALLBACK + expand_font_enumproc( + ENUMLOGFONTW *elf, + NEWTEXTMETRICW *ntm UNUSED, + DWORD type UNUSED, + LPARAM lparam) + { + LOGFONTW *lf = (LOGFONTW*)elf; + + # ifndef FEAT_PROPORTIONAL_FONTS + // Ignore non-monospace fonts without further ado + if ((ntm->tmPitchAndFamily & 1) != 0) + return 1; + # endif + + // Filter only on ANSI. Otherwise will see a lot of random fonts that we + // usually don't want. + if (lf->lfCharSet != ANSI_CHARSET) + return 1; + + int (*add_match)(char_u *) = (int (*)(char_u *))lparam; + + WCHAR *faceNameW = lf->lfFaceName; + char_u *faceName = utf16_to_enc(faceNameW, NULL); + if (!faceName) + return 0; + + add_match(faceName); + vim_free(faceName); + + return 1; + } + + /* + * Cmdline expansion for setting 'guifont'. Will enumerate through all + * monospace fonts for completion. If used after ':', will expand to possible + * font configuration options like font sizes. + * + * This function has "gui" in its name because in some platforms (GTK) font + * handling is done by the GUI code, whereas in Windows it's part of the + * platform code. + */ + void + gui_mch_expand_font(optexpand_T *args, void *param UNUSED, int (*add_match)(char_u *val)) + { + expand_T *xp = args->oe_xp; + if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern-1) == ':') + { + char buf[30]; + + // Always fill in with the current font size as first option for + // convenience. We simply round to the closest integer for simplicity. + int font_height = (int)round( + pixels_to_points(-current_font_height, TRUE, (long_i)NULL)); + vim_snprintf(buf, ARRAY_LENGTH(buf), "h%d", font_height); + add_match((char_u *)buf); + + // Note: Keep this in sync with get_logfont(). Don't include 'c' and + // 'q' as we fill in all the values below. + static char *(p_gfn_win_opt_values[]) = { + "h" , "w" , "W" , "b" , "i" , "u" , "s"}; + for (size_t i = 0; i < ARRAY_LENGTH(p_gfn_win_opt_values); i++) + add_match((char_u *)p_gfn_win_opt_values[i]); + + struct charset_pair *cp; + for (cp = charset_pairs; cp->name != NULL; ++cp) + { + vim_snprintf(buf, ARRAY_LENGTH(buf), "c%s", cp->name); + add_match((char_u *)buf); + } + struct quality_pair *qp; + for (qp = quality_pairs; qp->name != NULL; ++qp) + { + vim_snprintf(buf, ARRAY_LENGTH(buf), "q%s", qp->name); + add_match((char_u *)buf); + } + return; + } + + HWND hwnd = GetDesktopWindow(); + HDC hdc = GetWindowDC(hwnd); + + EnumFontFamiliesW(hdc, + NULL, + (FONTENUMPROCW)expand_font_enumproc, + (LPARAM)add_match); + + ReleaseDC(hwnd, hdc); + } + + /* + * Compare a UTF-16 string and an ASCII string literally. + * Only works all the code points are inside ASCII range. + */ + static int + utf16ascncmp(const WCHAR *w, const char *p, size_t n) + { + size_t i; + + for (i = 0; i < n; i++) + { + if (w[i] == 0 || w[i] != p[i]) + return w[i] - p[i]; + } + return 0; + } + + /* + * Equivalent of GetDpiForSystem(). + */ + UINT WINAPI + vimGetDpiForSystem(void) + { + HWND hwnd = GetDesktopWindow(); + HDC hdc = GetWindowDC(hwnd); + UINT dpi = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(hwnd, hdc); + return dpi; + } + + /* + * Set default logfont based on current language. + */ + static void + set_default_logfont(LOGFONTW *lf) + { + // Default font name for current language on MS-Windows. + // If not translated, falls back to "Consolas". + // This must be a fixed-pitch font. + const char *defaultfontname = N_("DefaultFontNameForWindows"); + char *fontname = _(defaultfontname); + + if (strcmp(fontname, defaultfontname) == 0) + fontname = "Consolas"; + + *lf = s_lfDefault; + lf->lfHeight = DEFAULT_FONT_HEIGHT * (int)vimGetDpiForSystem() / 96; + if (current_font_height == 0) + current_font_height = lf->lfHeight; + + WCHAR *wfontname = enc_to_utf16((char_u*)fontname, NULL); + if (wfontname != NULL) + { + wcscpy_s(lf->lfFaceName, LF_FACESIZE, wfontname); + vim_free(wfontname); + } + } + + /* + * Get font info from "name" into logfont "lf". + * Return OK for a valid name, FAIL otherwise. + */ + int + get_logfont( + LOGFONTW *lf, + char_u *name, + HDC printer_dc, + int verbose) + { + WCHAR *p; + int i; + int ret = FAIL; + static LOGFONTW *lastlf = NULL; + WCHAR *wname; + + set_default_logfont(lf); + if (name == NULL) + return OK; + + wname = enc_to_utf16(name, NULL); + if (wname == NULL) + return FAIL; + + if (wcscmp(wname, L"*") == 0) + { + # if defined(FEAT_GUI_MSWIN) + CHOOSEFONTW cf; + // if name is "*", bring up std font dialog: + CLEAR_FIELD(cf); + cf.lStructSize = sizeof(cf); + cf.hwndOwner = s_hwnd; + cf.Flags = CF_SCREENFONTS | CF_FIXEDPITCHONLY | CF_INITTOLOGFONTSTRUCT; + if (lastlf != NULL) + *lf = *lastlf; + cf.lpLogFont = lf; + cf.nFontType = 0 ; //REGULAR_FONTTYPE; + if (ChooseFontW(&cf)) + ret = OK; + # endif + goto theend; + } + + /* + * Split name up, it could be :h:w etc. + */ + for (p = wname; *p && *p != L':'; p++) + { + if (p - wname + 1 >= LF_FACESIZE) + goto theend; // Name too long + lf->lfFaceName[p - wname] = *p; + } + if (p != wname) + lf->lfFaceName[p - wname] = NUL; + + // First set defaults + lf->lfHeight = DEFAULT_FONT_HEIGHT * (int)vimGetDpiForSystem() / 96; + lf->lfWidth = 0; + lf->lfWeight = FW_NORMAL; + lf->lfItalic = FALSE; + lf->lfUnderline = FALSE; + lf->lfStrikeOut = FALSE; + + /* + * If the font can't be found, try replacing '_' by ' '. + */ + if (init_logfont(lf) == FAIL) + { + int did_replace = FALSE; + + for (i = 0; lf->lfFaceName[i]; ++i) + if (lf->lfFaceName[i] == L'_') + { + lf->lfFaceName[i] = L' '; + did_replace = TRUE; + } + if (!did_replace || init_logfont(lf) == FAIL) + goto theend; + } + + while (*p == L':') + p++; + + // Set the values found after ':' + while (*p) + { + switch (*p++) + { + // Note: Keep this in sync with gui_mch_expand_font(). + case L'h': + lf->lfHeight = - points_to_pixels(p, &p, TRUE, (long_i)printer_dc); + break; + case L'w': + lf->lfWidth = points_to_pixels(p, &p, FALSE, (long_i)printer_dc); + break; + case L'W': + lf->lfWeight = wcstol(p, &p, 10); + break; + case L'b': + lf->lfWeight = FW_BOLD; + break; + case L'i': + lf->lfItalic = TRUE; + break; + case L'u': + lf->lfUnderline = TRUE; + break; + case L's': + lf->lfStrikeOut = TRUE; + break; + case L'c': + { + struct charset_pair *cp; + + for (cp = charset_pairs; cp->name != NULL; ++cp) + if (utf16ascncmp(p, cp->name, strlen(cp->name)) == 0) + { + lf->lfCharSet = cp->charset; + p += strlen(cp->name); + break; + } + if (cp->name == NULL && verbose) + { + char_u *s = utf16_to_enc(p, NULL); + + semsg(_(e_illegal_str_name_str_in_font_name_str), + "charset", s, name); + vim_free(s); + break; + } + break; + } + case L'q': + { + struct quality_pair *qp; + + for (qp = quality_pairs; qp->name != NULL; ++qp) + if (utf16ascncmp(p, qp->name, strlen(qp->name)) == 0) + { + lf->lfQuality = qp->quality; + p += strlen(qp->name); + break; + } + if (qp->name == NULL && verbose) + { + char_u *s = utf16_to_enc(p, NULL); + semsg(_(e_illegal_str_name_str_in_font_name_str), + "quality", s, name); + vim_free(s); + break; + } + break; + } + default: + if (verbose) + semsg(_(e_illegal_char_nr_in_font_name_str), p[-1], name); + goto theend; + } + while (*p == L':') + p++; + } + ret = OK; + + theend: + // ron: init lastlf + if (ret == OK && printer_dc == NULL) + { + vim_free(lastlf); + lastlf = ALLOC_ONE(LOGFONTW); + if (lastlf != NULL) + mch_memmove(lastlf, lf, sizeof(LOGFONTW)); + } + vim_free(wname); + + return ret; + } + + #endif // defined(FEAT_GUI) || defined(FEAT_PRINTER) + + #if defined(FEAT_JOB_CHANNEL) || defined(PROTO) + /* + * Initialize the Winsock dll. + */ + void + channel_init_winsock(void) + { + WSADATA wsaData; + int wsaerr; + + if (WSInitialized) + return; + + wsaerr = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (wsaerr == 0) + WSInitialized = TRUE; + } + #endif diff --git a/src/test/resources/unparser/diff/error02.txt b/src/test/resources/unparser/diff/error02.txt new file mode 100644 index 00000000..a32c8e43 --- /dev/null +++ b/src/test/resources/unparser/diff/error02.txt @@ -0,0 +1,2067 @@ +textDiff: + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + #include "vim.h" + + #ifdef AMIGA + # include // for time() + #endif + + /* + * Vim originated from Stevie version 3.6 (Fish disk 217) by GRWalter (Fred) + * It has been changed beyond recognition since then. + * + * Differences between version 7.4 and 8.x can be found with ":help version8". + * Differences between version 6.4 and 7.x can be found with ":help version7". + * Differences between version 5.8 and 6.x can be found with ":help version6". + * Differences between version 4.x and 5.x can be found with ":help version5". + * Differences between version 3.0 and 4.x can be found with ":help version4". + * All the remarks about older versions have been removed, they are not very + * interesting. + */ + + #include "version.h" + + char *Version = VIM_VERSION_SHORT; + static char *mediumVersion = VIM_VERSION_MEDIUM; + + #if defined(HAVE_DATE_TIME) || defined(PROTO) + # if (defined(VMS) && defined(VAXC)) || defined(PROTO) + char longVersion[sizeof(VIM_VERSION_LONG_DATE) + sizeof(__DATE__) + + sizeof(__TIME__) + 3]; + + void + init_longVersion(void) + { + /* + * Construct the long version string. Necessary because + * VAX C can't concatenate strings in the preprocessor. + */ + strcpy(longVersion, VIM_VERSION_LONG_DATE); + strcat(longVersion, __DATE__); + strcat(longVersion, " "); + strcat(longVersion, __TIME__); + strcat(longVersion, ")"); + } + + # else + void + init_longVersion(void) + { + char *date_time = __DATE__ " " __TIME__; + char *msg = _("%s (%s, compiled %s)"); + size_t len = strlen(msg) + + strlen(VIM_VERSION_LONG_ONLY) + + strlen(VIM_VERSION_DATE_ONLY) + + strlen(date_time); + + longVersion = alloc(len); + if (longVersion == NULL) + longVersion = VIM_VERSION_LONG; + else + vim_snprintf(longVersion, len, msg, + VIM_VERSION_LONG_ONLY, VIM_VERSION_DATE_ONLY, date_time); + } + # endif + #else + char *longVersion = VIM_VERSION_LONG; + + void + init_longVersion(void) + { + // nothing to do + } + #endif + + static char *(features[]) = + { + #ifdef HAVE_ACL + "+acl", + #else + "-acl", + #endif + #ifdef AMIGA // only for Amiga systems + # ifdef FEAT_ARP + "+ARP", + # else + "-ARP", + # endif + #endif + #ifdef FEAT_ARABIC + "+arabic", + #else + "-arabic", + #endif + "+autocmd", + #ifdef FEAT_AUTOCHDIR + "+autochdir", + #else + "-autochdir", + #endif + #ifdef FEAT_AUTOSERVERNAME + "+autoservername", + #else + "-autoservername", + #endif + #ifdef FEAT_BEVAL_GUI + "+balloon_eval", + #else + "-balloon_eval", + #endif + #ifdef FEAT_BEVAL_TERM + "+balloon_eval_term", + #else + "-balloon_eval_term", + #endif + #ifdef FEAT_BROWSE + "+browse", + #else + "-browse", + #endif + #ifdef NO_BUILTIN_TCAPS + "-builtin_terms", + #endif + #ifdef SOME_BUILTIN_TCAPS + "+builtin_terms", + #endif + #ifdef ALL_BUILTIN_TCAPS + "++builtin_terms", + #endif + #ifdef FEAT_BYTEOFF + "+byte_offset", + #else + "-byte_offset", + #endif + #ifdef FEAT_JOB_CHANNEL + "+channel", + #else + "-channel", + #endif + #ifdef FEAT_CINDENT + "+cindent", + #else + "-cindent", + #endif + #ifdef FEAT_CLIENTSERVER + "+clientserver", + #else + "-clientserver", + #endif + #ifdef FEAT_CLIPBOARD + "+clipboard", + #else + "-clipboard", + #endif + "+cmdline_compl", + "+cmdline_hist", + #ifdef FEAT_CMDL_INFO + "+cmdline_info", + #else + "-cmdline_info", + #endif + "+comments", + #ifdef FEAT_CONCEAL + "+conceal", + #else + "-conceal", + #endif + #ifdef FEAT_CRYPT + "+cryptv", + #else + "-cryptv", + #endif + #ifdef FEAT_CSCOPE + "+cscope", + #else + "-cscope", + #endif + "+cursorbind", + #ifdef CURSOR_SHAPE + "+cursorshape", + #else + "-cursorshape", + #endif + #if defined(FEAT_CON_DIALOG) && defined(FEAT_GUI_DIALOG) + "+dialog_con_gui", + #else + # if defined(FEAT_CON_DIALOG) + "+dialog_con", + # else + # if defined(FEAT_GUI_DIALOG) + "+dialog_gui", + # else + "-dialog", + # endif + # endif + #endif + #ifdef FEAT_DIFF + "+diff", + #else + "-diff", + #endif + #ifdef FEAT_DIGRAPHS + "+digraphs", + #else + "-digraphs", + #endif + #ifdef FEAT_GUI_MSWIN + # ifdef FEAT_DIRECTX + "+directx", + # else + "-directx", + # endif + #endif + #ifdef FEAT_DND + "+dnd", + #else + "-dnd", + #endif + #ifdef EBCDIC + "+ebcdic", + #else + "-ebcdic", + #endif + #ifdef FEAT_EMACS_TAGS + "+emacs_tags", + #else + "-emacs_tags", + #endif + #ifdef FEAT_EVAL + "+eval", + #else + "-eval", + #endif + "+ex_extra", + #ifdef FEAT_SEARCH_EXTRA + "+extra_search", + #else + "-extra_search", + #endif + "-farsi", + #ifdef FEAT_SEARCHPATH + "+file_in_path", + #else + "-file_in_path", + #endif + #ifdef FEAT_FIND_ID + "+find_in_path", + #else + "-find_in_path", + #endif + #ifdef FEAT_FLOAT + "+float", + #else + "-float", + #endif + #ifdef FEAT_FOLDING + "+folding", + #else + "-folding", + #endif + #ifdef FEAT_FOOTER + "+footer", + #else + "-footer", + #endif + // only interesting on Unix systems + #if !defined(USE_SYSTEM) && defined(UNIX) + "+fork()", + #endif + #ifdef FEAT_GETTEXT + # ifdef DYNAMIC_GETTEXT + "+gettext/dyn", + # else + "+gettext", + # endif + #else + "-gettext", + #endif + "-hangul_input", + #if (defined(HAVE_ICONV_H) && defined(USE_ICONV)) || defined(DYNAMIC_ICONV) + # ifdef DYNAMIC_ICONV + "+iconv/dyn", + # else + "+iconv", + # endif + #else + "-iconv", + #endif + "+insert_expand", + #ifdef FEAT_JOB_CHANNEL + "+job", + #else + "-job", + #endif + #ifdef FEAT_JUMPLIST + "+jumplist", + #else + "-jumplist", + #endif + #ifdef FEAT_KEYMAP + "+keymap", + #else + "-keymap", + #endif + #ifdef FEAT_EVAL + "+lambda", + #else + "-lambda", + #endif + #ifdef FEAT_LANGMAP + "+langmap", + #else + "-langmap", + #endif + #ifdef FEAT_LIBCALL + "+libcall", + #else + "-libcall", + #endif + #ifdef FEAT_LINEBREAK + "+linebreak", + #else + "-linebreak", + #endif + #ifdef FEAT_LISP + "+lispindent", + #else + "-lispindent", + #endif + "+listcmds", + "+localmap", + #ifdef FEAT_LUA + # ifdef DYNAMIC_LUA + "+lua/dyn", + # else + "+lua", + # endif + #else + "-lua", + #endif + #ifdef FEAT_MENU + "+menu", + #else + "-menu", + #endif + #ifdef FEAT_SESSION + "+mksession", + #else + "-mksession", + #endif + "+modify_fname", + "+mouse", + #ifdef FEAT_MOUSESHAPE + "+mouseshape", + #else + "-mouseshape", + #endif + + #if defined(UNIX) || defined(VMS) + # ifdef FEAT_MOUSE_DEC + "+mouse_dec", + # else + "-mouse_dec", + # endif + # ifdef FEAT_MOUSE_GPM + "+mouse_gpm", + # else + "-mouse_gpm", + # endif + # ifdef FEAT_MOUSE_JSB + "+mouse_jsbterm", + # else + "-mouse_jsbterm", + # endif + # ifdef FEAT_MOUSE_NET + "+mouse_netterm", + # else + "-mouse_netterm", + # endif + #endif + + #ifdef __QNX__ + # ifdef FEAT_MOUSE_PTERM + "+mouse_pterm", + # else + "-mouse_pterm", + # endif + #endif + + #if defined(UNIX) || defined(VMS) + "+mouse_sgr", + # ifdef FEAT_SYSMOUSE + "+mouse_sysmouse", + # else + "-mouse_sysmouse", + # endif + # ifdef FEAT_MOUSE_URXVT + "+mouse_urxvt", + # else + "-mouse_urxvt", + # endif + "+mouse_xterm", + #endif + + #ifdef FEAT_MBYTE_IME + # ifdef DYNAMIC_IME + "+multi_byte_ime/dyn", + # else + "+multi_byte_ime", + # endif + #else + "+multi_byte", + #endif + #ifdef FEAT_MULTI_LANG + "+multi_lang", + #else + "-multi_lang", + #endif + #ifdef FEAT_MZSCHEME + # ifdef DYNAMIC_MZSCHEME + "+mzscheme/dyn", + # else + "+mzscheme", + # endif + #else + "-mzscheme", + #endif + #ifdef FEAT_NETBEANS_INTG + "+netbeans_intg", + #else + "-netbeans_intg", + #endif + "+num64", + #ifdef FEAT_GUI_MSWIN + # ifdef FEAT_OLE + "+ole", + # else + "-ole", + # endif + #endif + #ifdef FEAT_EVAL + "+packages", + #else + "-packages", + #endif + #ifdef FEAT_PATH_EXTRA + "+path_extra", + #else + "-path_extra", + #endif + #ifdef FEAT_PERL + # ifdef DYNAMIC_PERL + "+perl/dyn", + # else + "+perl", + # endif + #else + "-perl", + #endif + #ifdef FEAT_PERSISTENT_UNDO + "+persistent_undo", + #else + "-persistent_undo", + #endif + #ifdef FEAT_PROP_POPUP + "+popupwin", + #else + "-popupwin", + #endif + #ifdef FEAT_PRINTER + # ifdef FEAT_POSTSCRIPT + "+postscript", + # else + "-postscript", + # endif + "+printer", + #else + "-printer", + #endif + #ifdef FEAT_PROFILE + "+profile", + #else + "-profile", + #endif + #ifdef FEAT_PYTHON + # ifdef DYNAMIC_PYTHON + "+python/dyn", + # else + "+python", + # endif + #else + "-python", + #endif + #ifdef FEAT_PYTHON3 + # ifdef DYNAMIC_PYTHON3 + "+python3/dyn", + # else + "+python3", + # endif + #else + "-python3", + #endif + #ifdef FEAT_QUICKFIX + "+quickfix", + #else + "-quickfix", + #endif + #ifdef FEAT_RELTIME + "+reltime", + #else + "-reltime", + #endif + #ifdef FEAT_RIGHTLEFT + "+rightleft", + #else + "-rightleft", + #endif + #ifdef FEAT_RUBY + # ifdef DYNAMIC_RUBY + "+ruby/dyn", + # else + "+ruby", + # endif + #else + "-ruby", + #endif + "+scrollbind", + #ifdef FEAT_SIGNS + "+signs", + #else + "-signs", + #endif + #ifdef FEAT_SMARTINDENT + "+smartindent", + #else + "-smartindent", + #endif + #ifdef FEAT_SOUND + "+sound", + #else + "-sound", + #endif + #ifdef FEAT_SPELL + "+spell", + #else + "-spell", + #endif + #ifdef STARTUPTIME + "+startuptime", + #else + "-startuptime", + #endif + #ifdef FEAT_STL_OPT + "+statusline", + #else + "-statusline", + #endif + "-sun_workshop", + #ifdef FEAT_SYN_HL + "+syntax", + #else + "-syntax", + #endif + // only interesting on Unix systems + #if defined(USE_SYSTEM) && defined(UNIX) + "+system()", + #endif + #ifdef FEAT_TAG_BINS + "+tag_binary", + #else + "-tag_binary", + #endif + "-tag_old_static", + "-tag_any_white", + #ifdef FEAT_TCL + # ifdef DYNAMIC_TCL + "+tcl/dyn", + # else + "+tcl", + # endif + #else + "-tcl", + #endif + #ifdef FEAT_TERMGUICOLORS + "+termguicolors", + #else + "-termguicolors", + #endif + #ifdef FEAT_TERMINAL + "+terminal", + #else + "-terminal", + #endif + #if defined(UNIX) + // only Unix can have terminfo instead of termcap + # ifdef TERMINFO + "+terminfo", + # else + "-terminfo", + # endif + #endif + #ifdef FEAT_TERMRESPONSE + "+termresponse", + #else + "-termresponse", + #endif + #ifdef FEAT_TEXTOBJ + "+textobjects", + #else + "-textobjects", + #endif + #ifdef FEAT_PROP_POPUP + "+textprop", + #else + "-textprop", + #endif + #if !defined(UNIX) + // unix always includes termcap support + # ifdef HAVE_TGETENT + "+tgetent", + # else + "-tgetent", + # endif + #endif + #ifdef FEAT_TIMERS + "+timers", + #else + "-timers", + #endif + #ifdef FEAT_TITLE + "+title", + #else + "-title", + #endif + #ifdef FEAT_TOOLBAR + "+toolbar", + #else + "-toolbar", + #endif + "+user_commands", + #ifdef FEAT_VARTABS + "+vartabs", + #else + "-vartabs", + #endif + "+vertsplit", + "+virtualedit", + "+visual", + "+visualextra", + #ifdef FEAT_VIMINFO + "+viminfo", + #else + "-viminfo", + #endif + "+vreplace", + #ifdef MSWIN + # ifdef FEAT_VTP + "+vtp", + # else + "-vtp", + # endif + #endif + #ifdef FEAT_WILDIGN + "+wildignore", + #else + "-wildignore", + #endif + #ifdef FEAT_WILDMENU + "+wildmenu", + #else + "-wildmenu", + #endif + "+windows", + #ifdef FEAT_WRITEBACKUP + "+writebackup", + #else + "-writebackup", + #endif + #if defined(UNIX) || defined(VMS) + # ifdef FEAT_X11 + "+X11", + # else + "-X11", + # endif + #endif + #ifdef FEAT_XFONTSET + "+xfontset", + #else + "-xfontset", + #endif + #ifdef FEAT_XIM + "+xim", + #else + "-xim", + #endif + #ifdef MSWIN + # ifdef FEAT_XPM_W32 + "+xpm_w32", + # else + "-xpm_w32", + # endif + #else + # ifdef HAVE_XPM + "+xpm", + # else + "-xpm", + # endif + #endif + #if defined(UNIX) || defined(VMS) + # ifdef USE_XSMP_INTERACT + "+xsmp_interact", + # else + # ifdef USE_XSMP + "+xsmp", + # else + "-xsmp", + # endif + # endif + # ifdef FEAT_XCLIPBOARD + "+xterm_clipboard", + # else + "-xterm_clipboard", + # endif + #endif + #ifdef FEAT_XTERM_SAVE + "+xterm_save", + #else + "-xterm_save", + #endif + NULL + }; + + static int included_patches[] = + { /* Add new patch number below this line */ + /**/ ++ 320, ++/**/ + 319, + /**/ + 318, + /**/ + 317, + /**/ + 316, + /**/ + 315, + /**/ + 314, + /**/ + 313, + /**/ + 312, + /**/ + 311, + /**/ + 310, + /**/ + 309, + /**/ + 308, + /**/ + 307, + /**/ + 306, + /**/ + 305, + /**/ + 304, + /**/ + 303, + /**/ + 302, + /**/ + 301, + /**/ + 300, + /**/ + 299, + /**/ + 298, + /**/ + 297, + /**/ + 296, + /**/ + 295, + /**/ + 294, + /**/ + 293, + /**/ + 292, + /**/ + 291, + /**/ + 290, + /**/ + 289, + /**/ + 288, + /**/ + 287, + /**/ + 286, + /**/ + 285, + /**/ + 284, + /**/ + 283, + /**/ + 282, + /**/ + 281, + /**/ + 280, + /**/ + 279, + /**/ + 278, + /**/ + 277, + /**/ + 276, + /**/ + 275, + /**/ + 274, + /**/ + 273, + /**/ + 272, + /**/ + 271, + /**/ + 270, + /**/ + 269, + /**/ + 268, + /**/ + 267, + /**/ + 266, + /**/ + 265, + /**/ + 264, + /**/ + 263, + /**/ + 262, + /**/ + 261, + /**/ + 260, + /**/ + 259, + /**/ + 258, + /**/ + 257, + /**/ + 256, + /**/ + 255, + /**/ + 254, + /**/ + 253, + /**/ + 252, + /**/ + 251, + /**/ + 250, + /**/ + 249, + /**/ + 248, + /**/ + 247, + /**/ + 246, + /**/ + 245, + /**/ + 244, + /**/ + 243, + /**/ + 242, + /**/ + 241, + /**/ + 240, + /**/ + 239, + /**/ + 238, + /**/ + 237, + /**/ + 236, + /**/ + 235, + /**/ + 234, + /**/ + 233, + /**/ + 232, + /**/ + 231, + /**/ + 230, + /**/ + 229, + /**/ + 228, + /**/ + 227, + /**/ + 226, + /**/ + 225, + /**/ + 224, + /**/ + 223, + /**/ + 222, + /**/ + 221, + /**/ + 220, + /**/ + 219, + /**/ + 218, + /**/ + 217, + /**/ + 216, + /**/ + 215, + /**/ + 214, + /**/ + 213, + /**/ + 212, + /**/ + 211, + /**/ + 210, + /**/ + 209, + /**/ + 208, + /**/ + 207, + /**/ + 206, + /**/ + 205, + /**/ + 204, + /**/ + 203, + /**/ + 202, + /**/ + 201, + /**/ + 200, + /**/ + 199, + /**/ + 198, + /**/ + 197, + /**/ + 196, + /**/ + 195, + /**/ + 194, + /**/ + 193, + /**/ + 192, + /**/ + 191, + /**/ + 190, + /**/ + 189, + /**/ + 188, + /**/ + 187, + /**/ + 186, + /**/ + 185, + /**/ + 184, + /**/ + 183, + /**/ + 182, + /**/ + 181, + /**/ + 180, + /**/ + 179, + /**/ + 178, + /**/ + 177, + /**/ + 176, + /**/ + 175, + /**/ + 174, + /**/ + 173, + /**/ + 172, + /**/ + 171, + /**/ + 170, + /**/ + 169, + /**/ + 168, + /**/ + 167, + /**/ + 166, + /**/ + 165, + /**/ + 164, + /**/ + 163, + /**/ + 162, + /**/ + 161, + /**/ + 160, + /**/ + 159, + /**/ + 158, + /**/ + 157, + /**/ + 156, + /**/ + 155, + /**/ + 154, + /**/ + 153, + /**/ + 152, + /**/ + 151, + /**/ + 150, + /**/ + 149, + /**/ + 148, + /**/ + 147, + /**/ + 146, + /**/ + 145, + /**/ + 144, + /**/ + 143, + /**/ + 142, + /**/ + 141, + /**/ + 140, + /**/ + 139, + /**/ + 138, + /**/ + 137, + /**/ + 136, + /**/ + 135, + /**/ + 134, + /**/ + 133, + /**/ + 132, + /**/ + 131, + /**/ + 130, + /**/ + 129, + /**/ + 128, + /**/ + 127, + /**/ + 126, + /**/ + 125, + /**/ + 124, + /**/ + 123, + /**/ + 122, + /**/ + 121, + /**/ + 120, + /**/ + 119, + /**/ + 118, + /**/ + 117, + /**/ + 116, + /**/ + 115, + /**/ + 114, + /**/ + 113, + /**/ + 112, + /**/ + 111, + /**/ + 110, + /**/ + 109, + /**/ + 108, + /**/ + 107, + /**/ + 106, + /**/ + 105, + /**/ + 104, + /**/ + 103, + /**/ + 102, + /**/ + 101, + /**/ + 100, + /**/ + 99, + /**/ + 98, + /**/ + 97, + /**/ + 96, + /**/ + 95, + /**/ + 94, + /**/ + 93, + /**/ + 92, + /**/ + 91, + /**/ + 90, + /**/ + 89, + /**/ + 88, + /**/ + 87, + /**/ + 86, + /**/ + 85, + /**/ + 84, + /**/ + 83, + /**/ + 82, + /**/ + 81, + /**/ + 80, + /**/ + 79, + /**/ + 78, + /**/ + 77, + /**/ + 76, + /**/ + 75, + /**/ + 74, + /**/ + 73, + /**/ + 72, + /**/ + 71, + /**/ + 70, + /**/ + 69, + /**/ + 68, + /**/ + 67, + /**/ + 66, + /**/ + 65, + /**/ + 64, + /**/ + 63, + /**/ + 62, + /**/ + 61, + /**/ + 60, + /**/ + 59, + /**/ + 58, + /**/ + 57, + /**/ + 56, + /**/ + 55, + /**/ + 54, + /**/ + 53, + /**/ + 52, + /**/ + 51, + /**/ + 50, + /**/ + 49, + /**/ + 48, + /**/ + 47, + /**/ + 46, + /**/ + 45, + /**/ + 44, + /**/ + 43, + /**/ + 42, + /**/ + 41, + /**/ + 40, + /**/ + 39, + /**/ + 38, + /**/ + 37, + /**/ + 36, + /**/ + 35, + /**/ + 34, + /**/ + 33, + /**/ + 32, + /**/ + 31, + /**/ + 30, + /**/ + 29, + /**/ + 28, + /**/ + 27, + /**/ + 26, + /**/ + 25, + /**/ + 24, + /**/ + 23, + /**/ + 22, + /**/ + 21, + /**/ + 20, + /**/ + 19, + /**/ + 18, + /**/ + 17, + /**/ + 16, + /**/ + 15, + /**/ + 14, + /**/ + 13, + /**/ + 12, + /**/ + 11, + /**/ + 10, + /**/ + 9, + /**/ + 8, + /**/ + 7, + /**/ + 6, + /**/ + 5, + /**/ + 4, + /**/ + 3, + /**/ + 2, + /**/ + 1, + /**/ + 0 + }; + + /* + * Place to put a short description when adding a feature with a patch. + * Keep it short, e.g.,: "relative numbers", "persistent undo". + * Also add a comment marker to separate the lines. + * See the official Vim patches for the diff format: It must use a context of + * one line only. Create it by hand or use "diff -C2" and edit the patch. + */ + static char *(extra_patches[]) = + { /* Add your patch description below this line */ + /**/ + NULL + }; + + int + highest_patch(void) + { + // this relies on the highest patch number to be the first entry + return included_patches[0]; + } + + #if defined(FEAT_EVAL) || defined(PROTO) + /* + * Return TRUE if patch "n" has been included. + */ + int + has_patch(int n) + { + int i; + + for (i = 0; included_patches[i] != 0; ++i) + if (included_patches[i] == n) + return TRUE; + return FALSE; + } + #endif + + void + ex_version(exarg_T *eap) + { + /* + * Ignore a ":version 9.99" command. + */ + if (*eap->arg == NUL) + { + msg_putchar('\n'); + list_version(); + } + } + + /* + * Output a string for the version message. If it's going to wrap, output a + * newline, unless the message is too long to fit on the screen anyway. + * When "wrap" is TRUE wrap the string in []. + */ + static void + version_msg_wrap(char_u *s, int wrap) + { + int len = (int)vim_strsize(s) + (wrap ? 2 : 0); + + if (!got_int && len < (int)Columns && msg_col + len >= (int)Columns + && *s != '\n') + msg_putchar('\n'); + if (!got_int) + { + if (wrap) + msg_puts("["); + msg_puts((char *)s); + if (wrap) + msg_puts("]"); + } + } + + static void + version_msg(char *s) + { + version_msg_wrap((char_u *)s, FALSE); + } + + /* + * List all features aligned in columns, dictionary style. + */ + static void + list_features(void) + { + list_in_columns((char_u **)features, -1, -1); + } + + /* + * List string items nicely aligned in columns. + * When "size" is < 0 then the last entry is marked with NULL. + * The entry with index "current" is inclosed in []. + */ + void + list_in_columns(char_u **items, int size, int current) + { + int i; + int ncol; + int nrow; + int cur_row = 1; + int item_count = 0; + int width = 0; + #ifdef FEAT_SYN_HL + int use_highlight = (items == (char_u **)features); + #endif + + // Find the length of the longest item, use that + 1 as the column + // width. + for (i = 0; size < 0 ? items[i] != NULL : i < size; ++i) + { + int l = (int)vim_strsize(items[i]) + (i == current ? 2 : 0); + + if (l > width) + width = l; + ++item_count; + } + width += 1; + + if (Columns < width) + { + // Not enough screen columns - show one per line + for (i = 0; i < item_count; ++i) + { + version_msg_wrap(items[i], i == current); + if (msg_col > 0 && i < item_count - 1) + msg_putchar('\n'); + } + return; + } + + // The rightmost column doesn't need a separator. + // Sacrifice it to fit in one more column if possible. + ncol = (int) (Columns + 1) / width; + nrow = item_count / ncol + (item_count % ncol ? 1 : 0); + + // "i" counts columns then rows. "idx" counts rows then columns. + for (i = 0; !got_int && i < nrow * ncol; ++i) + { + int idx = (i / ncol) + (i % ncol) * nrow; + + if (idx < item_count) + { + int last_col = (i + 1) % ncol == 0; + + if (idx == current) + msg_putchar('['); + #ifdef FEAT_SYN_HL + if (use_highlight && items[idx][0] == '-') + msg_puts_attr((char *)items[idx], HL_ATTR(HLF_W)); + else + #endif + msg_puts((char *)items[idx]); + if (idx == current) + msg_putchar(']'); + if (last_col) + { + if (msg_col > 0 && cur_row < nrow) + msg_putchar('\n'); + ++cur_row; + } + else + { + while (msg_col % width) + msg_putchar(' '); + } + } + else + { + // this row is out of items, thus at the end of the row + if (msg_col > 0) + { + if (cur_row < nrow) + msg_putchar('\n'); + ++cur_row; + } + } + } + } + + void + list_version(void) + { + int i; + int first; + char *s = ""; + + /* + * When adding features here, don't forget to update the list of + * internal variables in eval.c! + */ + init_longVersion(); + msg(longVersion); + #ifdef MSWIN + # ifdef FEAT_GUI_MSWIN + # ifdef VIMDLL + # ifdef _WIN64 + msg_puts(_("\nMS-Windows 64-bit GUI/console version")); + # else + msg_puts(_("\nMS-Windows 32-bit GUI/console version")); + # endif + # else + # ifdef _WIN64 + msg_puts(_("\nMS-Windows 64-bit GUI version")); + # else + msg_puts(_("\nMS-Windows 32-bit GUI version")); + # endif + # endif + # ifdef FEAT_OLE + msg_puts(_(" with OLE support")); + # endif + # else + # ifdef _WIN64 + msg_puts(_("\nMS-Windows 64-bit console version")); + # else + msg_puts(_("\nMS-Windows 32-bit console version")); + # endif + # endif + #endif + #if defined(MACOS_X) + # if defined(MACOS_X_DARWIN) + msg_puts(_("\nmacOS version")); + # else + msg_puts(_("\nmacOS version w/o darwin feat.")); + # endif + #endif + + #ifdef VMS + msg_puts(_("\nOpenVMS version")); + # ifdef HAVE_PATHDEF + if (*compiled_arch != NUL) + { + msg_puts(" - "); + msg_puts((char *)compiled_arch); + } + # endif + + #endif + + // Print the list of patch numbers if there is at least one. + // Print a range when patches are consecutive: "1-10, 12, 15-40, 42-45" + if (included_patches[0] != 0) + { + msg_puts(_("\nIncluded patches: ")); + first = -1; + // find last one + for (i = 0; included_patches[i] != 0; ++i) + ; + while (--i >= 0) + { + if (first < 0) + first = included_patches[i]; + if (i == 0 || included_patches[i - 1] != included_patches[i] + 1) + { + msg_puts(s); + s = ", "; + msg_outnum((long)first); + if (first != included_patches[i]) + { + msg_puts("-"); + msg_outnum((long)included_patches[i]); + } + first = -1; + } + } + } + + // Print the list of extra patch descriptions if there is at least one. + if (extra_patches[0] != NULL) + { + msg_puts(_("\nExtra patches: ")); + s = ""; + for (i = 0; extra_patches[i] != NULL; ++i) + { + msg_puts(s); + s = ", "; + msg_puts(extra_patches[i]); + } + } + + #ifdef MODIFIED_BY + msg_puts("\n"); + msg_puts(_("Modified by ")); + msg_puts(MODIFIED_BY); + #endif + + #ifdef HAVE_PATHDEF + if (*compiled_user != NUL || *compiled_sys != NUL) + { + msg_puts(_("\nCompiled ")); + if (*compiled_user != NUL) + { + msg_puts(_("by ")); + msg_puts((char *)compiled_user); + } + if (*compiled_sys != NUL) + { + msg_puts("@"); + msg_puts((char *)compiled_sys); + } + } + #endif + + #ifdef FEAT_HUGE + msg_puts(_("\nHuge version ")); + #else + # ifdef FEAT_BIG + msg_puts(_("\nBig version ")); + # else + # ifdef FEAT_NORMAL + msg_puts(_("\nNormal version ")); + # else + # ifdef FEAT_SMALL + msg_puts(_("\nSmall version ")); + # else + msg_puts(_("\nTiny version ")); + # endif + # endif + # endif + #endif + #ifndef FEAT_GUI + msg_puts(_("without GUI.")); + #else + # ifdef FEAT_GUI_GTK + # ifdef USE_GTK3 + msg_puts(_("with GTK3 GUI.")); + # else + # ifdef FEAT_GUI_GNOME + msg_puts(_("with GTK2-GNOME GUI.")); + # else + msg_puts(_("with GTK2 GUI.")); + # endif + # endif + # else + # ifdef FEAT_GUI_MOTIF + msg_puts(_("with X11-Motif GUI.")); + # else + # ifdef FEAT_GUI_ATHENA + # ifdef FEAT_GUI_NEXTAW + msg_puts(_("with X11-neXtaw GUI.")); + # else + msg_puts(_("with X11-Athena GUI.")); + # endif + # else ++# ifdef FEAT_GUI_HAIKU ++ msg_puts(_("with Haiku GUI.")); ++# else + # ifdef FEAT_GUI_PHOTON + msg_puts(_("with Photon GUI.")); + # else + # if defined(MSWIN) + msg_puts(_("with GUI.")); + # else + # if defined(TARGET_API_MAC_CARBON) && TARGET_API_MAC_CARBON + msg_puts(_("with Carbon GUI.")); + # else + # if defined(TARGET_API_MAC_OSX) && TARGET_API_MAC_OSX + msg_puts(_("with Cocoa GUI.")); + # else + # endif + # endif ++# endif + # endif + # endif + # endif + # endif + # endif + #endif + version_msg(_(" Features included (+) or not (-):\n")); + + list_features(); + if (msg_col > 0) + msg_putchar('\n'); + + #ifdef SYS_VIMRC_FILE + version_msg(_(" system vimrc file: \"")); + version_msg(SYS_VIMRC_FILE); + version_msg("\"\n"); + #endif + #ifdef USR_VIMRC_FILE + version_msg(_(" user vimrc file: \"")); + version_msg(USR_VIMRC_FILE); + version_msg("\"\n"); + #endif + #ifdef USR_VIMRC_FILE2 + version_msg(_(" 2nd user vimrc file: \"")); + version_msg(USR_VIMRC_FILE2); + version_msg("\"\n"); + #endif + #ifdef USR_VIMRC_FILE3 + version_msg(_(" 3rd user vimrc file: \"")); + version_msg(USR_VIMRC_FILE3); + version_msg("\"\n"); + #endif + #ifdef USR_EXRC_FILE + version_msg(_(" user exrc file: \"")); + version_msg(USR_EXRC_FILE); + version_msg("\"\n"); + #endif + #ifdef USR_EXRC_FILE2 + version_msg(_(" 2nd user exrc file: \"")); + version_msg(USR_EXRC_FILE2); + version_msg("\"\n"); + #endif + #ifdef FEAT_GUI + # ifdef SYS_GVIMRC_FILE + version_msg(_(" system gvimrc file: \"")); + version_msg(SYS_GVIMRC_FILE); + version_msg("\"\n"); + # endif + version_msg(_(" user gvimrc file: \"")); + version_msg(USR_GVIMRC_FILE); + version_msg("\"\n"); + # ifdef USR_GVIMRC_FILE2 + version_msg(_("2nd user gvimrc file: \"")); + version_msg(USR_GVIMRC_FILE2); + version_msg("\"\n"); + # endif + # ifdef USR_GVIMRC_FILE3 + version_msg(_("3rd user gvimrc file: \"")); + version_msg(USR_GVIMRC_FILE3); + version_msg("\"\n"); + # endif + #endif + version_msg(_(" defaults file: \"")); + version_msg(VIM_DEFAULTS_FILE); + version_msg("\"\n"); + #ifdef FEAT_GUI + # ifdef SYS_MENU_FILE + version_msg(_(" system menu file: \"")); + version_msg(SYS_MENU_FILE); + version_msg("\"\n"); + # endif + #endif + #ifdef HAVE_PATHDEF + if (*default_vim_dir != NUL) + { + version_msg(_(" fall-back for $VIM: \"")); + version_msg((char *)default_vim_dir); + version_msg("\"\n"); + } + if (*default_vimruntime_dir != NUL) + { + version_msg(_(" f-b for $VIMRUNTIME: \"")); + version_msg((char *)default_vimruntime_dir); + version_msg("\"\n"); + } + version_msg(_("Compilation: ")); + version_msg((char *)all_cflags); + version_msg("\n"); + #ifdef VMS + if (*compiler_version != NUL) + { + version_msg(_("Compiler: ")); + version_msg((char *)compiler_version); + version_msg("\n"); + } + #endif + version_msg(_("Linking: ")); + version_msg((char *)all_lflags); + #endif + #ifdef DEBUG + version_msg("\n"); + version_msg(_(" DEBUG BUILD")); + #endif + } + + static void do_intro_line(int row, char_u *mesg, int add_version, int attr); + + /* + * Show the intro message when not editing a file. + */ + void + maybe_intro_message(void) + { + if (BUFEMPTY() + && curbuf->b_fname == NULL + && firstwin->w_next == NULL + && vim_strchr(p_shm, SHM_INTRO) == NULL) + intro_message(FALSE); + } + + /* + * Give an introductory message about Vim. + * Only used when starting Vim on an empty file, without a file name. + * Or with the ":intro" command (for Sven :-). + */ + void + intro_message( + int colon) // TRUE for ":intro" + { + int i; + int row; + int blanklines; + int sponsor; + char *p; + static char *(lines[]) = + { + N_("VIM - Vi IMproved"), + "", + N_("version "), + N_("by Bram Moolenaar et al."), + #ifdef MODIFIED_BY + " ", + #endif + N_("Vim is open source and freely distributable"), + "", + N_("Help poor children in Uganda!"), + N_("type :help iccf for information "), + "", + N_("type :q to exit "), + N_("type :help or for on-line help"), + N_("type :help version8 for version info"), + NULL, + "", + N_("Running in Vi compatible mode"), + N_("type :set nocp for Vim defaults"), + N_("type :help cp-default for info on this"), + }; + #ifdef FEAT_GUI + static char *(gui_lines[]) = + { + NULL, + NULL, + NULL, + NULL, + #ifdef MODIFIED_BY + NULL, + #endif + NULL, + NULL, + NULL, + N_("menu Help->Orphans for information "), + NULL, + N_("Running modeless, typed text is inserted"), + N_("menu Edit->Global Settings->Toggle Insert Mode "), + N_(" for two modes "), + NULL, + NULL, + NULL, + N_("menu Edit->Global Settings->Toggle Vi Compatible"), + N_(" for Vim defaults "), + }; + #endif + + // blanklines = screen height - # message lines + blanklines = (int)Rows - ((sizeof(lines) / sizeof(char *)) - 1); + if (!p_cp) + blanklines += 4; // add 4 for not showing "Vi compatible" message + + // Don't overwrite a statusline. Depends on 'cmdheight'. + if (p_ls > 1) + blanklines -= Rows - topframe->fr_height; + if (blanklines < 0) + blanklines = 0; + + // Show the sponsor and register message one out of four times, the Uganda + // message two out of four times. + sponsor = (int)time(NULL); + sponsor = ((sponsor & 2) == 0) - ((sponsor & 4) == 0); + + // start displaying the message lines after half of the blank lines + row = blanklines / 2; + if ((row >= 2 && Columns >= 50) || colon) + { + for (i = 0; i < (int)(sizeof(lines) / sizeof(char *)); ++i) + { + p = lines[i]; + #ifdef FEAT_GUI + if (p_im && gui.in_use && gui_lines[i] != NULL) + p = gui_lines[i]; + #endif + if (p == NULL) + { + if (!p_cp) + break; + continue; + } + if (sponsor != 0) + { + if (strstr(p, "children") != NULL) + p = sponsor < 0 + ? N_("Sponsor Vim development!") + : N_("Become a registered Vim user!"); + else if (strstr(p, "iccf") != NULL) + p = sponsor < 0 + ? N_("type :help sponsor for information ") + : N_("type :help register for information "); + else if (strstr(p, "Orphans") != NULL) + p = N_("menu Help->Sponsor/Register for information "); + } + if (*p != NUL) + do_intro_line(row, (char_u *)_(p), i == 2, 0); + ++row; + } + } + + // Make the wait-return message appear just below the text. + if (colon) + msg_row = row; + } + + static void + do_intro_line( + int row, + char_u *mesg, + int add_version, + int attr) + { + char_u vers[20]; + int col; + char_u *p; + int l; + int clen; + #ifdef MODIFIED_BY + # define MODBY_LEN 150 + char_u modby[MODBY_LEN]; + + if (*mesg == ' ') + { + vim_strncpy(modby, (char_u *)_("Modified by "), MODBY_LEN - 1); + l = (int)STRLEN(modby); + vim_strncpy(modby + l, (char_u *)MODIFIED_BY, MODBY_LEN - l - 1); + mesg = modby; + } + #endif + + // Center the message horizontally. + col = vim_strsize(mesg); + if (add_version) + { + STRCPY(vers, mediumVersion); + if (highest_patch()) + { + // Check for 9.9x or 9.9xx, alpha/beta version + if (isalpha((int)vers[3])) + { + int len = (isalpha((int)vers[4])) ? 5 : 4; + sprintf((char *)vers + len, ".%d%s", highest_patch(), + mediumVersion + len); + } + else + sprintf((char *)vers + 3, ".%d", highest_patch()); + } + col += (int)STRLEN(vers); + } + col = (Columns - col) / 2; + if (col < 0) + col = 0; + + // Split up in parts to highlight <> items differently. + for (p = mesg; *p != NUL; p += l) + { + clen = 0; + for (l = 0; p[l] != NUL + && (l == 0 || (p[l] != '<' && p[l - 1] != '>')); ++l) + { + if (has_mbyte) + { + clen += ptr2cells(p + l); + l += (*mb_ptr2len)(p + l) - 1; + } + else + clen += byte2cells(p[l]); + } + screen_puts_len(p, l, row, col, *p == '<' ? HL_ATTR(HLF_8) : attr); + col += clen; + } + + // Add the version number to the version line. + if (add_version) + screen_puts(vers, row, col, 0); + } + + /* + * ":intro": clear screen, display intro screen and wait for return. + */ + void + ex_intro(exarg_T *eap UNUSED) + { + screenclear(); + intro_message(TRUE); + wait_return(TRUE); + } diff --git a/src/test/resources/unparser/diff/error03.txt b/src/test/resources/unparser/diff/error03.txt new file mode 100644 index 00000000..87a00a73 --- /dev/null +++ b/src/test/resources/unparser/diff/error03.txt @@ -0,0 +1,3935 @@ +textDiff: + /* vi:set ts=8 sts=4 sw=4: + * + * MzScheme interface by Sergey Khorev + * Based on work by Brent Fulgham + * (Based on lots of help from Matthew Flatt) + * + * This consists of six parts: + * 1. MzScheme interpreter main program + * 2. Routines that handle the external interface between MzScheme and + * Vim. + * 3. MzScheme input/output handlers: writes output via [e]msg(). + * 4. Implementation of the Vim Features for MzScheme + * 5. Vim Window-related Manipulation Functions. + * 6. Vim Buffer-related Manipulation Functions + * + * NOTES + * 1. Memory, allocated with scheme_malloc*, need not to be freed explicitly, + * garbage collector will do it self + * 2. Requires at least NORMAL features. I can't imagine why one may want + * to build with SMALL or TINY features but with MzScheme interface. + * 3. I don't use K&R-style functions. Anyways, MzScheme headers are ANSI. + */ + + #include "vim.h" + + #include "if_mzsch.h" + + /* Only do the following when the feature is enabled. Needed for "make + * depend". */ + #if defined(FEAT_MZSCHEME) || defined(PROTO) + ++/* ++ * scheme_register_tls_space is only available on 32-bit Windows until ++ * racket-6.3. See ++ * http://docs.racket-lang.org/inside/im_memoryalloc.html?q=scheme_register_tls_space ++ */ ++#if MZSCHEME_VERSION_MAJOR >= 500 && defined(WIN32) \ ++ && defined(USE_THREAD_LOCAL) \ ++ && (!defined(_WIN64) || MZSCHEME_VERSION_MAJOR >= 603) ++# define HAVE_TLS_SPACE 1 ++#endif ++ ++/* ++ * Since version 4.x precise GC requires trampolined startup. ++ * Futures and places in version 5.x need it too. ++ */ ++#if defined(MZ_PRECISE_GC) && MZSCHEME_VERSION_MAJOR >= 400 \ ++ || MZSCHEME_VERSION_MAJOR >= 500 \ ++ && (defined(MZ_USE_FUTURES) || defined(MZ_USE_PLACES)) ++# define TRAMPOLINED_MZVIM_STARTUP ++#endif ++ + /* Base data structures */ + #define SCHEME_VIMBUFFERP(obj) SAME_TYPE(SCHEME_TYPE(obj), mz_buffer_type) + #define SCHEME_VIMWINDOWP(obj) SAME_TYPE(SCHEME_TYPE(obj), mz_window_type) + + typedef struct + { + Scheme_Object so; + buf_T *buf; + } vim_mz_buffer; + + #define INVALID_BUFFER_VALUE ((buf_T *)(-1)) + + typedef struct + { + Scheme_Object so; + win_T *win; + } vim_mz_window; + + #define INVALID_WINDOW_VALUE ((win_T *)(-1)) + + /* + * Prims that form MzScheme Vim interface + */ + typedef struct + { + Scheme_Closed_Prim *prim; + char *name; + int mina; /* arity information */ + int maxa; + } Vim_Prim; + + typedef struct + { + char *name; + Scheme_Object *port; + } Port_Info; + + /* + *======================================================================== + * Vim-Control Commands + *======================================================================== + */ + /* + *======================================================================== + * Utility functions for the vim/mzscheme interface + *======================================================================== + */ + #ifdef HAVE_SANDBOX + static Scheme_Object *sandbox_file_guard(int, Scheme_Object **); + static Scheme_Object *sandbox_network_guard(int, Scheme_Object **); + static void sandbox_check(void); + #endif + /* Buffer-related commands */ + static Scheme_Object *buffer_new(buf_T *buf); + static Scheme_Object *get_buffer_by_name(void *, int, Scheme_Object **); + static Scheme_Object *get_buffer_by_num(void *, int, Scheme_Object **); + static Scheme_Object *get_buffer_count(void *, int, Scheme_Object **); + static Scheme_Object *get_buffer_line(void *, int, Scheme_Object **); + static Scheme_Object *get_buffer_line_list(void *, int, Scheme_Object **); + static Scheme_Object *get_buffer_name(void *, int, Scheme_Object **); + static Scheme_Object *get_buffer_num(void *, int, Scheme_Object **); + static Scheme_Object *get_buffer_size(void *, int, Scheme_Object **); + static Scheme_Object *get_curr_buffer(void *, int, Scheme_Object **); + static Scheme_Object *get_next_buffer(void *, int, Scheme_Object **); + static Scheme_Object *get_prev_buffer(void *, int, Scheme_Object **); + static Scheme_Object *mzscheme_open_buffer(void *, int, Scheme_Object **); + static Scheme_Object *set_buffer_line(void *, int, Scheme_Object **); + static Scheme_Object *set_buffer_line_list(void *, int, Scheme_Object **); + static Scheme_Object *insert_buffer_line_list(void *, int, Scheme_Object **); + static Scheme_Object *get_range_start(void *, int, Scheme_Object **); + static Scheme_Object *get_range_end(void *, int, Scheme_Object **); + static vim_mz_buffer *get_vim_curr_buffer(void); + + /* Window-related commands */ + static Scheme_Object *window_new(win_T *win); + static Scheme_Object *get_curr_win(void *, int, Scheme_Object **); + static Scheme_Object *get_window_count(void *, int, Scheme_Object **); + static Scheme_Object *get_window_by_num(void *, int, Scheme_Object **); + static Scheme_Object *get_window_num(void *, int, Scheme_Object **); + static Scheme_Object *get_window_buffer(void *, int, Scheme_Object **); + static Scheme_Object *get_window_height(void *, int, Scheme_Object **); + static Scheme_Object *set_window_height(void *, int, Scheme_Object **); + #ifdef FEAT_VERTSPLIT + static Scheme_Object *get_window_width(void *, int, Scheme_Object **); + static Scheme_Object *set_window_width(void *, int, Scheme_Object **); + #endif + static Scheme_Object *get_cursor(void *, int, Scheme_Object **); + static Scheme_Object *set_cursor(void *, int, Scheme_Object **); + static Scheme_Object *get_window_list(void *, int, Scheme_Object **); + static vim_mz_window *get_vim_curr_window(void); + + /* Vim-related commands */ + static Scheme_Object *mzscheme_beep(void *, int, Scheme_Object **); + static Scheme_Object *get_option(void *, int, Scheme_Object **); + static Scheme_Object *set_option(void *, int, Scheme_Object **); + static Scheme_Object *vim_command(void *, int, Scheme_Object **); + static Scheme_Object *vim_eval(void *, int, Scheme_Object **); + static Scheme_Object *vim_bufferp(void *data, int, Scheme_Object **); + static Scheme_Object *vim_windowp(void *data, int, Scheme_Object **); + static Scheme_Object *vim_buffer_validp(void *data, int, Scheme_Object **); + static Scheme_Object *vim_window_validp(void *data, int, Scheme_Object **); + + /* + *======================================================================== + * Internal Function Prototypes + *======================================================================== + */ + static int vim_error_check(void); + static int do_mzscheme_command(exarg_T *, void *, Scheme_Closed_Prim *what); +-static void startup_mzscheme(void); ++static int startup_mzscheme(void); + static char *string_to_line(Scheme_Object *obj); +-#if MZSCHEME_VERSION_MAJOR >= 500 ++#if MZSCHEME_VERSION_MAJOR >= 501 + # define OUTPUT_LEN_TYPE intptr_t + #else + # define OUTPUT_LEN_TYPE long + #endif + static void do_output(char *mesg, OUTPUT_LEN_TYPE len); + static void do_printf(char *format, ...); + static void do_flush(void); + static Scheme_Object *_apply_thunk_catch_exceptions( + Scheme_Object *, Scheme_Object **); + static Scheme_Object *extract_exn_message(Scheme_Object *v); + static Scheme_Object *do_eval(void *, int noargc, Scheme_Object **noargv); + static Scheme_Object *do_load(void *, int noargc, Scheme_Object **noargv); + static void register_vim_exn(void); + static vim_mz_buffer *get_buffer_arg(const char *fname, int argnum, + int argc, Scheme_Object **argv); + static vim_mz_window *get_window_arg(const char *fname, int argnum, + int argc, Scheme_Object **argv); + static int line_in_range(linenr_T, buf_T *); + static void check_line_range(linenr_T, buf_T *); + static void mz_fix_cursor(int lo, int hi, int extra); + + static int eval_with_exn_handling(void *, Scheme_Closed_Prim *, + Scheme_Object **ret); + static void make_modules(void); + static void init_exn_catching_apply(void); + static int mzscheme_env_main(Scheme_Env *env, int argc, char **argv); + static int mzscheme_init(void); + #ifdef FEAT_EVAL + static Scheme_Object *vim_to_mzscheme(typval_T *vim_value); + static Scheme_Object *vim_to_mzscheme_impl(typval_T *vim_value, int depth, + Scheme_Hash_Table *visited); + static int mzscheme_to_vim(Scheme_Object *obj, typval_T *tv); + static int mzscheme_to_vim_impl(Scheme_Object *obj, typval_T *tv, int depth, + Scheme_Hash_Table *visited); + static Scheme_Object *vim_funcref(void *data, int argc, Scheme_Object **argv); + #endif + + #ifdef MZ_PRECISE_GC + static int buffer_size_proc(void *obj UNUSED) + { + return gcBYTES_TO_WORDS(sizeof(vim_mz_buffer)); + } + static int buffer_mark_proc(void *obj) + { + return buffer_size_proc(obj); + } + static int buffer_fixup_proc(void *obj) + { + /* apparently not needed as the object will be uncollectable while + * the buffer is alive + */ + /* + vim_mz_buffer* buf = (vim_mz_buffer*) obj; + buf->buf->b_mzscheme_ref = GC_fixup_self(obj); + */ + return buffer_size_proc(obj); + } + static int window_size_proc(void *obj UNUSED) + { + return gcBYTES_TO_WORDS(sizeof(vim_mz_window)); + } + static int window_mark_proc(void *obj) + { + return window_size_proc(obj); + } + static int window_fixup_proc(void *obj) + { + /* apparently not needed as the object will be uncollectable while + * the window is alive + */ + /* + vim_mz_window* win = (vim_mz_window*) obj; + win->win->w_mzscheme_ref = GC_fixup_self(obj); + */ + return window_size_proc(obj); + } + /* with precise GC, w_mzscheme_ref and b_mzscheme_ref are immobile boxes + * containing pointers to a window/buffer + * with conservative GC these are simply pointers*/ + # define WINDOW_REF(win) *(vim_mz_window **)((win)->w_mzscheme_ref) + # define BUFFER_REF(buf) *(vim_mz_buffer **)((buf)->b_mzscheme_ref) + #else + # define WINDOW_REF(win) (vim_mz_window *)((win)->w_mzscheme_ref) + # define BUFFER_REF(buf) (vim_mz_buffer *)((buf)->b_mzscheme_ref) + #endif + + #ifdef DYNAMIC_MZSCHEME + static Scheme_Object *dll_scheme_eof; + static Scheme_Object *dll_scheme_false; + static Scheme_Object *dll_scheme_void; + static Scheme_Object *dll_scheme_null; + static Scheme_Object *dll_scheme_true; + + static Scheme_Thread **dll_scheme_current_thread_ptr; + + static void (**dll_scheme_console_printf_ptr)(char *str, ...); +-static void (**dll_scheme_console_output_ptr)(char *str, long len); ++static void (**dll_scheme_console_output_ptr)(char *str, OUTPUT_LEN_TYPE len); + static void (**dll_scheme_notify_multithread_ptr)(int on); + + static void *(*dll_GC_malloc)(size_t size_in_bytes); + static void *(*dll_GC_malloc_atomic)(size_t size_in_bytes); + static Scheme_Env *(*dll_scheme_basic_env)(void); + static void (*dll_scheme_check_threads)(void); + static void (*dll_scheme_register_static)(void *ptr, long size); + static void (*dll_scheme_set_stack_base)(void *base, int no_auto_statics); + static void (*dll_scheme_add_global)(const char *name, Scheme_Object *val, + Scheme_Env *env); + static void (*dll_scheme_add_global_symbol)(Scheme_Object *name, + Scheme_Object *val, Scheme_Env *env); + static Scheme_Object *(*dll_scheme_apply)(Scheme_Object *rator, int num_rands, + Scheme_Object **rands); + static Scheme_Object *(*dll_scheme_builtin_value)(const char *name); + # if MZSCHEME_VERSION_MAJOR >= 299 + static Scheme_Object *(*dll_scheme_byte_string_to_char_string)(Scheme_Object *s); ++static Scheme_Object *(*dll_scheme_make_path)(const char *chars); + # endif + static void (*dll_scheme_close_input_port)(Scheme_Object *port); + static void (*dll_scheme_count_lines)(Scheme_Object *port); + #if MZSCHEME_VERSION_MAJOR < 360 + static Scheme_Object *(*dll_scheme_current_continuation_marks)(void); + #else + static Scheme_Object *(*dll_scheme_current_continuation_marks)(Scheme_Object *prompt_tag); + #endif + static void (*dll_scheme_display)(Scheme_Object *obj, Scheme_Object *port); +-static char *(*dll_scheme_display_to_string)(Scheme_Object *obj, long *len); ++static char *(*dll_scheme_display_to_string)(Scheme_Object *obj, OUTPUT_LEN_TYPE *len); + static int (*dll_scheme_eq)(Scheme_Object *obj1, Scheme_Object *obj2); + static Scheme_Object *(*dll_scheme_do_eval)(Scheme_Object *obj, + int _num_rands, Scheme_Object **rands, int val); + static void (*dll_scheme_dont_gc_ptr)(void *p); + static Scheme_Object *(*dll_scheme_eval)(Scheme_Object *obj, Scheme_Env *env); + static Scheme_Object *(*dll_scheme_eval_string)(const char *str, + Scheme_Env *env); + static Scheme_Object *(*dll_scheme_eval_string_all)(const char *str, + Scheme_Env *env, int all); + static void (*dll_scheme_finish_primitive_module)(Scheme_Env *env); + # if MZSCHEME_VERSION_MAJOR < 299 + static char *(*dll_scheme_format)(char *format, int flen, int argc, + Scheme_Object **argv, long *rlen); + # else + static char *(*dll_scheme_format_utf8)(char *format, int flen, int argc, +- Scheme_Object **argv, long *rlen); ++ Scheme_Object **argv, OUTPUT_LEN_TYPE *rlen); + static Scheme_Object *(*dll_scheme_get_param)(Scheme_Config *c, int pos); + # endif + static void (*dll_scheme_gc_ptr_ok)(void *p); + # if MZSCHEME_VERSION_MAJOR < 299 + static char *(*dll_scheme_get_sized_string_output)(Scheme_Object *, + long *len); + # else + static char *(*dll_scheme_get_sized_byte_string_output)(Scheme_Object *, +- long *len); ++ OUTPUT_LEN_TYPE *len); + # endif + static Scheme_Object *(*dll_scheme_intern_symbol)(const char *name); + static Scheme_Object *(*dll_scheme_lookup_global)(Scheme_Object *symbol, + Scheme_Env *env); + static Scheme_Object *(*dll_scheme_make_closed_prim_w_arity) + (Scheme_Closed_Prim *prim, void *data, const char *name, mzshort mina, + mzshort maxa); + static Scheme_Object *(*dll_scheme_make_integer_value)(long i); + static Scheme_Object *(*dll_scheme_make_pair)(Scheme_Object *car, + Scheme_Object *cdr); + static Scheme_Object *(*dll_scheme_make_prim_w_arity)(Scheme_Prim *prim, + const char *name, mzshort mina, mzshort maxa); + # if MZSCHEME_VERSION_MAJOR < 299 + static Scheme_Object *(*dll_scheme_make_string)(const char *chars); + static Scheme_Object *(*dll_scheme_make_string_output_port)(); + # else + static Scheme_Object *(*dll_scheme_make_byte_string)(const char *chars); + static Scheme_Object *(*dll_scheme_make_byte_string_output_port)(); + # endif + static Scheme_Object *(*dll_scheme_make_struct_instance)(Scheme_Object *stype, + int argc, Scheme_Object **argv); + static Scheme_Object **(*dll_scheme_make_struct_names)(Scheme_Object *base, + Scheme_Object *field_names, int flags, int *count_out); + static Scheme_Object *(*dll_scheme_make_struct_type)(Scheme_Object *base, + Scheme_Object *parent, Scheme_Object *inspector, int num_fields, + int num_uninit_fields, Scheme_Object *uninit_val, + Scheme_Object *properties + # if MZSCHEME_VERSION_MAJOR >= 299 + , Scheme_Object *guard + # endif + ); + static Scheme_Object **(*dll_scheme_make_struct_values)( + Scheme_Object *struct_type, Scheme_Object **names, int count, + int flags); + static Scheme_Type (*dll_scheme_make_type)(const char *name); + static Scheme_Object *(*dll_scheme_make_vector)(int size, + Scheme_Object *fill); + static void *(*dll_scheme_malloc_fail_ok)(void *(*f)(size_t), size_t); + static Scheme_Object *(*dll_scheme_open_input_file)(const char *name, + const char *who); + static Scheme_Env *(*dll_scheme_primitive_module)(Scheme_Object *name, + Scheme_Env *for_env); + static int (*dll_scheme_proper_list_length)(Scheme_Object *list); + static void (*dll_scheme_raise)(Scheme_Object *exn); + static Scheme_Object *(*dll_scheme_read)(Scheme_Object *port); + static void (*dll_scheme_signal_error)(const char *msg, ...); + static void (*dll_scheme_wrong_type)(const char *name, const char *expected, + int which, int argc, Scheme_Object **argv); + # if MZSCHEME_VERSION_MAJOR >= 299 + static void (*dll_scheme_set_param)(Scheme_Config *c, int pos, + Scheme_Object *o); + static Scheme_Config *(*dll_scheme_current_config)(void); + static Scheme_Object *(*dll_scheme_char_string_to_byte_string) + (Scheme_Object *s); + static Scheme_Object *(*dll_scheme_char_string_to_path) + (Scheme_Object *s); + static void *(*dll_scheme_set_collects_path)(Scheme_Object *p); + # endif + static Scheme_Hash_Table *(*dll_scheme_make_hash_table)(int type); + static void (*dll_scheme_hash_set)(Scheme_Hash_Table *table, + Scheme_Object *key, Scheme_Object *value); + static Scheme_Object *(*dll_scheme_hash_get)(Scheme_Hash_Table *table, + Scheme_Object *key); + static Scheme_Object *(*dll_scheme_make_double)(double d); +-# ifdef INCLUDE_MZSCHEME_BASE + static Scheme_Object *(*dll_scheme_make_sized_byte_string)(char *chars, + long len, int copy); + static Scheme_Object *(*dll_scheme_namespace_require)(Scheme_Object *req); ++static Scheme_Object *(*dll_scheme_dynamic_wind)(void (*pre)(void *), Scheme_Object *(* volatile act)(void *), void (* volatile post)(void *), Scheme_Object *(*jmp_handler)(void *), void * volatile data); ++# ifdef MZ_PRECISE_GC ++static void *(*dll_GC_malloc_one_tagged)(size_t size_in_bytes); ++static void (*dll_GC_register_traversers)(short tag, Size_Proc size, Mark_Proc mark, Fixup_Proc fixup, int is_constant_size, int is_atomic); ++# endif ++# if MZSCHEME_VERSION_MAJOR >= 400 ++static void (*dll_scheme_init_collection_paths)(Scheme_Env *global_env, Scheme_Object *extra_dirs); ++static void **(*dll_scheme_malloc_immobile_box)(void *p); ++static void (*dll_scheme_free_immobile_box)(void **b); ++# endif ++# if MZSCHEME_VERSION_MAJOR >= 500 ++# ifdef TRAMPOLINED_MZVIM_STARTUP ++static int (*dll_scheme_main_setup)(int no_auto_statics, Scheme_Env_Main _main, int argc, char **argv); ++# if defined(IMPLEMENT_THREAD_LOCAL_VIA_WIN_TLS) || MZSCHEME_VERSION_MAJOR >= 603 ++static void (*dll_scheme_register_tls_space)(void *tls_space, int _tls_index); ++# endif ++# endif ++# if defined(IMPLEMENT_THREAD_LOCAL_VIA_WIN_TLS) || defined(IMPLEMENT_THREAD_LOCAL_EXTERNALLY_VIA_PROC) ++static Thread_Local_Variables *(*dll_scheme_external_get_thread_local_variables)(void); ++# endif ++# endif ++# if MZSCHEME_VERSION_MAJOR >= 600 ++static void (*dll_scheme_embedded_load)(intptr_t len, const char *s, int predefined); ++static void (*dll_scheme_register_embedded_load)(intptr_t len, const char *s); ++static void (*dll_scheme_set_config_path)(Scheme_Object *p); + # endif + + /* arrays are imported directly */ + # define scheme_eof dll_scheme_eof + # define scheme_false dll_scheme_false + # define scheme_void dll_scheme_void + # define scheme_null dll_scheme_null + # define scheme_true dll_scheme_true + + /* pointers are GetProceAddress'ed as pointers to pointer */ +-# define scheme_current_thread (*dll_scheme_current_thread_ptr) ++#if !defined(USE_THREAD_LOCAL) && !defined(LINK_EXTENSIONS_BY_TABLE) ++# define scheme_current_thread (*dll_scheme_current_thread_ptr) ++# endif + # define scheme_console_printf (*dll_scheme_console_printf_ptr) + # define scheme_console_output (*dll_scheme_console_output_ptr) + # define scheme_notify_multithread (*dll_scheme_notify_multithread_ptr) + + /* and functions in a usual way */ + # define GC_malloc dll_GC_malloc + # define GC_malloc_atomic dll_GC_malloc_atomic + + # define scheme_add_global dll_scheme_add_global + # define scheme_add_global_symbol dll_scheme_add_global_symbol + # define scheme_apply dll_scheme_apply + # define scheme_basic_env dll_scheme_basic_env + # define scheme_builtin_value dll_scheme_builtin_value + # if MZSCHEME_VERSION_MAJOR >= 299 + # define scheme_byte_string_to_char_string dll_scheme_byte_string_to_char_string ++# define scheme_make_path dll_scheme_make_path + # endif + # define scheme_check_threads dll_scheme_check_threads + # define scheme_close_input_port dll_scheme_close_input_port + # define scheme_count_lines dll_scheme_count_lines + # define scheme_current_continuation_marks \ + dll_scheme_current_continuation_marks + # define scheme_display dll_scheme_display + # define scheme_display_to_string dll_scheme_display_to_string + # define scheme_do_eval dll_scheme_do_eval + # define scheme_dont_gc_ptr dll_scheme_dont_gc_ptr + # define scheme_eq dll_scheme_eq + # define scheme_eval dll_scheme_eval + # define scheme_eval_string dll_scheme_eval_string + # define scheme_eval_string_all dll_scheme_eval_string_all + # define scheme_finish_primitive_module dll_scheme_finish_primitive_module + # if MZSCHEME_VERSION_MAJOR < 299 + # define scheme_format dll_scheme_format + # else + # define scheme_format_utf8 dll_scheme_format_utf8 + # endif + # define scheme_gc_ptr_ok dll_scheme_gc_ptr_ok + # if MZSCHEME_VERSION_MAJOR < 299 + # define scheme_get_sized_byte_string_output dll_scheme_get_sized_string_output + # else + # define scheme_get_sized_byte_string_output \ + dll_scheme_get_sized_byte_string_output + # define scheme_get_param dll_scheme_get_param + # endif + # define scheme_intern_symbol dll_scheme_intern_symbol + # define scheme_lookup_global dll_scheme_lookup_global + # define scheme_make_closed_prim_w_arity dll_scheme_make_closed_prim_w_arity + # define scheme_make_integer_value dll_scheme_make_integer_value + # define scheme_make_pair dll_scheme_make_pair + # define scheme_make_prim_w_arity dll_scheme_make_prim_w_arity + # if MZSCHEME_VERSION_MAJOR < 299 + # define scheme_make_byte_string dll_scheme_make_string + # define scheme_make_byte_string_output_port dll_scheme_make_string_output_port + # else + # define scheme_make_byte_string dll_scheme_make_byte_string + # define scheme_make_byte_string_output_port \ + dll_scheme_make_byte_string_output_port + # endif + # define scheme_make_struct_instance dll_scheme_make_struct_instance + # define scheme_make_struct_names dll_scheme_make_struct_names + # define scheme_make_struct_type dll_scheme_make_struct_type + # define scheme_make_struct_values dll_scheme_make_struct_values + # define scheme_make_type dll_scheme_make_type + # define scheme_make_vector dll_scheme_make_vector + # define scheme_malloc_fail_ok dll_scheme_malloc_fail_ok + # define scheme_open_input_file dll_scheme_open_input_file + # define scheme_primitive_module dll_scheme_primitive_module + # define scheme_proper_list_length dll_scheme_proper_list_length + # define scheme_raise dll_scheme_raise + # define scheme_read dll_scheme_read + # define scheme_register_static dll_scheme_register_static + # define scheme_set_stack_base dll_scheme_set_stack_base + # define scheme_signal_error dll_scheme_signal_error + # define scheme_wrong_type dll_scheme_wrong_type + # if MZSCHEME_VERSION_MAJOR >= 299 + # define scheme_set_param dll_scheme_set_param + # define scheme_current_config dll_scheme_current_config + # define scheme_char_string_to_byte_string \ + dll_scheme_char_string_to_byte_string + # define scheme_char_string_to_path \ + dll_scheme_char_string_to_path + # define scheme_set_collects_path dll_scheme_set_collects_path + # endif + # define scheme_make_hash_table dll_scheme_make_hash_table + # define scheme_hash_set dll_scheme_hash_set + # define scheme_hash_get dll_scheme_hash_get + # define scheme_make_double dll_scheme_make_double +-# ifdef INCLUDE_MZSCHEME_BASE +-# define scheme_make_sized_byte_string dll_scheme_make_sized_byte_string +-# define scheme_namespace_require dll_scheme_namespace_require ++# define scheme_make_sized_byte_string dll_scheme_make_sized_byte_string ++# define scheme_namespace_require dll_scheme_namespace_require ++# define scheme_dynamic_wind dll_scheme_dynamic_wind ++# ifdef MZ_PRECISE_GC ++# define GC_malloc_one_tagged dll_GC_malloc_one_tagged ++# define GC_register_traversers dll_GC_register_traversers ++# endif ++# if MZSCHEME_VERSION_MAJOR >= 400 ++# ifdef TRAMPOLINED_MZVIM_STARTUP ++# define scheme_main_setup dll_scheme_main_setup ++# if defined(IMPLEMENT_THREAD_LOCAL_VIA_WIN_TLS) || MZSCHEME_VERSION_MAJOR >= 603 ++# define scheme_register_tls_space dll_scheme_register_tls_space ++# endif ++# endif ++# define scheme_init_collection_paths dll_scheme_init_collection_paths ++# define scheme_malloc_immobile_box dll_scheme_malloc_immobile_box ++# define scheme_free_immobile_box dll_scheme_free_immobile_box ++# endif ++# if MZSCHEME_VERSION_MAJOR >= 600 ++# define scheme_embedded_load dll_scheme_embedded_load ++# define scheme_register_embedded_load dll_scheme_register_embedded_load ++# define scheme_set_config_path dll_scheme_set_config_path ++# endif ++ ++# if MZSCHEME_VERSION_MAJOR >= 500 ++# if defined(IMPLEMENT_THREAD_LOCAL_VIA_WIN_TLS) || defined(IMPLEMENT_THREAD_LOCAL_EXTERNALLY_VIA_PROC) ++/* define as function for macro in schshread.h */ ++Thread_Local_Variables * ++scheme_external_get_thread_local_variables(void) ++{ ++ return dll_scheme_external_get_thread_local_variables(); ++} ++# endif + # endif + + typedef struct + { + char *name; + void **ptr; + } Thunk_Info; + + static Thunk_Info mzgc_imports[] = { + {"GC_malloc", (void **)&dll_GC_malloc}, + {"GC_malloc_atomic", (void **)&dll_GC_malloc_atomic}, + {NULL, NULL}}; + + static Thunk_Info mzsch_imports[] = { + {"scheme_eof", (void **)&dll_scheme_eof}, + {"scheme_false", (void **)&dll_scheme_false}, + {"scheme_void", (void **)&dll_scheme_void}, + {"scheme_null", (void **)&dll_scheme_null}, + {"scheme_true", (void **)&dll_scheme_true}, ++#if !defined(USE_THREAD_LOCAL) && !defined(LINK_EXTENSIONS_BY_TABLE) + {"scheme_current_thread", (void **)&dll_scheme_current_thread_ptr}, ++#endif + {"scheme_console_printf", (void **)&dll_scheme_console_printf_ptr}, + {"scheme_console_output", (void **)&dll_scheme_console_output_ptr}, + {"scheme_notify_multithread", + (void **)&dll_scheme_notify_multithread_ptr}, + {"scheme_add_global", (void **)&dll_scheme_add_global}, + {"scheme_add_global_symbol", (void **)&dll_scheme_add_global_symbol}, + {"scheme_apply", (void **)&dll_scheme_apply}, + {"scheme_basic_env", (void **)&dll_scheme_basic_env}, + # if MZSCHEME_VERSION_MAJOR >= 299 + {"scheme_byte_string_to_char_string", (void **)&dll_scheme_byte_string_to_char_string}, ++ {"scheme_make_path", (void **)&dll_scheme_make_path}, + # endif + {"scheme_builtin_value", (void **)&dll_scheme_builtin_value}, + {"scheme_check_threads", (void **)&dll_scheme_check_threads}, + {"scheme_close_input_port", (void **)&dll_scheme_close_input_port}, + {"scheme_count_lines", (void **)&dll_scheme_count_lines}, + {"scheme_current_continuation_marks", + (void **)&dll_scheme_current_continuation_marks}, + {"scheme_display", (void **)&dll_scheme_display}, + {"scheme_display_to_string", (void **)&dll_scheme_display_to_string}, + {"scheme_do_eval", (void **)&dll_scheme_do_eval}, + {"scheme_dont_gc_ptr", (void **)&dll_scheme_dont_gc_ptr}, + {"scheme_eq", (void **)&dll_scheme_eq}, + {"scheme_eval", (void **)&dll_scheme_eval}, + {"scheme_eval_string", (void **)&dll_scheme_eval_string}, + {"scheme_eval_string_all", (void **)&dll_scheme_eval_string_all}, + {"scheme_finish_primitive_module", + (void **)&dll_scheme_finish_primitive_module}, + # if MZSCHEME_VERSION_MAJOR < 299 + {"scheme_format", (void **)&dll_scheme_format}, + # else + {"scheme_format_utf8", (void **)&dll_scheme_format_utf8}, + {"scheme_get_param", (void **)&dll_scheme_get_param}, + #endif + {"scheme_gc_ptr_ok", (void **)&dll_scheme_gc_ptr_ok}, + # if MZSCHEME_VERSION_MAJOR < 299 + {"scheme_get_sized_string_output", + (void **)&dll_scheme_get_sized_string_output}, + # else + {"scheme_get_sized_byte_string_output", + (void **)&dll_scheme_get_sized_byte_string_output}, + #endif + {"scheme_intern_symbol", (void **)&dll_scheme_intern_symbol}, + {"scheme_lookup_global", (void **)&dll_scheme_lookup_global}, + {"scheme_make_closed_prim_w_arity", + (void **)&dll_scheme_make_closed_prim_w_arity}, + {"scheme_make_integer_value", (void **)&dll_scheme_make_integer_value}, + {"scheme_make_pair", (void **)&dll_scheme_make_pair}, + {"scheme_make_prim_w_arity", (void **)&dll_scheme_make_prim_w_arity}, + # if MZSCHEME_VERSION_MAJOR < 299 + {"scheme_make_string", (void **)&dll_scheme_make_string}, + {"scheme_make_string_output_port", + (void **)&dll_scheme_make_string_output_port}, + # else + {"scheme_make_byte_string", (void **)&dll_scheme_make_byte_string}, + {"scheme_make_byte_string_output_port", + (void **)&dll_scheme_make_byte_string_output_port}, + # endif + {"scheme_make_struct_instance", + (void **)&dll_scheme_make_struct_instance}, + {"scheme_make_struct_names", (void **)&dll_scheme_make_struct_names}, + {"scheme_make_struct_type", (void **)&dll_scheme_make_struct_type}, + {"scheme_make_struct_values", (void **)&dll_scheme_make_struct_values}, + {"scheme_make_type", (void **)&dll_scheme_make_type}, + {"scheme_make_vector", (void **)&dll_scheme_make_vector}, + {"scheme_malloc_fail_ok", (void **)&dll_scheme_malloc_fail_ok}, + {"scheme_open_input_file", (void **)&dll_scheme_open_input_file}, + {"scheme_primitive_module", (void **)&dll_scheme_primitive_module}, + {"scheme_proper_list_length", (void **)&dll_scheme_proper_list_length}, + {"scheme_raise", (void **)&dll_scheme_raise}, + {"scheme_read", (void **)&dll_scheme_read}, + {"scheme_register_static", (void **)&dll_scheme_register_static}, + {"scheme_set_stack_base", (void **)&dll_scheme_set_stack_base}, + {"scheme_signal_error", (void **)&dll_scheme_signal_error}, + {"scheme_wrong_type", (void **)&dll_scheme_wrong_type}, + # if MZSCHEME_VERSION_MAJOR >= 299 + {"scheme_set_param", (void **)&dll_scheme_set_param}, + {"scheme_current_config", (void **)&dll_scheme_current_config}, + {"scheme_char_string_to_byte_string", + (void **)&dll_scheme_char_string_to_byte_string}, + {"scheme_char_string_to_path", (void **)&dll_scheme_char_string_to_path}, + {"scheme_set_collects_path", (void **)&dll_scheme_set_collects_path}, + # endif + {"scheme_make_hash_table", (void **)&dll_scheme_make_hash_table}, + {"scheme_hash_set", (void **)&dll_scheme_hash_set}, + {"scheme_hash_get", (void **)&dll_scheme_hash_get}, + {"scheme_make_double", (void **)&dll_scheme_make_double}, +-# ifdef INCLUDE_MZSCHEME_BASE + {"scheme_make_sized_byte_string", (void **)&dll_scheme_make_sized_byte_string}, + {"scheme_namespace_require", (void **)&dll_scheme_namespace_require}, +-#endif ++ {"scheme_dynamic_wind", (void **)&dll_scheme_dynamic_wind}, ++# ifdef MZ_PRECISE_GC ++ {"GC_malloc_one_tagged", (void **)&dll_GC_malloc_one_tagged}, ++ {"GC_register_traversers", (void **)&dll_GC_register_traversers}, ++# endif ++# if MZSCHEME_VERSION_MAJOR >= 400 ++# ifdef TRAMPOLINED_MZVIM_STARTUP ++ {"scheme_main_setup", (void **)&dll_scheme_main_setup}, ++# if defined(IMPLEMENT_THREAD_LOCAL_VIA_WIN_TLS) || MZSCHEME_VERSION_MAJOR >= 603 ++ {"scheme_register_tls_space", (void **)&dll_scheme_register_tls_space}, ++# endif ++# endif ++ {"scheme_init_collection_paths", (void **)&dll_scheme_init_collection_paths}, ++ {"scheme_malloc_immobile_box", (void **)&dll_scheme_malloc_immobile_box}, ++ {"scheme_free_immobile_box", (void **)&dll_scheme_free_immobile_box}, ++# endif ++# if MZSCHEME_VERSION_MAJOR >= 500 ++# if defined(IMPLEMENT_THREAD_LOCAL_VIA_WIN_TLS) || defined(IMPLEMENT_THREAD_LOCAL_EXTERNALLY_VIA_PROC) ++ {"scheme_external_get_thread_local_variables", (void **)&dll_scheme_external_get_thread_local_variables}, ++# endif ++# endif ++# if MZSCHEME_VERSION_MAJOR >= 600 ++ {"scheme_embedded_load", (void **)&dll_scheme_embedded_load}, ++ {"scheme_register_embedded_load", (void **)&dll_scheme_register_embedded_load}, ++ {"scheme_set_config_path", (void **)&dll_scheme_set_config_path}, ++# endif + {NULL, NULL}}; + + static HINSTANCE hMzGC = 0; + static HINSTANCE hMzSch = 0; + + static void dynamic_mzscheme_end(void); + static int mzscheme_runtime_link_init(char *sch_dll, char *gc_dll, + int verbose); + + static int + mzscheme_runtime_link_init(char *sch_dll, char *gc_dll, int verbose) + { + Thunk_Info *thunk = NULL; + + if (hMzGC && hMzSch) + return OK; + hMzSch = vimLoadLib(sch_dll); + hMzGC = vimLoadLib(gc_dll); + + if (!hMzGC) + { + if (verbose) + EMSG2(_(e_loadlib), gc_dll); + return FAIL; + } + + if (!hMzSch) + { + if (verbose) + EMSG2(_(e_loadlib), sch_dll); + return FAIL; + } + + for (thunk = mzsch_imports; thunk->name; thunk++) + { + if ((*thunk->ptr = + (void *)GetProcAddress(hMzSch, thunk->name)) == NULL) + { + FreeLibrary(hMzSch); + hMzSch = 0; + FreeLibrary(hMzGC); + hMzGC = 0; + if (verbose) + EMSG2(_(e_loadfunc), thunk->name); + return FAIL; + } + } + for (thunk = mzgc_imports; thunk->name; thunk++) + { + if ((*thunk->ptr = + (void *)GetProcAddress(hMzGC, thunk->name)) == NULL) + { + FreeLibrary(hMzSch); + hMzSch = 0; + FreeLibrary(hMzGC); + hMzGC = 0; + if (verbose) + EMSG2(_(e_loadfunc), thunk->name); + return FAIL; + } + } + return OK; + } + + int + mzscheme_enabled(int verbose) + { + return mzscheme_runtime_link_init( + DYNAMIC_MZSCH_DLL, DYNAMIC_MZGC_DLL, verbose) == OK; + } + + static void + dynamic_mzscheme_end(void) + { + if (hMzSch) + { + FreeLibrary(hMzSch); + hMzSch = 0; + } + if (hMzGC) + { + FreeLibrary(hMzGC); + hMzGC = 0; + } + } + #endif /* DYNAMIC_MZSCHEME */ + + #if MZSCHEME_VERSION_MAJOR < 299 + # define GUARANTEED_STRING_ARG(proc, num) GUARANTEE_STRING(proc, num) + #else + static Scheme_Object * + guaranteed_byte_string_arg(char *proc, int num, int argc, Scheme_Object **argv) + { + if (SCHEME_BYTE_STRINGP(argv[num])) + { + return argv[num]; + } + else if (SCHEME_CHAR_STRINGP(argv[num])) + { + Scheme_Object *tmp = NULL; + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, argv[num]); + MZ_GC_VAR_IN_REG(1, tmp); + MZ_GC_REG(); + tmp = scheme_char_string_to_byte_string(argv[num]); + MZ_GC_UNREG(); + return tmp; + } + else + scheme_wrong_type(proc, "string", num, argc, argv); + /* unreachable */ + return scheme_void; + } + # define GUARANTEED_STRING_ARG(proc, num) guaranteed_byte_string_arg(proc, num, argc, argv) + #endif + + /* need to put it here for dynamic stuff to work */ + #if defined(INCLUDE_MZSCHEME_BASE) + # include "mzscheme_base.c" +-#elif MZSCHEME_VERSION_MAJOR >= 400 +-# error MzScheme >=4 must include mzscheme_base.c, for MinGW32 you need to define MZSCHEME_GENERATE_BASE=yes + #endif + + /* + *======================================================================== + * 1. MzScheme interpreter startup + *======================================================================== + */ + + static Scheme_Type mz_buffer_type; + static Scheme_Type mz_window_type; + + static int initialized = FALSE; ++#ifdef DYNAMIC_MZSCHEME ++static int disabled = FALSE; ++#endif ++static int load_base_module_failed = FALSE; + + /* global environment */ + static Scheme_Env *environment = NULL; + /* output/error handlers */ + static Scheme_Object *curout = NULL; + static Scheme_Object *curerr = NULL; + /* exn:vim exception */ + static Scheme_Object *exn_catching_apply = NULL; + static Scheme_Object *exn_p = NULL; + static Scheme_Object *exn_message = NULL; + static Scheme_Object *vim_exn = NULL; /* Vim Error exception */ + + #if !defined(MZ_PRECISE_GC) || MZSCHEME_VERSION_MAJOR < 400 + static void *stack_base = NULL; + #endif + + static long range_start; + static long range_end; + + /* MzScheme threads scheduling stuff */ + static int mz_threads_allow = 0; + + #if defined(FEAT_GUI_W32) + static void CALLBACK timer_proc(HWND, UINT, UINT, DWORD); + static UINT timer_id = 0; + #elif defined(FEAT_GUI_GTK) + static gint timer_proc(gpointer); + static guint timer_id = 0; + #elif defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA) + static void timer_proc(XtPointer, XtIntervalId *); + static XtIntervalId timer_id = (XtIntervalId)0; + #elif defined(FEAT_GUI_MAC) + pascal void timer_proc(EventLoopTimerRef, void *); + static EventLoopTimerRef timer_id = NULL; + static EventLoopTimerUPP timerUPP; + #endif + + #ifndef FEAT_GUI_W32 /* Win32 console and Unix */ + void + mzvim_check_threads(void) + { + /* Last time MzScheme threads were scheduled */ + static time_t mz_last_time = 0; + + if (mz_threads_allow && p_mzq > 0) + { + time_t now = time(NULL); + + if ((now - mz_last_time) * 1000 > p_mzq) + { + mz_last_time = now; + scheme_check_threads(); + } + } + } + #endif + + #ifdef MZSCHEME_GUI_THREADS + static void setup_timer(void); + static void remove_timer(void); + + /* timers are presented in GUI only */ + # if defined(FEAT_GUI_W32) + static void CALLBACK + timer_proc(HWND hwnd UNUSED, UINT uMsg UNUSED, UINT idEvent UNUSED, DWORD dwTime UNUSED) + # elif defined(FEAT_GUI_GTK) + static gint + timer_proc(gpointer data UNUSED) + # elif defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA) + static void + timer_proc(XtPointer timed_out UNUSED, XtIntervalId *interval_id UNUSED) + # elif defined(FEAT_GUI_MAC) + pascal void + timer_proc(EventLoopTimerRef theTimer UNUSED, void *userData UNUSED) + # endif + { + scheme_check_threads(); + # if defined(FEAT_GUI_GTK) + return TRUE; /* continue receiving notifications */ + # elif defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA) + /* renew timeout */ + if (mz_threads_allow && p_mzq > 0) + timer_id = XtAppAddTimeOut(app_context, p_mzq, + timer_proc, NULL); + # endif + } + + static void + setup_timer(void) + { + # if defined(FEAT_GUI_W32) + timer_id = SetTimer(NULL, 0, p_mzq, timer_proc); + # elif defined(FEAT_GUI_GTK) + timer_id = gtk_timeout_add((guint32)p_mzq, (GtkFunction)timer_proc, NULL); + # elif defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA) + timer_id = XtAppAddTimeOut(app_context, p_mzq, timer_proc, NULL); + # elif defined(FEAT_GUI_MAC) + timerUPP = NewEventLoopTimerUPP(timer_proc); + InstallEventLoopTimer(GetMainEventLoop(), p_mzq * kEventDurationMillisecond, + p_mzq * kEventDurationMillisecond, timerUPP, NULL, &timer_id); + # endif + } + + static void + remove_timer(void) + { + # if defined(FEAT_GUI_W32) + KillTimer(NULL, timer_id); + # elif defined(FEAT_GUI_GTK) + gtk_timeout_remove(timer_id); + # elif defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_ATHENA) + XtRemoveTimeOut(timer_id); + # elif defined(FEAT_GUI_MAC) + RemoveEventLoopTimer(timer_id); + DisposeEventLoopTimerUPP(timerUPP); + # endif + timer_id = 0; + } + + void + mzvim_reset_timer(void) + { + if (timer_id != 0) + remove_timer(); + if (mz_threads_allow && p_mzq > 0 && gui.in_use) + setup_timer(); + } + + #endif /* MZSCHEME_GUI_THREADS */ + + static void + notify_multithread(int on) + { + mz_threads_allow = on; + #ifdef MZSCHEME_GUI_THREADS + if (on && timer_id == 0 && p_mzq > 0 && gui.in_use) + setup_timer(); + if (!on && timer_id != 0) + remove_timer(); + #endif + } + + void + mzscheme_end(void) + { ++ /* We can not unload the DLL. Racket's thread might be still alive. */ ++#if 0 + #ifdef DYNAMIC_MZSCHEME + dynamic_mzscheme_end(); + #endif ++#endif + } + +-/* +- * scheme_register_tls_space is only available on 32-bit Windows. +- * See http://docs.racket-lang.org/inside/im_memoryalloc.html?q=scheme_register_tls_space +- */ +-#if MZSCHEME_VERSION_MAJOR >= 500 && defined(WIN32) \ +- && defined(USE_THREAD_LOCAL) && !defined(_WIN64) +-# define HAVE_TLS_SPACE 1 ++#if HAVE_TLS_SPACE ++# if defined(_MSC_VER) + static __declspec(thread) void *tls_space; +-#endif +- +-/* +- * Since version 4.x precise GC requires trampolined startup. +- * Futures and places in version 5.x need it too. +- */ +-#if defined(MZ_PRECISE_GC) && MZSCHEME_VERSION_MAJOR >= 400 \ +- || MZSCHEME_VERSION_MAJOR >= 500 && (defined(MZ_USE_FUTURES) || defined(MZ_USE_PLACES)) +-# ifdef DYNAMIC_MZSCHEME +-# error Precise GC v.4+ or Racket with futures/places do not support dynamic MzScheme ++extern intptr_t _tls_index; ++# elif defined(__MINGW32__) ++static __thread void *tls_space; ++extern intptr_t _tls_index; ++# else ++static THREAD_LOCAL void *tls_space; ++static intptr_t _tls_index = 0; + # endif +-# define TRAMPOLINED_MZVIM_STARTUP + #endif + + int + mzscheme_main(int argc, char** argv) + { ++#ifdef DYNAMIC_MZSCHEME ++ /* ++ * Racket requires trampolined startup. We can not load it later. ++ * If dynamic dll loading is failed, disable it. ++ */ ++ if (!mzscheme_enabled(FALSE)) ++ { ++ disabled = TRUE; ++ return vim_main2(argc, argv); ++ } ++#endif + #ifdef HAVE_TLS_SPACE +- scheme_register_tls_space(&tls_space, 0); ++ scheme_register_tls_space(&tls_space, _tls_index); + #endif + #ifdef TRAMPOLINED_MZVIM_STARTUP + return scheme_main_setup(TRUE, mzscheme_env_main, argc, argv); + #else + return mzscheme_env_main(NULL, argc, argv); + #endif + } + + static int + mzscheme_env_main(Scheme_Env *env, int argc, char **argv) + { + int vim_main_result; + #ifdef TRAMPOLINED_MZVIM_STARTUP + /* Scheme has created the environment for us */ + environment = env; + #else + # ifdef MZ_PRECISE_GC + Scheme_Object *dummy = NULL; + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, dummy); + + stack_base = &__gc_var_stack__; + # else + int dummy = 0; + stack_base = (void *)&dummy; + # endif + #endif + + /* mzscheme_main is called as a trampoline from main. + * We trampoline into vim_main2 + * Passing argc, argv through from mzscheme_main + */ + vim_main_result = vim_main2(argc, argv); + #if !defined(TRAMPOLINED_MZVIM_STARTUP) && defined(MZ_PRECISE_GC) + /* releasing dummy */ + MZ_GC_REG(); + MZ_GC_UNREG(); + #endif + return vim_main_result; + } + +- static void ++ static Scheme_Object* ++load_base_module(void *data) ++{ ++ scheme_namespace_require(scheme_intern_symbol((char *)data)); ++ return scheme_null; ++} ++ ++ static Scheme_Object * ++load_base_module_on_error(void *data) ++{ ++ load_base_module_failed = TRUE; ++ return scheme_null; ++} ++ ++ static int + startup_mzscheme(void) + { + #ifndef TRAMPOLINED_MZVIM_STARTUP + scheme_set_stack_base(stack_base, 1); + #endif + + #ifndef TRAMPOLINED_MZVIM_STARTUP + /* in newer versions of precise GC the initial env has been created */ + environment = scheme_basic_env(); + #endif + + MZ_REGISTER_STATIC(environment); + MZ_REGISTER_STATIC(curout); + MZ_REGISTER_STATIC(curerr); + MZ_REGISTER_STATIC(exn_catching_apply); + MZ_REGISTER_STATIC(exn_p); + MZ_REGISTER_STATIC(exn_message); + MZ_REGISTER_STATIC(vim_exn); + + MZ_GC_CHECK(); + + #ifdef INCLUDE_MZSCHEME_BASE +- { +- /* +- * versions 4.x do not provide Scheme bindings by default +- * we need to add them explicitly +- */ +- Scheme_Object *scheme_base_symbol = NULL; +- MZ_GC_DECL_REG(1); +- MZ_GC_VAR_IN_REG(0, scheme_base_symbol); +- MZ_GC_REG(); +- /* invoke function from generated and included mzscheme_base.c */ +- declare_modules(environment); +- scheme_base_symbol = scheme_intern_symbol("scheme/base"); +- MZ_GC_CHECK(); +- scheme_namespace_require(scheme_base_symbol); +- MZ_GC_CHECK(); +- MZ_GC_UNREG(); +- } ++ /* invoke function from generated and included mzscheme_base.c */ ++ declare_modules(environment); + #endif +- register_vim_exn(); +- /* use new environment to initialise exception handling */ +- init_exn_catching_apply(); + +- /* redirect output */ +- scheme_console_output = do_output; +- scheme_console_printf = do_printf; +- +-#ifdef MZSCHEME_COLLECTS + /* setup 'current-library-collection-paths' parameter */ + # if MZSCHEME_VERSION_MAJOR >= 299 +-# ifdef MACOS + { +- Scheme_Object *coll_byte_string = NULL; +- Scheme_Object *coll_char_string = NULL; +- Scheme_Object *coll_path = NULL; ++ Scheme_Object *coll_path = NULL; ++ int mustfree = FALSE; ++ char_u *s; + +- MZ_GC_DECL_REG(3); +- MZ_GC_VAR_IN_REG(0, coll_byte_string); +- MZ_GC_VAR_IN_REG(1, coll_char_string); +- MZ_GC_VAR_IN_REG(2, coll_path); ++ MZ_GC_DECL_REG(1); ++ MZ_GC_VAR_IN_REG(0, coll_path); + MZ_GC_REG(); +- coll_byte_string = scheme_make_byte_string(MZSCHEME_COLLECTS); +- MZ_GC_CHECK(); +- coll_char_string = scheme_byte_string_to_char_string(coll_byte_string); +- MZ_GC_CHECK(); +- coll_path = scheme_char_string_to_path(coll_char_string); +- MZ_GC_CHECK(); +- scheme_set_collects_path(coll_path); +- MZ_GC_CHECK(); +- MZ_GC_UNREG(); +- } +-# else +- { +- Scheme_Object *coll_byte_string = NULL; +- Scheme_Object *coll_char_string = NULL; +- Scheme_Object *coll_path = NULL; +- Scheme_Object *coll_pair = NULL; +- Scheme_Config *config = NULL; +- +- MZ_GC_DECL_REG(5); +- MZ_GC_VAR_IN_REG(0, coll_byte_string); +- MZ_GC_VAR_IN_REG(1, coll_char_string); +- MZ_GC_VAR_IN_REG(2, coll_path); +- MZ_GC_VAR_IN_REG(3, coll_pair); +- MZ_GC_VAR_IN_REG(4, config); +- MZ_GC_REG(); +- coll_byte_string = scheme_make_byte_string(MZSCHEME_COLLECTS); +- MZ_GC_CHECK(); +- coll_char_string = scheme_byte_string_to_char_string(coll_byte_string); +- MZ_GC_CHECK(); +- coll_path = scheme_char_string_to_path(coll_char_string); +- MZ_GC_CHECK(); +- coll_pair = scheme_make_pair(coll_path, scheme_null); +- MZ_GC_CHECK(); +- config = scheme_current_config(); +- MZ_GC_CHECK(); +- scheme_set_param(config, MZCONFIG_COLLECTION_PATHS, coll_pair); +- MZ_GC_CHECK(); +- MZ_GC_UNREG(); +- } ++ /* workaround for dynamic loading on windows */ ++ s = vim_getenv("PLTCOLLECTS", &mustfree); ++ if (s != NULL) ++ { ++ coll_path = scheme_make_path(s); ++ MZ_GC_CHECK(); ++ if (mustfree) ++ vim_free(s); ++ } ++# ifdef MZSCHEME_COLLECTS ++ if (coll_path == NULL) ++ { ++ coll_path = scheme_make_path(MZSCHEME_COLLECTS); ++ MZ_GC_CHECK(); ++ } + # endif ++ if (coll_path != NULL) ++ { ++ scheme_set_collects_path(coll_path); ++ MZ_GC_CHECK(); ++ } ++ MZ_GC_UNREG(); ++ } + # else ++# ifdef MZSCHEME_COLLECTS + { + Scheme_Object *coll_string = NULL; + Scheme_Object *coll_pair = NULL; + Scheme_Config *config = NULL; + + MZ_GC_DECL_REG(3); + MZ_GC_VAR_IN_REG(0, coll_string); + MZ_GC_VAR_IN_REG(1, coll_pair); + MZ_GC_VAR_IN_REG(2, config); + MZ_GC_REG(); + coll_string = scheme_make_byte_string(MZSCHEME_COLLECTS); + MZ_GC_CHECK(); + coll_pair = scheme_make_pair(coll_string, scheme_null); + MZ_GC_CHECK(); + config = scheme_current_config(); + MZ_GC_CHECK(); + scheme_set_param(config, MZCONFIG_COLLECTION_PATHS, coll_pair); + MZ_GC_CHECK(); + MZ_GC_UNREG(); + } + # endif + #endif ++ ++# if MZSCHEME_VERSION_MAJOR >= 600 ++ { ++ Scheme_Object *config_path = NULL; ++ int mustfree = FALSE; ++ char_u *s; ++ ++ MZ_GC_DECL_REG(1); ++ MZ_GC_VAR_IN_REG(0, config_path); ++ MZ_GC_REG(); ++ /* workaround for dynamic loading on windows */ ++ s = vim_getenv("PLTCONFIGDIR", &mustfree); ++ if (s != NULL) ++ { ++ config_path = scheme_make_path(s); ++ MZ_GC_CHECK(); ++ if (mustfree) ++ vim_free(s); ++ } ++#ifdef MZSCHEME_CONFIGDIR ++ if (config_path == NULL) ++ { ++ config_path = scheme_make_path(MZSCHEME_CONFIGDIR); ++ MZ_GC_CHECK(); ++ } ++#endif ++ if (config_path != NULL) ++ { ++ scheme_set_config_path(config_path); ++ MZ_GC_CHECK(); ++ } ++ MZ_GC_UNREG(); ++ } ++# endif ++ ++#if MZSCHEME_VERSION_MAJOR >= 400 ++ scheme_init_collection_paths(environment, scheme_null); ++#endif ++ ++ /* ++ * versions 4.x do not provide Scheme bindings by default ++ * we need to add them explicitly ++ */ ++ { ++ /* use error handler to avoid abort */ ++ scheme_dynamic_wind(NULL, load_base_module, NULL, ++ load_base_module_on_error, "racket/base"); ++ if (load_base_module_failed) ++ { ++ load_base_module_failed = FALSE; ++ scheme_dynamic_wind(NULL, load_base_module, NULL, ++ load_base_module_on_error, "scheme/base"); ++ if (load_base_module_failed) ++ return -1; ++ } ++ } ++ ++ register_vim_exn(); ++ /* use new environment to initialise exception handling */ ++ init_exn_catching_apply(); ++ ++ /* redirect output */ ++ scheme_console_output = do_output; ++ scheme_console_printf = do_printf; ++ + #ifdef HAVE_SANDBOX + { + Scheme_Object *make_security_guard = NULL; + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, make_security_guard); + MZ_GC_REG(); + + #if MZSCHEME_VERSION_MAJOR < 400 + { + Scheme_Object *make_security_guard_symbol = NULL; + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, make_security_guard_symbol); + MZ_GC_REG(); + make_security_guard_symbol = scheme_intern_symbol("make-security-guard"); + MZ_GC_CHECK(); + make_security_guard = scheme_lookup_global( + make_security_guard_symbol, environment); + MZ_GC_UNREG(); + } + #else + make_security_guard = scheme_builtin_value("make-security-guard"); + MZ_GC_CHECK(); + #endif + + /* setup sandbox guards */ + if (make_security_guard != NULL) + { + Scheme_Object *args[3] = {NULL, NULL, NULL}; + Scheme_Object *guard = NULL; + Scheme_Config *config = NULL; + MZ_GC_DECL_REG(5); + MZ_GC_ARRAY_VAR_IN_REG(0, args, 3); + MZ_GC_VAR_IN_REG(3, guard); + MZ_GC_VAR_IN_REG(4, config); + MZ_GC_REG(); + config = scheme_current_config(); + MZ_GC_CHECK(); + args[0] = scheme_get_param(config, MZCONFIG_SECURITY_GUARD); + MZ_GC_CHECK(); + args[1] = scheme_make_prim_w_arity(sandbox_file_guard, + "sandbox-file-guard", 3, 3); + args[2] = scheme_make_prim_w_arity(sandbox_network_guard, + "sandbox-network-guard", 4, 4); + guard = scheme_apply(make_security_guard, 3, args); + MZ_GC_CHECK(); + scheme_set_param(config, MZCONFIG_SECURITY_GUARD, guard); + MZ_GC_CHECK(); + MZ_GC_UNREG(); + } + MZ_GC_UNREG(); + } + #endif + /* Create buffer and window types for use in Scheme code */ + mz_buffer_type = scheme_make_type(""); + MZ_GC_CHECK(); + mz_window_type = scheme_make_type(""); + MZ_GC_CHECK(); + #ifdef MZ_PRECISE_GC + GC_register_traversers(mz_buffer_type, + buffer_size_proc, buffer_mark_proc, buffer_fixup_proc, + TRUE, TRUE); + GC_register_traversers(mz_window_type, + window_size_proc, window_mark_proc, window_fixup_proc, + TRUE, TRUE); + #endif + + make_modules(); + + /* + * setup callback to receive notifications + * whether thread scheduling is (or not) required + */ + scheme_notify_multithread = notify_multithread; ++ ++ return 0; + } + + /* + * This routine is called for each new invocation of MzScheme + * to make sure things are properly initialized. + */ + static int + mzscheme_init(void) + { + if (!initialized) + { + #ifdef DYNAMIC_MZSCHEME +- if (!mzscheme_enabled(TRUE)) ++ if (disabled || !mzscheme_enabled(TRUE)) + { + EMSG(_("E815: Sorry, this command is disabled, the MzScheme libraries could not be loaded.")); + return -1; + } + #endif +- startup_mzscheme(); ++ if (load_base_module_failed || startup_mzscheme()) ++ { ++ EMSG(_("Exxx: Sorry, this command is disabled, the MzScheme's racket/base module could not be loaded.")); ++ return -1; ++ } + initialized = TRUE; + } + { + Scheme_Config *config = NULL; + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, config); + MZ_GC_REG(); + config = scheme_current_config(); + MZ_GC_CHECK(); + /* recreate ports each call effectively clearing these ones */ + curout = scheme_make_byte_string_output_port(); + MZ_GC_CHECK(); + curerr = scheme_make_byte_string_output_port(); + MZ_GC_CHECK(); + scheme_set_param(config, MZCONFIG_OUTPUT_PORT, curout); + MZ_GC_CHECK(); + scheme_set_param(config, MZCONFIG_ERROR_PORT, curerr); + MZ_GC_CHECK(); + MZ_GC_UNREG(); + } + + return 0; + } + + /* + *======================================================================== + * 2. External Interface + *======================================================================== + */ + + /* + * Evaluate command with exception handling + */ + static int + eval_with_exn_handling(void *data, Scheme_Closed_Prim *what, Scheme_Object **ret) + { + Scheme_Object *value = NULL; + Scheme_Object *exn = NULL; + Scheme_Object *prim = NULL; + + MZ_GC_DECL_REG(3); + MZ_GC_VAR_IN_REG(0, value); + MZ_GC_VAR_IN_REG(1, exn); + MZ_GC_VAR_IN_REG(2, prim); + MZ_GC_REG(); + + prim = scheme_make_closed_prim_w_arity(what, data, "mzvim", 0, 0); + MZ_GC_CHECK(); + value = _apply_thunk_catch_exceptions(prim, &exn); + MZ_GC_CHECK(); + + if (!value) + { + value = extract_exn_message(exn); + /* Got an exn? */ + if (value) + { + scheme_display(value, curerr); /* Send to stderr-vim */ + MZ_GC_CHECK(); + do_flush(); + } + MZ_GC_UNREG(); + /* `raise' was called on some arbitrary value */ + return FAIL; + } + + if (ret != NULL) /* if pointer to retval supported give it up */ + *ret = value; + /* Print any result, as long as it's not a void */ + else if (!SCHEME_VOIDP(value)) + { + scheme_display(value, curout); /* Send to stdout-vim */ + MZ_GC_CHECK(); + } + + do_flush(); + MZ_GC_UNREG(); + return OK; + } + + /* :mzscheme */ + static int + do_mzscheme_command(exarg_T *eap, void *data, Scheme_Closed_Prim *what) + { + if (mzscheme_init()) + return FAIL; + + range_start = eap->line1; + range_end = eap->line2; + + return eval_with_exn_handling(data, what, NULL); + } + + /* + * Routine called by VIM when deleting a buffer + */ + void + mzscheme_buffer_free(buf_T *buf) + { + if (buf->b_mzscheme_ref) + { + vim_mz_buffer *bp = NULL; + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, bp); + MZ_GC_REG(); + + bp = BUFFER_REF(buf); + bp->buf = INVALID_BUFFER_VALUE; + #ifndef MZ_PRECISE_GC + scheme_gc_ptr_ok(bp); + #else + scheme_free_immobile_box(buf->b_mzscheme_ref); + #endif + buf->b_mzscheme_ref = NULL; + MZ_GC_CHECK(); + MZ_GC_UNREG(); + } + } + + /* + * Routine called by VIM when deleting a Window + */ + void + mzscheme_window_free(win_T *win) + { + if (win->w_mzscheme_ref) + { + vim_mz_window *wp = NULL; + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, wp); + MZ_GC_REG(); + wp = WINDOW_REF(win); + wp->win = INVALID_WINDOW_VALUE; + #ifndef MZ_PRECISE_GC + scheme_gc_ptr_ok(wp); + #else + scheme_free_immobile_box(win->w_mzscheme_ref); + #endif + win->w_mzscheme_ref = NULL; + MZ_GC_CHECK(); + MZ_GC_UNREG(); + } + } + + /* + * ":mzscheme" (or ":mz") + */ + void + ex_mzscheme(exarg_T *eap) + { + char_u *script; + + script = script_get(eap, eap->arg); + if (!eap->skip) + { + if (script == NULL) + do_mzscheme_command(eap, eap->arg, do_eval); + else + { + do_mzscheme_command(eap, script, do_eval); + vim_free(script); + } + } + } + + static Scheme_Object * + do_load(void *data, int noargc UNUSED, Scheme_Object **noargv UNUSED) + { + Scheme_Object *expr = NULL; + Scheme_Object *result = NULL; + char *file = NULL; + Port_Info *pinfo = (Port_Info *)data; + + MZ_GC_DECL_REG(3); + MZ_GC_VAR_IN_REG(0, expr); + MZ_GC_VAR_IN_REG(1, result); + MZ_GC_VAR_IN_REG(2, file); + MZ_GC_REG(); + + file = (char *)scheme_malloc_fail_ok(scheme_malloc_atomic, MAXPATHL + 1); + MZ_GC_CHECK(); + + /* make Vim expansion */ + expand_env((char_u *)pinfo->name, (char_u *)file, MAXPATHL); + pinfo->port = scheme_open_input_file(file, "mzfile"); + MZ_GC_CHECK(); + scheme_count_lines(pinfo->port); /* to get accurate read error location*/ + MZ_GC_CHECK(); + + /* Like REPL but print only last result */ + while (!SCHEME_EOFP(expr = scheme_read(pinfo->port))) + { + result = scheme_eval(expr, environment); + MZ_GC_CHECK(); + } + + /* errors will be caught in do_mzscheme_command and ex_mzfile */ + scheme_close_input_port(pinfo->port); + MZ_GC_CHECK(); + pinfo->port = NULL; + MZ_GC_UNREG(); + return result; + } + + /* :mzfile */ + void + ex_mzfile(exarg_T *eap) + { + Port_Info pinfo = {NULL, NULL}; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, pinfo.port); + MZ_GC_REG(); + + pinfo.name = (char *)eap->arg; + if (do_mzscheme_command(eap, &pinfo, do_load) != OK + && pinfo.port != NULL) /* looks like port was not closed */ + { + scheme_close_input_port(pinfo.port); + MZ_GC_CHECK(); + } + MZ_GC_UNREG(); + } + + + /* + *======================================================================== + * Exception handling code -- cribbed form the MzScheme sources and + * Matthew Flatt's "Inside PLT MzScheme" document. + *======================================================================== + */ + static void + init_exn_catching_apply(void) + { + if (!exn_catching_apply) + { + char *e = + "(lambda (thunk) " + "(with-handlers ([void (lambda (exn) (cons #f exn))]) " + "(cons #t (thunk))))"; + + exn_catching_apply = scheme_eval_string(e, environment); + MZ_GC_CHECK(); + exn_p = scheme_builtin_value("exn?"); + MZ_GC_CHECK(); + exn_message = scheme_builtin_value("exn-message"); + MZ_GC_CHECK(); + } + } + + /* + * This function applies a thunk, returning the Scheme value if there's + * no exception, otherwise returning NULL and setting *exn to the raised + * value (usually an exn structure). + */ + static Scheme_Object * + _apply_thunk_catch_exceptions(Scheme_Object *f, Scheme_Object **exn) + { + Scheme_Object *v; + + v = _scheme_apply(exn_catching_apply, 1, &f); + /* v is a pair: (cons #t value) or (cons #f exn) */ + + if (SCHEME_TRUEP(SCHEME_CAR(v))) + return SCHEME_CDR(v); + else + { + *exn = SCHEME_CDR(v); + return NULL; + } + } + + static Scheme_Object * + extract_exn_message(Scheme_Object *v) + { + if (SCHEME_TRUEP(_scheme_apply(exn_p, 1, &v))) + return _scheme_apply(exn_message, 1, &v); + else + return NULL; /* Not an exn structure */ + } + + static Scheme_Object * + do_eval(void *s, int noargc UNUSED, Scheme_Object **noargv UNUSED) + { + return scheme_eval_string_all((char *)s, environment, TRUE); + } + + /* + *======================================================================== + * 3. MzScheme I/O Handlers + *======================================================================== + */ + static void + do_intrnl_output(char *mesg, int error) + { + char *p, *prev; + + prev = mesg; + p = strchr(prev, '\n'); + while (p) + { + *p = '\0'; + if (error) + EMSG(prev); + else + MSG(prev); + prev = p + 1; + p = strchr(prev, '\n'); + } + + if (error) + EMSG(prev); + else + MSG(prev); + } + + static void + do_output(char *mesg, OUTPUT_LEN_TYPE len UNUSED) + { + /* TODO: use len, the string may not be NUL terminated */ + do_intrnl_output(mesg, 0); + } + + static void + do_err_output(char *mesg) + { + do_intrnl_output(mesg, 1); + } + + static void + do_printf(char *format, ...) + { + do_intrnl_output(format, 1); + } + + static void + do_flush(void) + { + char *buff; + OUTPUT_LEN_TYPE length; + + buff = scheme_get_sized_byte_string_output(curerr, &length); + MZ_GC_CHECK(); + if (length) + { + do_err_output(buff); + return; + } + + buff = scheme_get_sized_byte_string_output(curout, &length); + MZ_GC_CHECK(); + if (length) + do_output(buff, length); + } + + /* + *======================================================================== + * 4. Implementation of the Vim Features for MzScheme + *======================================================================== + */ + + /* (command {command-string}) */ + static Scheme_Object * + vim_command(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + Scheme_Object *cmd = NULL; + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, cmd); + MZ_GC_REG(); + cmd = GUARANTEED_STRING_ARG(prim->name, 0); + + /* may be use do_cmdline_cmd? */ + do_cmdline(BYTE_STRING_VALUE(cmd), NULL, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE); + update_screen(VALID); + + MZ_GC_UNREG(); + raise_if_error(); + return scheme_void; + } + + /* (eval {expr-string}) */ + static Scheme_Object * + vim_eval(void *data UNUSED, int argc UNUSED, Scheme_Object **argv UNUSED) + { + #ifdef FEAT_EVAL + Vim_Prim *prim = (Vim_Prim *)data; + Scheme_Object *result = NULL; + typval_T *vim_result; + Scheme_Object *expr = NULL; + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, result); + MZ_GC_VAR_IN_REG(1, expr); + MZ_GC_REG(); + expr = GUARANTEED_STRING_ARG(prim->name, 0); + + vim_result = eval_expr(BYTE_STRING_VALUE(expr), NULL); + + if (vim_result == NULL) + raise_vim_exn(_("invalid expression")); + + result = vim_to_mzscheme(vim_result); + MZ_GC_CHECK(); + free_tv(vim_result); + + MZ_GC_UNREG(); + return result; + #else + raise_vim_exn(_("expressions disabled at compile time")); + /* unreachable */ + return scheme_false; + #endif + } + + /* (range-start) */ + static Scheme_Object * + get_range_start(void *data UNUSED, int argc UNUSED, Scheme_Object **argv UNUSED) + { + return scheme_make_integer(range_start); + } + + /* (range-end) */ + static Scheme_Object * + get_range_end(void *data UNUSED, int argc UNUSED, Scheme_Object **argv UNUSED) + { + return scheme_make_integer(range_end); + } + + /* (beep) */ + static Scheme_Object * + mzscheme_beep(void *data UNUSED, int argc UNUSED, Scheme_Object **argv UNUSED) + { + vim_beep(BO_LANG); + return scheme_void; + } + + static Scheme_Object *M_global = NULL; + + /* (get-option {option-name}) [buffer/window] */ + static Scheme_Object * + get_option(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + long value; + char *strval; + int rc; + Scheme_Object *rval = NULL; + Scheme_Object *name = NULL; + int opt_flags = 0; + buf_T *save_curb = curbuf; + win_T *save_curw = curwin; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, rval); + MZ_GC_VAR_IN_REG(1, name); + MZ_GC_REG(); + + name = GUARANTEED_STRING_ARG(prim->name, 0); + + if (argc > 1) + { + if (M_global == NULL) + { + MZ_REGISTER_STATIC(M_global); + M_global = scheme_intern_symbol("global"); + MZ_GC_CHECK(); + } + + if (argv[1] == M_global) + opt_flags = OPT_GLOBAL; + else if (SCHEME_VIMBUFFERP(argv[1])) + { + curbuf = get_valid_buffer(argv[1]); + opt_flags = OPT_LOCAL; + } + else if (SCHEME_VIMWINDOWP(argv[1])) + { + win_T *win = get_valid_window(argv[1]); + + curwin = win; + curbuf = win->w_buffer; + opt_flags = OPT_LOCAL; + } + else + scheme_wrong_type(prim->name, "vim-buffer/window", 1, argc, argv); + } + + rc = get_option_value(BYTE_STRING_VALUE(name), &value, (char_u **)&strval, opt_flags); + curbuf = save_curb; + curwin = save_curw; + + switch (rc) + { + case 1: + MZ_GC_UNREG(); + return scheme_make_integer_value(value); + case 0: + rval = scheme_make_byte_string(strval); + MZ_GC_CHECK(); + vim_free(strval); + MZ_GC_UNREG(); + return rval; + case -1: + case -2: + MZ_GC_UNREG(); + raise_vim_exn(_("hidden option")); + case -3: + MZ_GC_UNREG(); + raise_vim_exn(_("unknown option")); + } + /* unreachable */ + return scheme_void; + } + + /* (set-option {option-changing-string} [buffer/window]) */ + static Scheme_Object * + set_option(void *data, int argc, Scheme_Object **argv) + { + char_u *command = NULL; + int opt_flags = 0; + buf_T *save_curb = curbuf; + win_T *save_curw = curwin; + Vim_Prim *prim = (Vim_Prim *)data; + Scheme_Object *cmd = NULL; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, cmd); + MZ_GC_REG(); + cmd = GUARANTEED_STRING_ARG(prim->name, 0); + + if (argc > 1) + { + if (M_global == NULL) + { + MZ_REGISTER_STATIC(M_global); + M_global = scheme_intern_symbol("global"); + MZ_GC_CHECK(); + } + + if (argv[1] == M_global) + opt_flags = OPT_GLOBAL; + else if (SCHEME_VIMBUFFERP(argv[1])) + { + curbuf = get_valid_buffer(argv[1]); + opt_flags = OPT_LOCAL; + } + else if (SCHEME_VIMWINDOWP(argv[1])) + { + win_T *win = get_valid_window(argv[1]); + curwin = win; + curbuf = win->w_buffer; + opt_flags = OPT_LOCAL; + } + else + scheme_wrong_type(prim->name, "vim-buffer/window", 1, argc, argv); + } + + /* do_set can modify cmd, make copy */ + command = vim_strsave(BYTE_STRING_VALUE(cmd)); + MZ_GC_UNREG(); + do_set(command, opt_flags); + vim_free(command); + update_screen(NOT_VALID); + curbuf = save_curb; + curwin = save_curw; + raise_if_error(); + return scheme_void; + } + + /* + *=========================================================================== + * 5. Vim Window-related Manipulation Functions + *=========================================================================== + */ + + /* (curr-win) */ + static Scheme_Object * + get_curr_win(void *data UNUSED, int argc UNUSED, Scheme_Object **argv UNUSED) + { + return (Scheme_Object *)get_vim_curr_window(); + } + + /* (win-count) */ + static Scheme_Object * + get_window_count(void *data UNUSED, int argc UNUSED, Scheme_Object **argv UNUSED) + { + int n = 0; + #ifdef FEAT_WINDOWS + win_T *w; + + for (w = firstwin; w != NULL; w = w->w_next) + #endif + ++n; + return scheme_make_integer(n); + } + + /* (get-win-list [buffer]) */ + static Scheme_Object * + get_window_list(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_buffer *buf; + Scheme_Object *list; + win_T *w = firstwin; + + buf = get_buffer_arg(prim->name, 0, argc, argv); + list = scheme_null; + + #ifdef FEAT_WINDOWS + for ( ; w != NULL; w = w->w_next) + #endif + if (w->w_buffer == buf->buf) + { + list = scheme_make_pair(window_new(w), list); + MZ_GC_CHECK(); + } + + return list; + } + + static Scheme_Object * + window_new(win_T *win) + { + vim_mz_window *self = NULL; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, self); + + /* We need to handle deletion of windows underneath us. + * If we add a "w_mzscheme_ref" field to the win_T structure, + * then we can get at it in win_free() in vim. + * + * On a win_free() we set the Scheme object's win_T *field + * to an invalid value. We trap all uses of a window + * object, and reject them if the win_T *field is invalid. + */ + if (win->w_mzscheme_ref != NULL) + return (Scheme_Object *)WINDOW_REF(win); + + MZ_GC_REG(); + self = scheme_malloc_fail_ok(scheme_malloc_tagged, sizeof(vim_mz_window)); + vim_memset(self, 0, sizeof(vim_mz_window)); + #ifndef MZ_PRECISE_GC + scheme_dont_gc_ptr(self); /* because win isn't visible to GC */ + #else + win->w_mzscheme_ref = scheme_malloc_immobile_box(NULL); + #endif + MZ_GC_CHECK(); + WINDOW_REF(win) = self; + MZ_GC_CHECK(); + self->win = win; + self->so.type = mz_window_type; + + MZ_GC_UNREG(); + return (Scheme_Object *)self; + } + + /* (get-win-num [window]) */ + static Scheme_Object * + get_window_num(void *data UNUSED, int argc UNUSED, Scheme_Object **argv UNUSED) + { + int nr = 1; + #ifdef FEAT_WINDOWS + Vim_Prim *prim = (Vim_Prim *)data; + win_T *win = get_window_arg(prim->name, 0, argc, argv)->win; + win_T *wp; + + for (wp = firstwin; wp != win; wp = wp->w_next) + #endif + ++nr; + + return scheme_make_integer(nr); + } + + /* (get-win-by-num {windownum}) */ + static Scheme_Object * + get_window_by_num(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + win_T *win = firstwin; + int fnum; + + fnum = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 0)); + if (fnum < 1) + scheme_signal_error(_("window index is out of range")); + + #ifdef FEAT_WINDOWS + for ( ; win != NULL; win = win->w_next, --fnum) + #endif + if (fnum == 1) /* to be 1-based */ + return window_new(win); + + return scheme_false; + } + + /* (get-win-buffer [window]) */ + static Scheme_Object * + get_window_buffer(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_window *win = get_window_arg(prim->name, 0, argc, argv); + + return buffer_new(win->win->w_buffer); + } + + /* (get-win-height [window]) */ + static Scheme_Object * + get_window_height(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_window *win = get_window_arg(prim->name, 0, argc, argv); + + return scheme_make_integer(win->win->w_height); + } + + /* (set-win-height {height} [window]) */ + static Scheme_Object * + set_window_height(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_window *win; + win_T *savewin; + int height; + + win = get_window_arg(prim->name, 1, argc, argv); + height = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 0)); + + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + + savewin = curwin; + curwin = win->win; + win_setheight(height); + curwin = savewin; + + raise_if_error(); + return scheme_void; + } + + #ifdef FEAT_VERTSPLIT + /* (get-win-width [window]) */ + static Scheme_Object * + get_window_width(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_window *win = get_window_arg(prim->name, 0, argc, argv); + + return scheme_make_integer(W_WIDTH(win->win)); + } + + /* (set-win-width {width} [window]) */ + static Scheme_Object * + set_window_width(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_window *win; + win_T *savewin; + int width = 0; + + win = get_window_arg(prim->name, 1, argc, argv); + width = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 0)); + + # ifdef FEAT_GUI + need_mouse_correct = TRUE; + # endif + + savewin = curwin; + curwin = win->win; + win_setwidth(width); + curwin = savewin; + + raise_if_error(); + return scheme_void; + } + #endif + + /* (get-cursor [window]) -> (line . col) */ + static Scheme_Object * + get_cursor(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_window *win; + pos_T pos; + + win = get_window_arg(prim->name, 0, argc, argv); + pos = win->win->w_cursor; + return scheme_make_pair(scheme_make_integer_value((long)pos.lnum), + scheme_make_integer_value((long)pos.col + 1)); + } + + /* (set-cursor (line . col) [window]) */ + static Scheme_Object * + set_cursor(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_window *win; + long lnum = 0; + long col = 0; + + #ifdef HAVE_SANDBOX + sandbox_check(); + #endif + win = get_window_arg(prim->name, 1, argc, argv); + GUARANTEE_PAIR(prim->name, 0); + + if (!SCHEME_INTP(SCHEME_CAR(argv[0])) + || !SCHEME_INTP(SCHEME_CDR(argv[0]))) + scheme_wrong_type(prim->name, "integer pair", 0, argc, argv); + + lnum = SCHEME_INT_VAL(SCHEME_CAR(argv[0])); + col = SCHEME_INT_VAL(SCHEME_CDR(argv[0])) - 1; + + check_line_range(lnum, win->win->w_buffer); + /* don't know how to catch invalid column value */ + + win->win->w_cursor.lnum = lnum; + win->win->w_cursor.col = col; + update_screen(VALID); + + raise_if_error(); + return scheme_void; + } + /* + *=========================================================================== + * 6. Vim Buffer-related Manipulation Functions + *=========================================================================== + */ + + /* (open-buff {filename}) */ + static Scheme_Object * + mzscheme_open_buffer(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + int num = 0; + Scheme_Object *onum = NULL; + Scheme_Object *buf = NULL; + Scheme_Object *fname; + + MZ_GC_DECL_REG(3); + MZ_GC_VAR_IN_REG(0, onum); + MZ_GC_VAR_IN_REG(1, buf); + MZ_GC_VAR_IN_REG(2, fname); + MZ_GC_REG(); + fname = GUARANTEED_STRING_ARG(prim->name, 0); + + #ifdef HAVE_SANDBOX + sandbox_check(); + #endif + /* TODO make open existing file */ + num = buflist_add(BYTE_STRING_VALUE(fname), BLN_LISTED | BLN_CURBUF); + + if (num == 0) + raise_vim_exn(_("couldn't open buffer")); + + onum = scheme_make_integer(num); + buf = get_buffer_by_num(data, 1, &onum); + MZ_GC_UNREG(); + return buf; + } + + /* (get-buff-by-num {buffernum}) */ + static Scheme_Object * + get_buffer_by_num(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + buf_T *buf; + int fnum; + + fnum = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 0)); + + for (buf = firstbuf; buf; buf = buf->b_next) + if (buf->b_fnum == fnum) + return buffer_new(buf); + + return scheme_false; + } + + /* (get-buff-by-name {buffername}) */ + static Scheme_Object * + get_buffer_by_name(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + buf_T *buf; + Scheme_Object *buffer = NULL; + Scheme_Object *fname = NULL; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, buffer); + MZ_GC_VAR_IN_REG(1, fname); + MZ_GC_REG(); + fname = GUARANTEED_STRING_ARG(prim->name, 0); + buffer = scheme_false; + + for (buf = firstbuf; buf; buf = buf->b_next) + { + if (buf->b_ffname == NULL || buf->b_sfname == NULL) + /* empty string */ + { + if (BYTE_STRING_VALUE(fname)[0] == NUL) + buffer = buffer_new(buf); + } + else if (!fnamecmp(buf->b_ffname, BYTE_STRING_VALUE(fname)) + || !fnamecmp(buf->b_sfname, BYTE_STRING_VALUE(fname))) + { + /* either short or long filename matches */ + buffer = buffer_new(buf); + } + } + + MZ_GC_UNREG(); + return buffer; + } + + /* (get-next-buff [buffer]) */ + static Scheme_Object * + get_next_buffer(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + buf_T *buf = get_buffer_arg(prim->name, 0, argc, argv)->buf; + + if (buf->b_next == NULL) + return scheme_false; + else + return buffer_new(buf->b_next); + } + + /* (get-prev-buff [buffer]) */ + static Scheme_Object * + get_prev_buffer(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + buf_T *buf = get_buffer_arg(prim->name, 0, argc, argv)->buf; + + if (buf->b_prev == NULL) + return scheme_false; + else + return buffer_new(buf->b_prev); + } + + /* (get-buff-num [buffer]) */ + static Scheme_Object * + get_buffer_num(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_buffer *buf = get_buffer_arg(prim->name, 0, argc, argv); + + return scheme_make_integer(buf->buf->b_fnum); + } + + /* (buff-count) */ + static Scheme_Object * + get_buffer_count(void *data UNUSED, int argc UNUSED, Scheme_Object **argv UNUSED) + { + buf_T *b; + int n = 0; + + for (b = firstbuf; b; b = b->b_next) ++n; + return scheme_make_integer(n); + } + + /* (get-buff-name [buffer]) */ + static Scheme_Object * + get_buffer_name(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_buffer *buf = get_buffer_arg(prim->name, 0, argc, argv); + + return scheme_make_byte_string((char *)buf->buf->b_ffname); + } + + /* (curr-buff) */ + static Scheme_Object * + get_curr_buffer(void *data UNUSED, int argc UNUSED, Scheme_Object **argv UNUSED) + { + return (Scheme_Object *)get_vim_curr_buffer(); + } + + static Scheme_Object * + buffer_new(buf_T *buf) + { + vim_mz_buffer *self = NULL; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, self); + + /* We need to handle deletion of buffers underneath us. + * If we add a "b_mzscheme_ref" field to the buf_T structure, + * then we can get at it in buf_freeall() in vim. + */ + if (buf->b_mzscheme_ref) + return (Scheme_Object *)BUFFER_REF(buf); + + MZ_GC_REG(); + self = scheme_malloc_fail_ok(scheme_malloc_tagged, sizeof(vim_mz_buffer)); + vim_memset(self, 0, sizeof(vim_mz_buffer)); + #ifndef MZ_PRECISE_GC + scheme_dont_gc_ptr(self); /* because buf isn't visible to GC */ + #else + buf->b_mzscheme_ref = scheme_malloc_immobile_box(NULL); + #endif + MZ_GC_CHECK(); + BUFFER_REF(buf) = self; + MZ_GC_CHECK(); + self->buf = buf; + self->so.type = mz_buffer_type; + + MZ_GC_UNREG(); + return (Scheme_Object *)self; + } + + /* + * (get-buff-size [buffer]) + * + * Get the size (number of lines) in the current buffer. + */ + static Scheme_Object * + get_buffer_size(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_buffer *buf = get_buffer_arg(prim->name, 0, argc, argv); + + return scheme_make_integer(buf->buf->b_ml.ml_line_count); + } + + /* + * (get-buff-line {linenr} [buffer]) + * + * Get a line from the specified buffer. The line number is + * in Vim format (1-based). The line is returned as a MzScheme + * string object. + */ + static Scheme_Object * + get_buffer_line(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_buffer *buf; + int linenr; + char_u *line; + + buf = get_buffer_arg(prim->name, 1, argc, argv); + linenr = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 0)); + line = ml_get_buf(buf->buf, (linenr_T)linenr, FALSE); + + raise_if_error(); + return scheme_make_byte_string((char *)line); + } + + + /* + * (get-buff-line-list {start} {end} [buffer]) + * + * Get a list of lines from the specified buffer. The line numbers + * are in Vim format (1-based). The range is from lo up to, but not + * including, hi. The list is returned as a list of string objects. + */ + static Scheme_Object * + get_buffer_line_list(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_buffer *buf; + int i, hi, lo, n; + Scheme_Object *list = NULL; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, list); + MZ_GC_REG(); + + buf = get_buffer_arg(prim->name, 2, argc, argv); + list = scheme_null; + hi = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 1)); + lo = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 0)); + + /* + * Handle some error conditions + */ + if (lo < 0) + lo = 0; + + if (hi < 0) + hi = 0; + if (hi < lo) + hi = lo; + + n = hi - lo; + + for (i = n; i >= 0; --i) + { + Scheme_Object *str = scheme_make_byte_string( + (char *)ml_get_buf(buf->buf, (linenr_T)(lo+i), FALSE)); + raise_if_error(); + + /* Set the list item */ + list = scheme_make_pair(str, list); + MZ_GC_CHECK(); + } + MZ_GC_UNREG(); + return list; + } + + /* + * (set-buff-line {linenr} {string/#f} [buffer]) + * + * Replace a line in the specified buffer. The line number is + * in Vim format (1-based). The replacement line is given as + * an MzScheme string object. The object is checked for validity + * and correct format. An exception is thrown if the values are not + * the correct format. + * + * It returns a Scheme Object that indicates the length of the + * string changed. + */ + static Scheme_Object * + set_buffer_line(void *data, int argc, Scheme_Object **argv) + { + /* First of all, we check the value of the supplied MzScheme object. + * There are three cases: + * 1. #f - this is a deletion. + * 2. A string - this is a replacement. + * 3. Anything else - this is an error. + */ + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_buffer *buf; + Scheme_Object *line = NULL; + char *save; + int n; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, line); + MZ_GC_REG(); + + #ifdef HAVE_SANDBOX + sandbox_check(); + #endif + n = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 0)); + if (!SCHEME_STRINGP(argv[1]) && !SCHEME_FALSEP(argv[1])) + scheme_wrong_type(prim->name, "string or #f", 1, argc, argv); + line = argv[1]; + buf = get_buffer_arg(prim->name, 2, argc, argv); + + check_line_range(n, buf->buf); + + if (SCHEME_FALSEP(line)) + { + buf_T *savebuf = curbuf; + + curbuf = buf->buf; + + if (u_savedel((linenr_T)n, 1L) == FAIL) + { + curbuf = savebuf; + raise_vim_exn(_("cannot save undo information")); + } + else if (ml_delete((linenr_T)n, FALSE) == FAIL) + { + curbuf = savebuf; + raise_vim_exn(_("cannot delete line")); + } + if (buf->buf == curwin->w_buffer) + mz_fix_cursor(n, n + 1, -1); + deleted_lines_mark((linenr_T)n, 1L); + + curbuf = savebuf; + + MZ_GC_UNREG(); + raise_if_error(); + return scheme_void; + } + else + { + /* Otherwise it's a line */ + buf_T *savebuf = curbuf; + + save = string_to_line(line); + + curbuf = buf->buf; + + if (u_savesub((linenr_T)n) == FAIL) + { + curbuf = savebuf; + vim_free(save); + raise_vim_exn(_("cannot save undo information")); + } + else if (ml_replace((linenr_T)n, (char_u *)save, TRUE) == FAIL) + { + curbuf = savebuf; + vim_free(save); + raise_vim_exn(_("cannot replace line")); + } + else + { + vim_free(save); + changed_bytes((linenr_T)n, 0); + } + + curbuf = savebuf; + + /* Check that the cursor is not beyond the end of the line now. */ + if (buf->buf == curwin->w_buffer) + check_cursor_col(); + + MZ_GC_UNREG(); + raise_if_error(); + return scheme_void; + } + } + + static void + free_array(char **array) + { + char **curr = array; + while (*curr != NULL) + vim_free(*curr++); + vim_free(array); + } + + /* + * (set-buff-line-list {start} {end} {string-list/#f/null} [buffer]) + * + * Replace a range of lines in the specified buffer. The line numbers are in + * Vim format (1-based). The range is from lo up to, but not including, hi. + * The replacement lines are given as a Scheme list of string objects. The + * list is checked for validity and correct format. + * + * Errors are returned as a value of FAIL. The return value is OK on success. + * If OK is returned and len_change is not NULL, *len_change is set to the + * change in the buffer length. + */ + static Scheme_Object * + set_buffer_line_list(void *data, int argc, Scheme_Object **argv) + { + /* First of all, we check the type of the supplied MzScheme object. + * There are three cases: + * 1. #f - this is a deletion. + * 2. A list - this is a replacement. + * 3. Anything else - this is an error. + */ + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_buffer *buf = NULL; + Scheme_Object *line_list = NULL; + int i, old_len, new_len, hi, lo; + long extra; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, line_list); + MZ_GC_REG(); + + #ifdef HAVE_SANDBOX + sandbox_check(); + #endif + lo = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 0)); + hi = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 1)); + if (!SCHEME_PAIRP(argv[2]) + && !SCHEME_FALSEP(argv[2]) && !SCHEME_NULLP(argv[2])) + scheme_wrong_type(prim->name, "list or #f", 2, argc, argv); + line_list = argv[2]; + buf = get_buffer_arg(prim->name, 3, argc, argv); + old_len = hi - lo; + if (old_len < 0) /* process inverse values wisely */ + { + i = lo; + lo = hi; + hi = i; + old_len = -old_len; + } + extra = 0; + + check_line_range(lo, buf->buf); /* inclusive */ + check_line_range(hi - 1, buf->buf); /* exclusive */ + + if (SCHEME_FALSEP(line_list) || SCHEME_NULLP(line_list)) + { + buf_T *savebuf = curbuf; + curbuf = buf->buf; + + if (u_savedel((linenr_T)lo, (long)old_len) == FAIL) + { + curbuf = savebuf; + raise_vim_exn(_("cannot save undo information")); + } + else + { + for (i = 0; i < old_len; i++) + if (ml_delete((linenr_T)lo, FALSE) == FAIL) + { + curbuf = savebuf; + raise_vim_exn(_("cannot delete line")); + } + if (buf->buf == curwin->w_buffer) + mz_fix_cursor(lo, hi, -old_len); + deleted_lines_mark((linenr_T)lo, (long)old_len); + } + + curbuf = savebuf; + + MZ_GC_UNREG(); + raise_if_error(); + return scheme_void; + } + else + { + buf_T *savebuf = curbuf; + + /* List */ + new_len = scheme_proper_list_length(line_list); + MZ_GC_CHECK(); + if (new_len < 0) /* improper or cyclic list */ + scheme_wrong_type(prim->name, "proper list", + 2, argc, argv); + else + { + char **array = NULL; + Scheme_Object *line = NULL; + Scheme_Object *rest = NULL; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, line); + MZ_GC_VAR_IN_REG(1, rest); + MZ_GC_REG(); + + array = (char **)alloc((new_len+1)* sizeof(char *)); + vim_memset(array, 0, (new_len+1) * sizeof(char *)); + + rest = line_list; + for (i = 0; i < new_len; ++i) + { + line = SCHEME_CAR(rest); + rest = SCHEME_CDR(rest); + if (!SCHEME_STRINGP(line)) + { + free_array(array); + scheme_wrong_type(prim->name, "string-list", 2, argc, argv); + } + array[i] = string_to_line(line); + } + + curbuf = buf->buf; + + if (u_save((linenr_T)(lo-1), (linenr_T)hi) == FAIL) + { + curbuf = savebuf; + free_array(array); + raise_vim_exn(_("cannot save undo information")); + } + + /* + * If the size of the range is reducing (ie, new_len < old_len) we + * need to delete some old_len. We do this at the start, by + * repeatedly deleting line "lo". + */ + for (i = 0; i < old_len - new_len; ++i) + { + if (ml_delete((linenr_T)lo, FALSE) == FAIL) + { + curbuf = savebuf; + free_array(array); + raise_vim_exn(_("cannot delete line")); + } + extra--; + } + + /* + * For as long as possible, replace the existing old_len with the + * new old_len. This is a more efficient operation, as it requires + * less memory allocation and freeing. + */ + for (i = 0; i < old_len && i < new_len; i++) + if (ml_replace((linenr_T)(lo+i), (char_u *)array[i], TRUE) == FAIL) + { + curbuf = savebuf; + free_array(array); + raise_vim_exn(_("cannot replace line")); + } + + /* + * Now we may need to insert the remaining new_len. We don't need to + * free the string passed back because MzScheme has control of that + * memory. + */ + while (i < new_len) + { + if (ml_append((linenr_T)(lo + i - 1), + (char_u *)array[i], 0, FALSE) == FAIL) + { + curbuf = savebuf; + free_array(array); + raise_vim_exn(_("cannot insert line")); + } + ++i; + ++extra; + } + MZ_GC_UNREG(); + free_array(array); + } + + /* + * Adjust marks. Invalidate any which lie in the + * changed range, and move any in the remainder of the buffer. + */ + mark_adjust((linenr_T)lo, (linenr_T)(hi - 1), (long)MAXLNUM, (long)extra); + changed_lines((linenr_T)lo, 0, (linenr_T)hi, (long)extra); + + if (buf->buf == curwin->w_buffer) + mz_fix_cursor(lo, hi, extra); + curbuf = savebuf; + + MZ_GC_UNREG(); + raise_if_error(); + return scheme_void; + } + } + + /* + * (insert-buff-line-list {linenr} {string/string-list} [buffer]) + * + * Insert a number of lines into the specified buffer after the specified line. + * The line number is in Vim format (1-based). The lines to be inserted are + * given as an MzScheme list of string objects or as a single string. The lines + * to be added are checked for validity and correct format. Errors are + * returned as a value of FAIL. The return value is OK on success. + * If OK is returned and len_change is not NULL, *len_change + * is set to the change in the buffer length. + */ + static Scheme_Object * + insert_buffer_line_list(void *data, int argc, Scheme_Object **argv) + { + Vim_Prim *prim = (Vim_Prim *)data; + vim_mz_buffer *buf = NULL; + Scheme_Object *list = NULL; + char *str = NULL; + int i, n, size; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, list); + MZ_GC_REG(); + + #ifdef HAVE_SANDBOX + sandbox_check(); + #endif + /* + * First of all, we check the type of the supplied MzScheme object. + * It must be a string or a list, or the call is in error. + */ + n = SCHEME_INT_VAL(GUARANTEE_INTEGER(prim->name, 0)); + list = argv[1]; + + if (!SCHEME_STRINGP(list) && !SCHEME_PAIRP(list)) + scheme_wrong_type(prim->name, "string or list", 1, argc, argv); + buf = get_buffer_arg(prim->name, 2, argc, argv); + + if (n != 0) /* 0 can be used in insert */ + check_line_range(n, buf->buf); + if (SCHEME_STRINGP(list)) + { + buf_T *savebuf = curbuf; + + str = string_to_line(list); + curbuf = buf->buf; + + if (u_save((linenr_T)n, (linenr_T)(n+1)) == FAIL) + { + curbuf = savebuf; + vim_free(str); + raise_vim_exn(_("cannot save undo information")); + } + else if (ml_append((linenr_T)n, (char_u *)str, 0, FALSE) == FAIL) + { + curbuf = savebuf; + vim_free(str); + raise_vim_exn(_("cannot insert line")); + } + else + { + vim_free(str); + appended_lines_mark((linenr_T)n, 1L); + } + + curbuf = savebuf; + update_screen(VALID); + + MZ_GC_UNREG(); + raise_if_error(); + return scheme_void; + } + + /* List */ + size = scheme_proper_list_length(list); + MZ_GC_CHECK(); + if (size < 0) /* improper or cyclic list */ + scheme_wrong_type(prim->name, "proper list", + 2, argc, argv); + else + { + Scheme_Object *line = NULL; + Scheme_Object *rest = NULL; + char **array; + buf_T *savebuf = curbuf; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, line); + MZ_GC_VAR_IN_REG(1, rest); + MZ_GC_REG(); + + array = (char **)alloc((size+1) * sizeof(char *)); + vim_memset(array, 0, (size+1) * sizeof(char *)); + + rest = list; + for (i = 0; i < size; ++i) + { + line = SCHEME_CAR(rest); + rest = SCHEME_CDR(rest); + array[i] = string_to_line(line); + } + + curbuf = buf->buf; + + if (u_save((linenr_T)n, (linenr_T)(n + 1)) == FAIL) + { + curbuf = savebuf; + free_array(array); + raise_vim_exn(_("cannot save undo information")); + } + else + { + for (i = 0; i < size; ++i) + if (ml_append((linenr_T)(n + i), (char_u *)array[i], + 0, FALSE) == FAIL) + { + curbuf = savebuf; + free_array(array); + raise_vim_exn(_("cannot insert line")); + } + + if (i > 0) + appended_lines_mark((linenr_T)n, (long)i); + } + free_array(array); + MZ_GC_UNREG(); + curbuf = savebuf; + update_screen(VALID); + } + + MZ_GC_UNREG(); + raise_if_error(); + return scheme_void; + } + + /* + * Predicates + */ + /* (buff? obj) */ + static Scheme_Object * + vim_bufferp(void *data UNUSED, int argc UNUSED, Scheme_Object **argv) + { + if (SCHEME_VIMBUFFERP(argv[0])) + return scheme_true; + else + return scheme_false; + } + + /* (win? obj) */ + static Scheme_Object * + vim_windowp(void *data UNUSED, int argc UNUSED, Scheme_Object **argv) + { + if (SCHEME_VIMWINDOWP(argv[0])) + return scheme_true; + else + return scheme_false; + } + + /* (buff-valid? obj) */ + static Scheme_Object * + vim_buffer_validp(void *data UNUSED, int argc UNUSED, Scheme_Object **argv) + { + if (SCHEME_VIMBUFFERP(argv[0]) + && ((vim_mz_buffer *)argv[0])->buf != INVALID_BUFFER_VALUE) + return scheme_true; + else + return scheme_false; + } + + /* (win-valid? obj) */ + static Scheme_Object * + vim_window_validp(void *data UNUSED, int argc UNUSED, Scheme_Object **argv) + { + if (SCHEME_VIMWINDOWP(argv[0]) + && ((vim_mz_window *)argv[0])->win != INVALID_WINDOW_VALUE) + return scheme_true; + else + return scheme_false; + } + + /* + *=========================================================================== + * Utilities + *=========================================================================== + */ + + /* + * Convert an MzScheme string into a Vim line. + * + * All internal nulls are replaced by newline characters. + * It is an error for the string to contain newline characters. + * + * Returns pointer to Vim allocated memory + */ + static char * + string_to_line(Scheme_Object *obj) + { + char *scheme_str = NULL; + char *vim_str = NULL; + OUTPUT_LEN_TYPE len; + int i; + + scheme_str = scheme_display_to_string(obj, &len); + + /* Error checking: String must not contain newlines, as we + * are replacing a single line, and we must replace it with + * a single line. + */ + if (memchr(scheme_str, '\n', len)) + scheme_signal_error(_("string cannot contain newlines")); + + vim_str = (char *)alloc(len + 1); + + /* Create a copy of the string, with internal nulls replaced by + * newline characters, as is the vim convention. + */ + for (i = 0; i < len; ++i) + { + if (scheme_str[i] == '\0') + vim_str[i] = '\n'; + else + vim_str[i] = scheme_str[i]; + } + + vim_str[i] = '\0'; + + MZ_GC_CHECK(); + return vim_str; + } + + #ifdef FEAT_EVAL + /* + * Convert Vim value into MzScheme, adopted from if_python.c + */ + static Scheme_Object * + vim_to_mzscheme(typval_T *vim_value) + { + Scheme_Object *result = NULL; + /* hash table to store visited values to avoid infinite loops */ + Scheme_Hash_Table *visited = NULL; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, result); + MZ_GC_VAR_IN_REG(1, visited); + MZ_GC_REG(); + + visited = scheme_make_hash_table(SCHEME_hash_ptr); + MZ_GC_CHECK(); + + result = vim_to_mzscheme_impl(vim_value, 1, visited); + + MZ_GC_UNREG(); + return result; + } + + static Scheme_Object * + vim_to_mzscheme_impl(typval_T *vim_value, int depth, Scheme_Hash_Table *visited) + { + Scheme_Object *result = NULL; + int new_value = TRUE; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, result); + MZ_GC_VAR_IN_REG(1, visited); + MZ_GC_REG(); + + /* Avoid infinite recursion */ + if (depth > 100) + { + MZ_GC_UNREG(); + return scheme_void; + } + + /* Check if we run into a recursive loop. The item must be in visited + * then and we can use it again. + */ + result = scheme_hash_get(visited, (Scheme_Object *)vim_value); + MZ_GC_CHECK(); + if (result != NULL) /* found, do nothing */ + new_value = FALSE; + else if (vim_value->v_type == VAR_STRING) + { + result = scheme_make_byte_string((char *)vim_value->vval.v_string); + MZ_GC_CHECK(); + } + else if (vim_value->v_type == VAR_NUMBER) + { + result = scheme_make_integer((long)vim_value->vval.v_number); + MZ_GC_CHECK(); + } + # ifdef FEAT_FLOAT + else if (vim_value->v_type == VAR_FLOAT) + { + result = scheme_make_double((double)vim_value->vval.v_float); + MZ_GC_CHECK(); + } + # endif + else if (vim_value->v_type == VAR_LIST) + { + list_T *list = vim_value->vval.v_list; + listitem_T *curr; + + if (list == NULL || list->lv_first == NULL) + result = scheme_null; + else + { + Scheme_Object *obj = NULL; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, obj); + MZ_GC_REG(); + + curr = list->lv_last; + obj = vim_to_mzscheme_impl(&curr->li_tv, depth + 1, visited); + result = scheme_make_pair(obj, scheme_null); + MZ_GC_CHECK(); + + while (curr != list->lv_first) + { + curr = curr->li_prev; + obj = vim_to_mzscheme_impl(&curr->li_tv, depth + 1, visited); + result = scheme_make_pair(obj, result); + MZ_GC_CHECK(); + } + } + MZ_GC_UNREG(); + } + else if (vim_value->v_type == VAR_DICT) + { + Scheme_Object *key = NULL; + Scheme_Object *obj = NULL; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, key); + MZ_GC_VAR_IN_REG(1, obj); + MZ_GC_REG(); + + result = (Scheme_Object *)scheme_make_hash_table(SCHEME_hash_ptr); + MZ_GC_CHECK(); + if (vim_value->vval.v_dict != NULL) + { + hashtab_T *ht = &vim_value->vval.v_dict->dv_hashtab; + long_u todo = ht->ht_used; + hashitem_T *hi; + dictitem_T *di; + + for (hi = ht->ht_array; todo > 0; ++hi) + { + if (!HASHITEM_EMPTY(hi)) + { + --todo; + + di = dict_lookup(hi); + obj = vim_to_mzscheme_impl(&di->di_tv, depth + 1, visited); + key = scheme_make_byte_string((char *)hi->hi_key); + MZ_GC_CHECK(); + scheme_hash_set((Scheme_Hash_Table *)result, key, obj); + MZ_GC_CHECK(); + } + } + } + MZ_GC_UNREG(); + } + else if (vim_value->v_type == VAR_FUNC) + { + Scheme_Object *funcname = NULL; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, funcname); + MZ_GC_REG(); + + funcname = scheme_make_byte_string((char *)vim_value->vval.v_string); + MZ_GC_CHECK(); + result = scheme_make_closed_prim_w_arity(vim_funcref, funcname, + (const char *)BYTE_STRING_VALUE(funcname), 0, -1); + MZ_GC_CHECK(); + + MZ_GC_UNREG(); + } + else + { + result = scheme_void; + new_value = FALSE; + } + if (new_value) + { + scheme_hash_set(visited, (Scheme_Object *)vim_value, result); + MZ_GC_CHECK(); + } + MZ_GC_UNREG(); + return result; + } + + static int + mzscheme_to_vim(Scheme_Object *obj, typval_T *tv) + { + int i, status; + Scheme_Hash_Table *visited = NULL; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, obj); + MZ_GC_VAR_IN_REG(1, visited); + MZ_GC_REG(); + + visited = scheme_make_hash_table(SCHEME_hash_ptr); + MZ_GC_CHECK(); + + status = mzscheme_to_vim_impl(obj, tv, 1, visited); + for (i = 0; i < visited->size; ++i) + { + /* free up remembered objects */ + if (visited->vals[i] != NULL) + free_tv((typval_T *)visited->vals[i]); + } + + MZ_GC_UNREG(); + return status; + } + static int + mzscheme_to_vim_impl(Scheme_Object *obj, typval_T *tv, int depth, + Scheme_Hash_Table *visited) + { + int status = OK; + typval_T *found; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, obj); + MZ_GC_VAR_IN_REG(1, visited); + MZ_GC_REG(); + + MZ_GC_CHECK(); + if (depth > 100) /* limit the deepest recursion level */ + { + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + return FAIL; + } + + found = (typval_T *)scheme_hash_get(visited, obj); + if (found != NULL) + copy_tv(found, tv); + else if (SCHEME_VOIDP(obj)) + { + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + } + else if (SCHEME_INTP(obj)) + { + tv->v_type = VAR_NUMBER; + tv->vval.v_number = SCHEME_INT_VAL(obj); + } + else if (SCHEME_BOOLP(obj)) + { + tv->v_type = VAR_NUMBER; + tv->vval.v_number = SCHEME_TRUEP(obj); + } + # ifdef FEAT_FLOAT + else if (SCHEME_DBLP(obj)) + { + tv->v_type = VAR_FLOAT; + tv->vval.v_float = SCHEME_DBL_VAL(obj); + } + # endif + else if (SCHEME_BYTE_STRINGP(obj)) + { + tv->v_type = VAR_STRING; + tv->vval.v_string = vim_strsave(BYTE_STRING_VALUE(obj)); + } + # if MZSCHEME_VERSION_MAJOR >= 299 + else if (SCHEME_CHAR_STRINGP(obj)) + { + Scheme_Object *tmp = NULL; + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, tmp); + MZ_GC_REG(); + + tmp = scheme_char_string_to_byte_string(obj); + tv->v_type = VAR_STRING; + tv->vval.v_string = vim_strsave(BYTE_STRING_VALUE(tmp)); + MZ_GC_UNREG(); + } + #endif + else if (SCHEME_VECTORP(obj) || SCHEME_NULLP(obj) + || SCHEME_PAIRP(obj) || SCHEME_MUTABLE_PAIRP(obj)) + { + list_T *list = list_alloc(); + if (list == NULL) + status = FAIL; + else + { + int i; + Scheme_Object *curr = NULL; + Scheme_Object *cval = NULL; + /* temporary var to hold current element of vectors and pairs */ + typval_T *v; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, curr); + MZ_GC_VAR_IN_REG(1, cval); + MZ_GC_REG(); + + tv->v_type = VAR_LIST; + tv->vval.v_list = list; + ++list->lv_refcount; + + v = (typval_T *)alloc(sizeof(typval_T)); + if (v == NULL) + status = FAIL; + else + { + /* add the value in advance to allow handling of self-referential + * data structures */ + typval_T *visited_tv = (typval_T *)alloc(sizeof(typval_T)); + copy_tv(tv, visited_tv); + scheme_hash_set(visited, obj, (Scheme_Object *)visited_tv); + + if (SCHEME_VECTORP(obj)) + { + for (i = 0; i < SCHEME_VEC_SIZE(obj); ++i) + { + cval = SCHEME_VEC_ELS(obj)[i]; + status = mzscheme_to_vim_impl(cval, v, depth + 1, visited); + if (status == FAIL) + break; + status = list_append_tv(list, v); + clear_tv(v); + if (status == FAIL) + break; + } + } + else if (SCHEME_PAIRP(obj) || SCHEME_MUTABLE_PAIRP(obj)) + { + for (curr = obj; + SCHEME_PAIRP(curr) || SCHEME_MUTABLE_PAIRP(curr); + curr = SCHEME_CDR(curr)) + { + cval = SCHEME_CAR(curr); + status = mzscheme_to_vim_impl(cval, v, depth + 1, visited); + if (status == FAIL) + break; + status = list_append_tv(list, v); + clear_tv(v); + if (status == FAIL) + break; + } + /* improper list not terminated with null + * need to handle the last element */ + if (status == OK && !SCHEME_NULLP(curr)) + { + status = mzscheme_to_vim_impl(cval, v, depth + 1, visited); + if (status == OK) + { + status = list_append_tv(list, v); + clear_tv(v); + } + } + } + /* nothing to do for scheme_null */ + vim_free(v); + } + MZ_GC_UNREG(); + } + } + else if (SCHEME_HASHTP(obj)) + { + int i; + dict_T *dict; + Scheme_Object *key = NULL; + Scheme_Object *val = NULL; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, key); + MZ_GC_VAR_IN_REG(1, val); + MZ_GC_REG(); + + dict = dict_alloc(); + if (dict == NULL) + status = FAIL; + else + { + typval_T *visited_tv = (typval_T *)alloc(sizeof(typval_T)); + + tv->v_type = VAR_DICT; + tv->vval.v_dict = dict; + ++dict->dv_refcount; + + copy_tv(tv, visited_tv); + scheme_hash_set(visited, obj, (Scheme_Object *)visited_tv); + + for (i = 0; i < ((Scheme_Hash_Table *)obj)->size; ++i) + { + if (((Scheme_Hash_Table *) obj)->vals[i] != NULL) + { + /* generate item for `display'ed Scheme key */ + dictitem_T *item = dictitem_alloc((char_u *)string_to_line( + ((Scheme_Hash_Table *) obj)->keys[i])); + /* convert Scheme val to Vim and add it to the dict */ + if (mzscheme_to_vim_impl(((Scheme_Hash_Table *) obj)->vals[i], + &item->di_tv, depth + 1, visited) == FAIL + || dict_add(dict, item) == FAIL) + { + dictitem_free(item); + status = FAIL; + break; + } + } + + } + } + MZ_GC_UNREG(); + } + else + { + /* `display' any other value to string */ + tv->v_type = VAR_STRING; + tv->vval.v_string = (char_u *)string_to_line(obj); + } + MZ_GC_UNREG(); + return status; + } + + /* Scheme prim procedure wrapping Vim funcref */ + static Scheme_Object * + vim_funcref(void *name, int argc, Scheme_Object **argv) + { + int i; + typval_T args; + int status = OK; + Scheme_Object *result = NULL; + list_T *list = list_alloc(); + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, result); + MZ_GC_REG(); + + result = scheme_void; + if (list == NULL) + status = FAIL; + else + { + args.v_type = VAR_LIST; + args.vval.v_list = list; + ++list->lv_refcount; + for (i = 0; status == OK && i < argc; ++i) + { + typval_T *v = (typval_T *)alloc(sizeof(typval_T)); + if (v == NULL) + status = FAIL; + else + { + status = mzscheme_to_vim(argv[i], v); + if (status == OK) + { + status = list_append_tv(list, v); + clear_tv(v); + } + vim_free(v); + } + } + if (status == OK) + { + typval_T ret; + ret.v_type = VAR_UNKNOWN; + + mzscheme_call_vim(BYTE_STRING_VALUE((Scheme_Object *)name), &args, &ret); + MZ_GC_CHECK(); + result = vim_to_mzscheme(&ret); + clear_tv(&ret); + MZ_GC_CHECK(); + } + } + clear_tv(&args); + MZ_GC_UNREG(); + if (status != OK) + raise_vim_exn(_("error converting Scheme values to Vim")); + else + raise_if_error(); + return result; + } + + void + do_mzeval(char_u *str, typval_T *rettv) + { + Scheme_Object *ret = NULL; + + MZ_GC_DECL_REG(1); + MZ_GC_VAR_IN_REG(0, ret); + MZ_GC_REG(); + + if (mzscheme_init()) + { + MZ_GC_UNREG(); + return; + } + + MZ_GC_CHECK(); + if (eval_with_exn_handling(str, do_eval, &ret) == OK) + mzscheme_to_vim(ret, rettv); + + MZ_GC_UNREG(); + } + #endif + + /* + * Check to see whether a Vim error has been reported, or a keyboard + * interrupt (from vim --> got_int) has been detected. + */ + static int + vim_error_check(void) + { + return (got_int || did_emsg); + } + + /* + * register Scheme exn:vim + */ + static void + register_vim_exn(void) + { + int nc = 0; + int i; + Scheme_Object *struct_exn = NULL; + Scheme_Object *exn_name = NULL; + + MZ_GC_DECL_REG(2); + MZ_GC_VAR_IN_REG(0, struct_exn); + MZ_GC_VAR_IN_REG(1, exn_name); + MZ_GC_REG(); + + exn_name = scheme_intern_symbol("exn:vim"); + MZ_GC_CHECK(); + struct_exn = scheme_builtin_value("struct:exn"); + MZ_GC_CHECK(); + + if (vim_exn == NULL) + vim_exn = scheme_make_struct_type(exn_name, + struct_exn, NULL, 0, 0, NULL, NULL + #if MZSCHEME_VERSION_MAJOR >= 299 + , NULL + #endif + ); + + + { + Scheme_Object **tmp = NULL; + Scheme_Object *exn_names[5] = {NULL, NULL, NULL, NULL, NULL}; + Scheme_Object *exn_values[5] = {NULL, NULL, NULL, NULL, NULL}; + MZ_GC_DECL_REG(6); + MZ_GC_ARRAY_VAR_IN_REG(0, exn_names, 5); + MZ_GC_ARRAY_VAR_IN_REG(3, exn_values, 5); + MZ_GC_REG(); + + tmp = scheme_make_struct_names(exn_name, scheme_null, 0, &nc); + mch_memmove(exn_names, tmp, nc * sizeof(Scheme_Object *)); + MZ_GC_CHECK(); + + tmp = scheme_make_struct_values(vim_exn, exn_names, nc, 0); + mch_memmove(exn_values, tmp, nc * sizeof(Scheme_Object *)); + MZ_GC_CHECK(); + + for (i = 0; i < nc; i++) + { + scheme_add_global_symbol(exn_names[i], + exn_values[i], environment); + MZ_GC_CHECK(); + } + MZ_GC_UNREG(); + } + MZ_GC_UNREG(); + } + + /* + * raise exn:vim, may be with additional info string + */ + void + raise_vim_exn(const char *add_info) + { + char *fmt = _("Vim error: ~a"); + Scheme_Object *argv[2] = {NULL, NULL}; + Scheme_Object *exn = NULL; + Scheme_Object *byte_string = NULL; + + MZ_GC_DECL_REG(5); + MZ_GC_ARRAY_VAR_IN_REG(0, argv, 2); + MZ_GC_VAR_IN_REG(3, exn); + MZ_GC_VAR_IN_REG(4, byte_string); + MZ_GC_REG(); + + if (add_info != NULL) + { + char *c_string = NULL; + Scheme_Object *info = NULL; + + MZ_GC_DECL_REG(3); + MZ_GC_VAR_IN_REG(0, c_string); + MZ_GC_VAR_IN_REG(2, info); + MZ_GC_REG(); + + info = scheme_make_byte_string(add_info); + MZ_GC_CHECK(); + c_string = scheme_format_utf8(fmt, STRLEN(fmt), 1, &info, NULL); + MZ_GC_CHECK(); + byte_string = scheme_make_byte_string(c_string); + MZ_GC_CHECK(); + argv[0] = scheme_byte_string_to_char_string(byte_string); + SCHEME_SET_IMMUTABLE(argv[0]); + MZ_GC_UNREG(); + } + else + { + byte_string = scheme_make_byte_string(_("Vim error")); + MZ_GC_CHECK(); + argv[0] = scheme_byte_string_to_char_string(byte_string); + MZ_GC_CHECK(); + } + MZ_GC_CHECK(); + + #if MZSCHEME_VERSION_MAJOR < 360 + argv[1] = scheme_current_continuation_marks(); + MZ_GC_CHECK(); + #else + argv[1] = scheme_current_continuation_marks(NULL); + MZ_GC_CHECK(); + #endif + + exn = scheme_make_struct_instance(vim_exn, 2, argv); + MZ_GC_CHECK(); + scheme_raise(exn); + MZ_GC_UNREG(); + } + + void + raise_if_error(void) + { + if (vim_error_check()) + raise_vim_exn(NULL); + } + + /* get buffer: + * either current + * or passed as argv[argnum] with checks + */ + static vim_mz_buffer * + get_buffer_arg(const char *fname, int argnum, int argc, Scheme_Object **argv) + { + vim_mz_buffer *b; + + if (argc < argnum + 1) + return get_vim_curr_buffer(); + if (!SCHEME_VIMBUFFERP(argv[argnum])) + scheme_wrong_type(fname, "vim-buffer", argnum, argc, argv); + b = (vim_mz_buffer *)argv[argnum]; + (void)get_valid_buffer(argv[argnum]); + return b; + } + + /* get window: + * either current + * or passed as argv[argnum] with checks + */ + static vim_mz_window * + get_window_arg(const char *fname, int argnum, int argc, Scheme_Object **argv) + { + vim_mz_window *w; + + if (argc < argnum + 1) + return get_vim_curr_window(); + w = (vim_mz_window *)argv[argnum]; + if (!SCHEME_VIMWINDOWP(argv[argnum])) + scheme_wrong_type(fname, "vim-window", argnum, argc, argv); + (void)get_valid_window(argv[argnum]); + return w; + } + + /* get valid Vim buffer from Scheme_Object* */ + buf_T *get_valid_buffer(void *obj) + { + buf_T *buf = ((vim_mz_buffer *)obj)->buf; + + if (buf == INVALID_BUFFER_VALUE) + scheme_signal_error(_("buffer is invalid")); + return buf; + } + + /* get valid Vim window from Scheme_Object* */ + win_T *get_valid_window(void *obj) + { + win_T *win = ((vim_mz_window *)obj)->win; + if (win == INVALID_WINDOW_VALUE) + scheme_signal_error(_("window is invalid")); + return win; + } + + int + mzthreads_allowed(void) + { + return mz_threads_allow; + } + + static int + line_in_range(linenr_T lnum, buf_T *buf) + { + return (lnum > 0 && lnum <= buf->b_ml.ml_line_count); + } + + static void + check_line_range(linenr_T lnum, buf_T *buf) + { + if (!line_in_range(lnum, buf)) + scheme_signal_error(_("linenr out of range")); + } + + /* + * Check if deleting lines made the cursor position invalid + * (or you'll get msg from Vim about invalid linenr). + * Changed the lines from "lo" to "hi" and added "extra" lines (negative if + * deleted). Got from if_python.c + */ + static void + mz_fix_cursor(int lo, int hi, int extra) + { + if (curwin->w_cursor.lnum >= lo) + { + /* Adjust the cursor position if it's in/after the changed + * lines. */ + if (curwin->w_cursor.lnum >= hi) + { + curwin->w_cursor.lnum += extra; + check_cursor_col(); + } + else if (extra < 0) + { + curwin->w_cursor.lnum = lo; + check_cursor(); + } + else + check_cursor_col(); + changed_cline_bef_curs(); + } + invalidate_botline(); + } + + static Vim_Prim prims[]= + { + /* + * Buffer-related commands + */ + {get_buffer_line, "get-buff-line", 1, 2}, + {set_buffer_line, "set-buff-line", 2, 3}, + {get_buffer_line_list, "get-buff-line-list", 2, 3}, + {get_buffer_name, "get-buff-name", 0, 1}, + {get_buffer_num, "get-buff-num", 0, 1}, + {get_buffer_size, "get-buff-size", 0, 1}, + {set_buffer_line_list, "set-buff-line-list", 3, 4}, + {insert_buffer_line_list, "insert-buff-line-list", 2, 3}, + {get_curr_buffer, "curr-buff", 0, 0}, + {get_buffer_count, "buff-count", 0, 0}, + {get_next_buffer, "get-next-buff", 0, 1}, + {get_prev_buffer, "get-prev-buff", 0, 1}, + {mzscheme_open_buffer, "open-buff", 1, 1}, + {get_buffer_by_name, "get-buff-by-name", 1, 1}, + {get_buffer_by_num, "get-buff-by-num", 1, 1}, + /* + * Window-related commands + */ + {get_curr_win, "curr-win", 0, 0}, + {get_window_count, "win-count", 0, 0}, + {get_window_by_num, "get-win-by-num", 1, 1}, + {get_window_num, "get-win-num", 0, 1}, + {get_window_buffer, "get-win-buffer", 0, 1}, + {get_window_height, "get-win-height", 0, 1}, + {set_window_height, "set-win-height", 1, 2}, + #ifdef FEAT_VERTSPLIT + {get_window_width, "get-win-width", 0, 1}, + {set_window_width, "set-win-width", 1, 2}, + #endif + {get_cursor, "get-cursor", 0, 1}, + {set_cursor, "set-cursor", 1, 2}, + {get_window_list, "get-win-list", 0, 1}, + /* + * Vim-related commands + */ + {vim_command, "command", 1, 1}, + {vim_eval, "eval", 1, 1}, + {get_range_start, "range-start", 0, 0}, + {get_range_end, "range-end", 0, 0}, + {mzscheme_beep, "beep", 0, 0}, + {get_option, "get-option", 1, 2}, + {set_option, "set-option", 1, 2}, + /* + * small utilities + */ + {vim_bufferp, "buff?", 1, 1}, + {vim_windowp, "win?", 1, 1}, + {vim_buffer_validp, "buff-valid?", 1, 1}, + {vim_window_validp, "win-valid?", 1, 1} + }; + + /* return MzScheme wrapper for curbuf */ + static vim_mz_buffer * + get_vim_curr_buffer(void) + { + if (curbuf->b_mzscheme_ref == NULL) + return (vim_mz_buffer *)buffer_new(curbuf); + else + return BUFFER_REF(curbuf); + } + + /* return MzScheme wrapper for curwin */ + static vim_mz_window * + get_vim_curr_window(void) + { + if (curwin->w_mzscheme_ref == NULL) + return (vim_mz_window *)window_new(curwin); + else + return WINDOW_REF(curwin); + } + + static void + make_modules() + { + int i; + Scheme_Env *mod = NULL; + Scheme_Object *vimext_symbol = NULL; + Scheme_Object *closed_prim = NULL; + + MZ_GC_DECL_REG(3); + MZ_GC_VAR_IN_REG(0, mod); + MZ_GC_VAR_IN_REG(1, vimext_symbol); + MZ_GC_VAR_IN_REG(2, closed_prim); + MZ_GC_REG(); + + vimext_symbol = scheme_intern_symbol("vimext"); + MZ_GC_CHECK(); + mod = scheme_primitive_module(vimext_symbol, environment); + MZ_GC_CHECK(); + /* all prims made closed so they can access their own names */ + for (i = 0; i < (int)(sizeof(prims)/sizeof(prims[0])); i++) + { + Vim_Prim *prim = prims + i; + closed_prim = scheme_make_closed_prim_w_arity(prim->prim, prim, prim->name, + prim->mina, prim->maxa); + scheme_add_global(prim->name, closed_prim, mod); + MZ_GC_CHECK(); + } + scheme_finish_primitive_module(mod); + MZ_GC_CHECK(); + MZ_GC_UNREG(); + } + + #ifdef HAVE_SANDBOX + static Scheme_Object *M_write = NULL; + static Scheme_Object *M_read = NULL; + static Scheme_Object *M_execute = NULL; + static Scheme_Object *M_delete = NULL; + + static void + sandbox_check(void) + { + if (sandbox) + raise_vim_exn(_("not allowed in the Vim sandbox")); + } + + /* security guards to force Vim's sandbox restrictions on MzScheme level */ + static Scheme_Object * + sandbox_file_guard(int argc UNUSED, Scheme_Object **argv) + { + if (sandbox) + { + Scheme_Object *requested_access = argv[2]; + + if (M_write == NULL) + { + MZ_REGISTER_STATIC(M_write); + M_write = scheme_intern_symbol("write"); + MZ_GC_CHECK(); + } + if (M_read == NULL) + { + MZ_REGISTER_STATIC(M_read); + M_read = scheme_intern_symbol("read"); + MZ_GC_CHECK(); + } + if (M_execute == NULL) + { + MZ_REGISTER_STATIC(M_execute); + M_execute = scheme_intern_symbol("execute"); + MZ_GC_CHECK(); + } + if (M_delete == NULL) + { + MZ_REGISTER_STATIC(M_delete); + M_delete = scheme_intern_symbol("delete"); + MZ_GC_CHECK(); + } + + while (!SCHEME_NULLP(requested_access)) + { + Scheme_Object *item = SCHEME_CAR(requested_access); + if (scheme_eq(item, M_write) || scheme_eq(item, M_read) + || scheme_eq(item, M_execute) || scheme_eq(item, M_delete)) + { + raise_vim_exn(_("not allowed in the Vim sandbox")); + } + requested_access = SCHEME_CDR(requested_access); + } + } + return scheme_void; + } + + static Scheme_Object * + sandbox_network_guard(int argc UNUSED, Scheme_Object **argv UNUSED) + { + return scheme_void; + } + #endif + + #endif diff --git a/src/test/resources/unparser/diff/error04.txt b/src/test/resources/unparser/diff/error04.txt new file mode 100644 index 00000000..5c6dc0c7 --- /dev/null +++ b/src/test/resources/unparser/diff/error04.txt @@ -0,0 +1,3569 @@ +textDiff: + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + /* + * ui.c: functions that handle the user interface. + * 1. Keyboard input stuff, and a bit of windowing stuff. These are called + * before the machine specific stuff (mch_*) so that we can call the GUI + * stuff instead if the GUI is running. + * 2. Clipboard stuff. + * 3. Input buffer stuff. + */ + + #include "vim.h" + + #ifdef FEAT_CYGWIN_WIN32_CLIPBOARD + # define WIN32_LEAN_AND_MEAN + # include + # include "winclip.pro" + #endif + + void + ui_write(char_u *s, int len) + { + #ifdef FEAT_GUI + if (gui.in_use && !gui.dying && !gui.starting) + { + gui_write(s, len); + if (p_wd) + gui_wait_for_chars(p_wd, typebuf.tb_change_cnt); + return; + } + #endif + #ifndef NO_CONSOLE + /* Don't output anything in silent mode ("ex -s") unless 'verbose' set */ + if (!(silent_mode && p_verbose == 0)) + { + #if !defined(MSWIN) + char_u *tofree = NULL; + + if (output_conv.vc_type != CONV_NONE) + { + /* Convert characters from 'encoding' to 'termencoding'. */ + tofree = string_convert(&output_conv, s, &len); + if (tofree != NULL) + s = tofree; + } + #endif + + mch_write(s, len); + + # if !defined(MSWIN) + if (output_conv.vc_type != CONV_NONE) + vim_free(tofree); + # endif + } + #endif + } + + #if defined(UNIX) || defined(VMS) || defined(PROTO) || defined(MSWIN) + /* + * When executing an external program, there may be some typed characters that + * are not consumed by it. Give them back to ui_inchar() and they are stored + * here for the next call. + */ + static char_u *ta_str = NULL; + static int ta_off; /* offset for next char to use when ta_str != NULL */ + static int ta_len; /* length of ta_str when it's not NULL*/ + + void + ui_inchar_undo(char_u *s, int len) + { + char_u *new; + int newlen; + + newlen = len; + if (ta_str != NULL) + newlen += ta_len - ta_off; + new = alloc(newlen); + if (new != NULL) + { + if (ta_str != NULL) + { + mch_memmove(new, ta_str + ta_off, (size_t)(ta_len - ta_off)); + mch_memmove(new + ta_len - ta_off, s, (size_t)len); + vim_free(ta_str); + } + else + mch_memmove(new, s, (size_t)len); + ta_str = new; + ta_len = newlen; + ta_off = 0; + } + } + #endif + + /* + * ui_inchar(): low level input function. + * Get characters from the keyboard. + * Return the number of characters that are available. + * If "wtime" == 0 do not wait for characters. + * If "wtime" == -1 wait forever for characters. + * If "wtime" > 0 wait "wtime" milliseconds for a character. + * + * "tb_change_cnt" is the value of typebuf.tb_change_cnt if "buf" points into + * it. When typebuf.tb_change_cnt changes (e.g., when a message is received + * from a remote client) "buf" can no longer be used. "tb_change_cnt" is NULL + * otherwise. + */ + int + ui_inchar( + char_u *buf, + int maxlen, + long wtime, /* don't use "time", MIPS cannot handle it */ + int tb_change_cnt) + { + int retval = 0; + + #if defined(FEAT_GUI) && (defined(UNIX) || defined(VMS)) + /* + * Use the typeahead if there is any. + */ + if (ta_str != NULL) + { + if (maxlen >= ta_len - ta_off) + { + mch_memmove(buf, ta_str + ta_off, (size_t)ta_len); + VIM_CLEAR(ta_str); + return ta_len; + } + mch_memmove(buf, ta_str + ta_off, (size_t)maxlen); + ta_off += maxlen; + return maxlen; + } + #endif + + #ifdef FEAT_PROFILE + if (do_profiling == PROF_YES && wtime != 0) + prof_inchar_enter(); + #endif + + #ifdef NO_CONSOLE_INPUT + /* Don't wait for character input when the window hasn't been opened yet. + * Do try reading, this works when redirecting stdin from a file. + * Must return something, otherwise we'll loop forever. If we run into + * this very often we probably got stuck, exit Vim. */ + if (no_console_input()) + { + static int count = 0; + + # ifndef NO_CONSOLE + retval = mch_inchar(buf, maxlen, wtime, tb_change_cnt); + if (retval > 0 || typebuf_changed(tb_change_cnt) || wtime >= 0) + goto theend; + # endif + if (wtime == -1 && ++count == 1000) + read_error_exit(); + buf[0] = CAR; + retval = 1; + goto theend; + } + #endif + + /* If we are going to wait for some time or block... */ + if (wtime == -1 || wtime > 100L) + { + /* ... allow signals to kill us. */ + (void)vim_handle_signal(SIGNAL_UNBLOCK); + + /* ... there is no need for CTRL-C to interrupt something, don't let + * it set got_int when it was mapped. */ + if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) + ctrl_c_interrupts = FALSE; + } + + /* + * Here we call gui_inchar() or mch_inchar(), the GUI or machine-dependent + * input function. The functionality they implement is like this: + * + * while (not timed out) + * { + * handle-resize; + * parse-queued-messages; + * if (waited for 'updatetime') + * trigger-cursorhold; + * ui_wait_for_chars_or_timer() + * if (character available) + * break; + * } + * + * ui_wait_for_chars_or_timer() does: + * + * while (not timed out) + * { + * if (any-timer-triggered) + * invoke-timer-callback; + * wait-for-character(); + * if (character available) + * break; + * } + * + * wait-for-character() does: + * while (not timed out) + * { + * Wait for event; + * if (something on channel) + * read/write channel; + * else if (resized) + * handle_resize(); + * else if (system event) + * deal-with-system-event; + * else if (character available) + * break; + * } + * + */ + + #ifdef FEAT_GUI + if (gui.in_use) + retval = gui_inchar(buf, maxlen, wtime, tb_change_cnt); + #endif + #ifndef NO_CONSOLE + # ifdef FEAT_GUI + else + # endif + retval = mch_inchar(buf, maxlen, wtime, tb_change_cnt); + #endif + + if (wtime == -1 || wtime > 100L) + /* block SIGHUP et al. */ + (void)vim_handle_signal(SIGNAL_BLOCK); + + ctrl_c_interrupts = TRUE; + + #ifdef NO_CONSOLE_INPUT + theend: + #endif + #ifdef FEAT_PROFILE + if (do_profiling == PROF_YES && wtime != 0) + prof_inchar_exit(); + #endif + return retval; + } + + #if defined(UNIX) || defined(FEAT_GUI) || defined(PROTO) + /* + * Common code for mch_inchar() and gui_inchar(): Wait for a while or + * indefinitely until characters are available, dealing with timers and + * messages on channels. + * + * "buf" may be NULL if the available characters are not to be returned, only + * check if they are available. + * + * Return the number of characters that are available. + * If "wtime" == 0 do not wait for characters. + * If "wtime" == n wait a short time for characters. + * If "wtime" == -1 wait forever for characters. + */ + int + inchar_loop( + char_u *buf, + int maxlen, + long wtime, // don't use "time", MIPS cannot handle it + int tb_change_cnt, + int (*wait_func)(long wtime, int *interrupted, int ignore_input), + int (*resize_func)(int check_only)) + { + int len; + int interrupted = FALSE; + int did_call_wait_func = FALSE; + int did_start_blocking = FALSE; + long wait_time; + long elapsed_time = 0; + #ifdef ELAPSED_FUNC + elapsed_T start_tv; + + ELAPSED_INIT(start_tv); + #endif + + /* repeat until we got a character or waited long enough */ + for (;;) + { + /* Check if window changed size while we were busy, perhaps the ":set + * columns=99" command was used. */ + if (resize_func != NULL) + resize_func(FALSE); + + #ifdef MESSAGE_QUEUE + // Only process messages when waiting. + if (wtime != 0) + { + parse_queued_messages(); + // If input was put directly in typeahead buffer bail out here. + if (typebuf_changed(tb_change_cnt)) + return 0; + } + #endif + if (wtime < 0 && did_start_blocking) + // blocking and already waited for p_ut + wait_time = -1; + else + { + if (wtime >= 0) + wait_time = wtime; + else + // going to block after p_ut + wait_time = p_ut; + #ifdef ELAPSED_FUNC + elapsed_time = ELAPSED_FUNC(start_tv); + #endif + wait_time -= elapsed_time; + + // If the waiting time is now zero or less, we timed out. However, + // loop at least once to check for characters and events. Matters + // when "wtime" is zero. + if (wait_time <= 0 && did_call_wait_func) + { + if (wtime >= 0) + // no character available within "wtime" + return 0; + + // No character available within 'updatetime'. + did_start_blocking = TRUE; + if (trigger_cursorhold() && maxlen >= 3 + && !typebuf_changed(tb_change_cnt)) + { + // Put K_CURSORHOLD in the input buffer or return it. + if (buf == NULL) + { + char_u ibuf[3]; + + ibuf[0] = CSI; + ibuf[1] = KS_EXTRA; + ibuf[2] = (int)KE_CURSORHOLD; + add_to_input_buf(ibuf, 3); + } + else + { + buf[0] = K_SPECIAL; + buf[1] = KS_EXTRA; + buf[2] = (int)KE_CURSORHOLD; + } + return 3; + } + + // There is no character available within 'updatetime' seconds: + // flush all the swap files to disk. Also done when + // interrupted by SIGWINCH. + before_blocking(); + continue; + } + } + + #ifdef FEAT_JOB_CHANNEL + if (wait_time < 0 || wait_time > 100L) + { + // Checking if a job ended requires polling. Do this at least + // every 100 msec. + if (has_pending_job()) + wait_time = 100L; + + // If there is readahead then parse_queued_messages() timed out and + // we should call it again soon. + if (channel_any_readahead()) + wait_time = 10L; + } + #endif + #ifdef FEAT_BEVAL_GUI + if (p_beval && wait_time > 100L) + // The 'balloonexpr' may indirectly invoke a callback while waiting + // for a character, need to check often. + wait_time = 100L; + #endif + + // Wait for a character to be typed or another event, such as the winch + // signal or an event on the monitored file descriptors. + did_call_wait_func = TRUE; + if (wait_func(wait_time, &interrupted, FALSE)) + { + // If input was put directly in typeahead buffer bail out here. + if (typebuf_changed(tb_change_cnt)) + return 0; + + // We might have something to return now. + if (buf == NULL) + // "buf" is NULL, we were just waiting, not actually getting + // input. + return input_available(); + + len = read_from_input_buf(buf, (long)maxlen); + if (len > 0) + return len; + continue; + } + // Timed out or interrupted with no character available. + + #ifndef ELAPSED_FUNC + // estimate the elapsed time + elapsed_time += wait_time; + #endif + + if ((resize_func != NULL && resize_func(TRUE)) + #if defined(FEAT_CLIENTSERVER) && defined(UNIX) + || server_waiting() + #endif + #ifdef MESSAGE_QUEUE + || interrupted + #endif + || wait_time > 0 + || (wtime < 0 && !did_start_blocking)) + // no character available, but something to be done, keep going + continue; + + // no character available or interrupted, return zero + break; + } + return 0; + } + #endif + + #if defined(FEAT_TIMERS) || defined(PROTO) + /* + * Wait for a timer to fire or "wait_func" to return non-zero. + * Returns OK when something was read. + * Returns FAIL when it timed out or was interrupted. + */ + int + ui_wait_for_chars_or_timer( + long wtime, + int (*wait_func)(long wtime, int *interrupted, int ignore_input), + int *interrupted, + int ignore_input) + { + int due_time; + long remaining = wtime; + int tb_change_cnt = typebuf.tb_change_cnt; + # ifdef FEAT_JOB_CHANNEL + int brief_wait = FALSE; + # endif + + // When waiting very briefly don't trigger timers. + if (wtime >= 0 && wtime < 10L) + return wait_func(wtime, NULL, ignore_input); + + while (wtime < 0 || remaining > 0) + { + // Trigger timers and then get the time in wtime until the next one is + // due. Wait up to that time. + due_time = check_due_timer(); + if (typebuf.tb_change_cnt != tb_change_cnt) + { + /* timer may have used feedkeys() */ + return FAIL; + } + if (due_time <= 0 || (wtime > 0 && due_time > remaining)) + due_time = remaining; + # ifdef FEAT_JOB_CHANNEL + if ((due_time < 0 || due_time > 10L) + # ifdef FEAT_GUI + && !gui.in_use + # endif + && (has_pending_job() || channel_any_readahead())) + { + // There is a pending job or channel, should return soon in order + // to handle them ASAP. Do check for input briefly. + due_time = 10L; + brief_wait = TRUE; + } + # endif + if (wait_func(due_time, interrupted, ignore_input)) + return OK; + if ((interrupted != NULL && *interrupted) + # ifdef FEAT_JOB_CHANNEL + || brief_wait + # endif + ) + // Nothing available, but need to return so that side effects get + // handled, such as handling a message on a channel. + return FAIL; + if (wtime > 0) + remaining -= due_time; + } + return FAIL; + } + #endif + + /* + * Return non-zero if a character is available. + */ + int + ui_char_avail(void) + { + #ifdef FEAT_GUI + if (gui.in_use) + { + gui_mch_update(); + return input_available(); + } + #endif + #ifndef NO_CONSOLE + # ifdef NO_CONSOLE_INPUT + if (no_console_input()) + return 0; + # endif + return mch_char_avail(); + #else + return 0; + #endif + } + + /* + * Delay for the given number of milliseconds. If ignoreinput is FALSE then we + * cancel the delay if a key is hit. + */ + void + ui_delay(long msec, int ignoreinput) + { + #ifdef FEAT_GUI + if (gui.in_use && !ignoreinput) + gui_wait_for_chars(msec, typebuf.tb_change_cnt); + else + #endif + mch_delay(msec, ignoreinput); + } + + /* + * If the machine has job control, use it to suspend the program, + * otherwise fake it by starting a new shell. + * When running the GUI iconify the window. + */ + void + ui_suspend(void) + { + #ifdef FEAT_GUI + if (gui.in_use) + { + gui_mch_iconify(); + return; + } + #endif + mch_suspend(); + } + + #if !defined(UNIX) || !defined(SIGTSTP) || defined(PROTO) || defined(__BEOS__) + /* + * When the OS can't really suspend, call this function to start a shell. + * This is never called in the GUI. + */ + void + suspend_shell(void) + { + if (*p_sh == NUL) + emsg(_(e_shellempty)); + else + { + msg_puts(_("new shell started\n")); + do_shell(NULL, 0); + } + } + #endif + + /* + * Try to get the current Vim shell size. Put the result in Rows and Columns. + * Use the new sizes as defaults for 'columns' and 'lines'. + * Return OK when size could be determined, FAIL otherwise. + */ + int + ui_get_shellsize(void) + { + int retval; + + #ifdef FEAT_GUI + if (gui.in_use) + retval = gui_get_shellsize(); + else + #endif + retval = mch_get_shellsize(); + + check_shellsize(); + + /* adjust the default for 'lines' and 'columns' */ + if (retval == OK) + { + set_number_default("lines", Rows); + set_number_default("columns", Columns); + } + return retval; + } + + /* + * Set the size of the Vim shell according to Rows and Columns, if possible. + * The gui_set_shellsize() or mch_set_shellsize() function will try to set the + * new size. If this is not possible, it will adjust Rows and Columns. + */ + void + ui_set_shellsize( + int mustset UNUSED) /* set by the user */ + { + #ifdef FEAT_GUI + if (gui.in_use) + gui_set_shellsize(mustset, TRUE, RESIZE_BOTH); + else + #endif + mch_set_shellsize(); + } + + /* + * Called when Rows and/or Columns changed. Adjust scroll region and mouse + * region. + */ + void + ui_new_shellsize(void) + { + if (full_screen && !exiting) + { + #ifdef FEAT_GUI + if (gui.in_use) + gui_new_shellsize(); + else + #endif + mch_new_shellsize(); + } + } + + #if ((defined(FEAT_EVAL) || defined(FEAT_TERMINAL)) \ + && (defined(FEAT_GUI) \ + || defined(MSWIN) \ + || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)))) \ + || defined(PROTO) + /* + * Get the window position in pixels, if possible. + * Return FAIL when not possible. + */ + int + ui_get_winpos(int *x, int *y, varnumber_T timeout) + { + # ifdef FEAT_GUI + if (gui.in_use) + return gui_mch_get_winpos(x, y); + # endif + # if defined(MSWIN) && (!defined(FEAT_GUI) || defined(VIMDLL)) + return mch_get_winpos(x, y); + # else + # if defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE) + return term_get_winpos(x, y, timeout); + # else + return FAIL; + # endif + # endif + } + #endif + + void + ui_breakcheck(void) + { + ui_breakcheck_force(FALSE); + } + + /* + * When "force" is true also check when the terminal is not in raw mode. + * This is useful to read input on channels. + */ + void + ui_breakcheck_force(int force) + { + static int recursive = FALSE; + int save_updating_screen = updating_screen; + + // We could be called recursively if stderr is redirected, calling + // fill_input_buf() calls settmode() when stdin isn't a tty. settmode() + // calls vgetorpeek() which calls ui_breakcheck() again. + if (recursive) + return; + recursive = TRUE; + + // We do not want gui_resize_shell() to redraw the screen here. + ++updating_screen; + + #ifdef FEAT_GUI + if (gui.in_use) + gui_mch_update(); + else + #endif + mch_breakcheck(force); + + if (save_updating_screen) + updating_screen = TRUE; + else + after_updating_screen(FALSE); + + recursive = FALSE; + } + + /***************************************************************************** + * Functions for copying and pasting text between applications. + * This is always included in a GUI version, but may also be included when the + * clipboard and mouse is available to a terminal version such as xterm. + * Note: there are some more functions in ops.c that handle selection stuff. + * + * Also note that the majority of functions here deal with the X 'primary' + * (visible - for Visual mode use) selection, and only that. There are no + * versions of these for the 'clipboard' selection, as Visual mode has no use + * for them. + */ + + #if defined(FEAT_CLIPBOARD) || defined(PROTO) + + /* + * Selection stuff using Visual mode, for cutting and pasting text to other + * windows. + */ + + /* + * Call this to initialise the clipboard. Pass it FALSE if the clipboard code + * is included, but the clipboard can not be used, or TRUE if the clipboard can + * be used. Eg unix may call this with FALSE, then call it again with TRUE if + * the GUI starts. + */ + void + clip_init(int can_use) + { + VimClipboard *cb; + + cb = &clip_star; + for (;;) + { + cb->available = can_use; + cb->owned = FALSE; + cb->start.lnum = 0; + cb->start.col = 0; + cb->end.lnum = 0; + cb->end.col = 0; + cb->state = SELECT_CLEARED; + + if (cb == &clip_plus) + break; + cb = &clip_plus; + } + } + + /* + * Check whether the VIsual area has changed, and if so try to become the owner + * of the selection, and free any old converted selection we may still have + * lying around. If the VIsual mode has ended, make a copy of what was + * selected so we can still give it to others. Will probably have to make sure + * this is called whenever VIsual mode is ended. + */ + void + clip_update_selection(VimClipboard *clip) + { + pos_T start, end; + + /* If visual mode is only due to a redo command ("."), then ignore it */ + if (!redo_VIsual_busy && VIsual_active && (State & NORMAL)) + { + if (LT_POS(VIsual, curwin->w_cursor)) + { + start = VIsual; + end = curwin->w_cursor; + if (has_mbyte) + end.col += (*mb_ptr2len)(ml_get_cursor()) - 1; + } + else + { + start = curwin->w_cursor; + end = VIsual; + } + if (!EQUAL_POS(clip->start, start) + || !EQUAL_POS(clip->end, end) + || clip->vmode != VIsual_mode) + { + clip_clear_selection(clip); + clip->start = start; + clip->end = end; + clip->vmode = VIsual_mode; + clip_free_selection(clip); + clip_own_selection(clip); + clip_gen_set_selection(clip); + } + } + } + + void + clip_own_selection(VimClipboard *cbd) + { + /* + * Also want to check somehow that we are reading from the keyboard rather + * than a mapping etc. + */ + #ifdef FEAT_X11 + /* Always own the selection, we might have lost it without being + * notified, e.g. during a ":sh" command. */ + if (cbd->available) + { + int was_owned = cbd->owned; + + cbd->owned = (clip_gen_own_selection(cbd) == OK); + if (!was_owned && (cbd == &clip_star || cbd == &clip_plus)) + { + /* May have to show a different kind of highlighting for the + * selected area. There is no specific redraw command for this, + * just redraw all windows on the current buffer. */ + if (cbd->owned + && (get_real_state() == VISUAL + || get_real_state() == SELECTMODE) + && (cbd == &clip_star ? clip_isautosel_star() + : clip_isautosel_plus()) + && HL_ATTR(HLF_V) != HL_ATTR(HLF_VNC)) + redraw_curbuf_later(INVERTED_ALL); + } + } + #else + /* Only own the clipboard when we didn't own it yet. */ + if (!cbd->owned && cbd->available) + cbd->owned = (clip_gen_own_selection(cbd) == OK); + #endif + } + + void + clip_lose_selection(VimClipboard *cbd) + { + #ifdef FEAT_X11 + int was_owned = cbd->owned; + #endif + int visual_selection = FALSE; + + if (cbd == &clip_star || cbd == &clip_plus) + visual_selection = TRUE; + + clip_free_selection(cbd); + cbd->owned = FALSE; + if (visual_selection) + clip_clear_selection(cbd); + clip_gen_lose_selection(cbd); + #ifdef FEAT_X11 + if (visual_selection) + { + /* May have to show a different kind of highlighting for the selected + * area. There is no specific redraw command for this, just redraw all + * windows on the current buffer. */ + if (was_owned + && (get_real_state() == VISUAL + || get_real_state() == SELECTMODE) + && (cbd == &clip_star ? + clip_isautosel_star() : clip_isautosel_plus()) + && HL_ATTR(HLF_V) != HL_ATTR(HLF_VNC)) + { + update_curbuf(INVERTED_ALL); + setcursor(); + cursor_on(); + out_flush_cursor(TRUE, FALSE); + } + } + #endif + } + + static void + clip_copy_selection(VimClipboard *clip) + { + if (VIsual_active && (State & NORMAL) && clip->available) + { + clip_update_selection(clip); + clip_free_selection(clip); + clip_own_selection(clip); + if (clip->owned) + clip_get_selection(clip); + clip_gen_set_selection(clip); + } + } + + /* + * Save and restore clip_unnamed before doing possibly many changes. This + * prevents accessing the clipboard very often which might slow down Vim + * considerably. + */ + static int global_change_count = 0; /* if set, inside a start_global_changes */ + static int clipboard_needs_update = FALSE; /* clipboard needs to be updated */ + static int clip_did_set_selection = TRUE; + + /* + * Save clip_unnamed and reset it. + */ + void + start_global_changes(void) + { + if (++global_change_count > 1) + return; + clip_unnamed_saved = clip_unnamed; + clipboard_needs_update = FALSE; + + if (clip_did_set_selection) + { + clip_unnamed = FALSE; + clip_did_set_selection = FALSE; + } + } + + /* + * Return TRUE if setting the clipboard was postponed, it already contains the + * right text. + */ + int + is_clipboard_needs_update() + { + return clipboard_needs_update; + } + + /* + * Restore clip_unnamed and set the selection when needed. + */ + void + end_global_changes(void) + { + if (--global_change_count > 0) + /* recursive */ + return; + if (!clip_did_set_selection) + { + clip_did_set_selection = TRUE; + clip_unnamed = clip_unnamed_saved; + clip_unnamed_saved = FALSE; + if (clipboard_needs_update) + { + /* only store something in the clipboard, + * if we have yanked anything to it */ + if (clip_unnamed & CLIP_UNNAMED) + { + clip_own_selection(&clip_star); + clip_gen_set_selection(&clip_star); + } + if (clip_unnamed & CLIP_UNNAMED_PLUS) + { + clip_own_selection(&clip_plus); + clip_gen_set_selection(&clip_plus); + } + } + } + clipboard_needs_update = FALSE; + } + + /* + * Called when Visual mode is ended: update the selection. + */ + void + clip_auto_select(void) + { + if (clip_isautosel_star()) + clip_copy_selection(&clip_star); + if (clip_isautosel_plus()) + clip_copy_selection(&clip_plus); + } + + /* + * Return TRUE if automatic selection of Visual area is desired for the * + * register. + */ + int + clip_isautosel_star(void) + { + return ( + #ifdef FEAT_GUI + gui.in_use ? (vim_strchr(p_go, GO_ASEL) != NULL) : + #endif + clip_autoselect_star); + } + + /* + * Return TRUE if automatic selection of Visual area is desired for the + + * register. + */ + int + clip_isautosel_plus(void) + { + return ( + #ifdef FEAT_GUI + gui.in_use ? (vim_strchr(p_go, GO_ASELPLUS) != NULL) : + #endif + clip_autoselect_plus); + } + + + /* + * Stuff for general mouse selection, without using Visual mode. + */ + + static void clip_invert_area(int, int, int, int, int how); + static void clip_invert_rectangle(int row, int col, int height, int width, int invert); + static void clip_get_word_boundaries(VimClipboard *, int, int); + static int clip_get_line_end(int); + static void clip_update_modeless_selection(VimClipboard *, int, int, + int, int); + + /* flags for clip_invert_area() */ + #define CLIP_CLEAR 1 + #define CLIP_SET 2 + #define CLIP_TOGGLE 3 + + /* + * Start, continue or end a modeless selection. Used when editing the + * command-line and in the cmdline window. + */ + void + clip_modeless(int button, int is_click, int is_drag) + { + int repeat; + + repeat = ((clip_star.mode == SELECT_MODE_CHAR + || clip_star.mode == SELECT_MODE_LINE) + && (mod_mask & MOD_MASK_2CLICK)) + || (clip_star.mode == SELECT_MODE_WORD + && (mod_mask & MOD_MASK_3CLICK)); + if (is_click && button == MOUSE_RIGHT) + { + /* Right mouse button: If there was no selection, start one. + * Otherwise extend the existing selection. */ + if (clip_star.state == SELECT_CLEARED) + clip_start_selection(mouse_col, mouse_row, FALSE); + clip_process_selection(button, mouse_col, mouse_row, repeat); + } + else if (is_click) + clip_start_selection(mouse_col, mouse_row, repeat); + else if (is_drag) + { + /* Don't try extending a selection if there isn't one. Happens when + * button-down is in the cmdline and them moving mouse upwards. */ + if (clip_star.state != SELECT_CLEARED) + clip_process_selection(button, mouse_col, mouse_row, repeat); + } + else /* release */ + clip_process_selection(MOUSE_RELEASE, mouse_col, mouse_row, FALSE); + } + + /* + * Compare two screen positions ala strcmp() + */ + static int + clip_compare_pos( + int row1, + int col1, + int row2, + int col2) + { + if (row1 > row2) return(1); + if (row1 < row2) return(-1); + if (col1 > col2) return(1); + if (col1 < col2) return(-1); + return(0); + } + + /* + * Start the selection + */ + void + clip_start_selection(int col, int row, int repeated_click) + { + VimClipboard *cb = &clip_star; + + if (cb->state == SELECT_DONE) + clip_clear_selection(cb); + + row = check_row(row); + col = check_col(col); + col = mb_fix_col(col, row); + + cb->start.lnum = row; + cb->start.col = col; + cb->end = cb->start; + cb->origin_row = (short_u)cb->start.lnum; + cb->state = SELECT_IN_PROGRESS; + + if (repeated_click) + { + if (++cb->mode > SELECT_MODE_LINE) + cb->mode = SELECT_MODE_CHAR; + } + else + cb->mode = SELECT_MODE_CHAR; + + #ifdef FEAT_GUI + /* clear the cursor until the selection is made */ + if (gui.in_use) + gui_undraw_cursor(); + #endif + + switch (cb->mode) + { + case SELECT_MODE_CHAR: + cb->origin_start_col = cb->start.col; + cb->word_end_col = clip_get_line_end((int)cb->start.lnum); + break; + + case SELECT_MODE_WORD: + clip_get_word_boundaries(cb, (int)cb->start.lnum, cb->start.col); + cb->origin_start_col = cb->word_start_col; + cb->origin_end_col = cb->word_end_col; + + clip_invert_area((int)cb->start.lnum, cb->word_start_col, + (int)cb->end.lnum, cb->word_end_col, CLIP_SET); + cb->start.col = cb->word_start_col; + cb->end.col = cb->word_end_col; + break; + + case SELECT_MODE_LINE: + clip_invert_area((int)cb->start.lnum, 0, (int)cb->start.lnum, + (int)Columns, CLIP_SET); + cb->start.col = 0; + cb->end.col = Columns; + break; + } + + cb->prev = cb->start; + + #ifdef DEBUG_SELECTION + printf("Selection started at (%u,%u)\n", cb->start.lnum, cb->start.col); + #endif + } + + /* + * Continue processing the selection + */ + void + clip_process_selection( + int button, + int col, + int row, + int_u repeated_click) + { + VimClipboard *cb = &clip_star; + int diff; + int slen = 1; /* cursor shape width */ + + if (button == MOUSE_RELEASE) + { + /* Check to make sure we have something selected */ + if (cb->start.lnum == cb->end.lnum && cb->start.col == cb->end.col) + { + #ifdef FEAT_GUI + if (gui.in_use) + gui_update_cursor(FALSE, FALSE); + #endif + cb->state = SELECT_CLEARED; + return; + } + + #ifdef DEBUG_SELECTION + printf("Selection ended: (%u,%u) to (%u,%u)\n", cb->start.lnum, + cb->start.col, cb->end.lnum, cb->end.col); + #endif + if (clip_isautosel_star() + || ( + #ifdef FEAT_GUI + gui.in_use ? (vim_strchr(p_go, GO_ASELML) != NULL) : + #endif + clip_autoselectml)) + clip_copy_modeless_selection(FALSE); + #ifdef FEAT_GUI + if (gui.in_use) + gui_update_cursor(FALSE, FALSE); + #endif + + cb->state = SELECT_DONE; + return; + } + + row = check_row(row); + col = check_col(col); + col = mb_fix_col(col, row); + + if (col == (int)cb->prev.col && row == cb->prev.lnum && !repeated_click) + return; + + /* + * When extending the selection with the right mouse button, swap the + * start and end if the position is before half the selection + */ + if (cb->state == SELECT_DONE && button == MOUSE_RIGHT) + { + /* + * If the click is before the start, or the click is inside the + * selection and the start is the closest side, set the origin to the + * end of the selection. + */ + if (clip_compare_pos(row, col, (int)cb->start.lnum, cb->start.col) < 0 + || (clip_compare_pos(row, col, + (int)cb->end.lnum, cb->end.col) < 0 + && (((cb->start.lnum == cb->end.lnum + && cb->end.col - col > col - cb->start.col)) + || ((diff = (cb->end.lnum - row) - + (row - cb->start.lnum)) > 0 + || (diff == 0 && col < (int)(cb->start.col + + cb->end.col) / 2))))) + { + cb->origin_row = (short_u)cb->end.lnum; + cb->origin_start_col = cb->end.col - 1; + cb->origin_end_col = cb->end.col; + } + else + { + cb->origin_row = (short_u)cb->start.lnum; + cb->origin_start_col = cb->start.col; + cb->origin_end_col = cb->start.col; + } + if (cb->mode == SELECT_MODE_WORD && !repeated_click) + cb->mode = SELECT_MODE_CHAR; + } + + /* set state, for when using the right mouse button */ + cb->state = SELECT_IN_PROGRESS; + + #ifdef DEBUG_SELECTION + printf("Selection extending to (%d,%d)\n", row, col); + #endif + + if (repeated_click && ++cb->mode > SELECT_MODE_LINE) + cb->mode = SELECT_MODE_CHAR; + + switch (cb->mode) + { + case SELECT_MODE_CHAR: + /* If we're on a different line, find where the line ends */ + if (row != cb->prev.lnum) + cb->word_end_col = clip_get_line_end(row); + + /* See if we are before or after the origin of the selection */ + if (clip_compare_pos(row, col, cb->origin_row, + cb->origin_start_col) >= 0) + { + if (col >= (int)cb->word_end_col) + clip_update_modeless_selection(cb, cb->origin_row, + cb->origin_start_col, row, (int)Columns); + else + { + if (has_mbyte && mb_lefthalve(row, col)) + slen = 2; + clip_update_modeless_selection(cb, cb->origin_row, + cb->origin_start_col, row, col + slen); + } + } + else + { + if (has_mbyte + && mb_lefthalve(cb->origin_row, cb->origin_start_col)) + slen = 2; + if (col >= (int)cb->word_end_col) + clip_update_modeless_selection(cb, row, cb->word_end_col, + cb->origin_row, cb->origin_start_col + slen); + else + clip_update_modeless_selection(cb, row, col, + cb->origin_row, cb->origin_start_col + slen); + } + break; + + case SELECT_MODE_WORD: + /* If we are still within the same word, do nothing */ + if (row == cb->prev.lnum && col >= (int)cb->word_start_col + && col < (int)cb->word_end_col && !repeated_click) + return; + + /* Get new word boundaries */ + clip_get_word_boundaries(cb, row, col); + + /* Handle being after the origin point of selection */ + if (clip_compare_pos(row, col, cb->origin_row, + cb->origin_start_col) >= 0) + clip_update_modeless_selection(cb, cb->origin_row, + cb->origin_start_col, row, cb->word_end_col); + else + clip_update_modeless_selection(cb, row, cb->word_start_col, + cb->origin_row, cb->origin_end_col); + break; + + case SELECT_MODE_LINE: + if (row == cb->prev.lnum && !repeated_click) + return; + + if (clip_compare_pos(row, col, cb->origin_row, + cb->origin_start_col) >= 0) + clip_update_modeless_selection(cb, cb->origin_row, 0, row, + (int)Columns); + else + clip_update_modeless_selection(cb, row, 0, cb->origin_row, + (int)Columns); + break; + } + + cb->prev.lnum = row; + cb->prev.col = col; + + #ifdef DEBUG_SELECTION + printf("Selection is: (%u,%u) to (%u,%u)\n", cb->start.lnum, + cb->start.col, cb->end.lnum, cb->end.col); + #endif + } + + # if defined(FEAT_GUI) || defined(PROTO) + /* + * Redraw part of the selection if character at "row,col" is inside of it. + * Only used for the GUI. + */ + void + clip_may_redraw_selection(int row, int col, int len) + { + int start = col; + int end = col + len; + + if (clip_star.state != SELECT_CLEARED + && row >= clip_star.start.lnum + && row <= clip_star.end.lnum) + { + if (row == clip_star.start.lnum && start < (int)clip_star.start.col) + start = clip_star.start.col; + if (row == clip_star.end.lnum && end > (int)clip_star.end.col) + end = clip_star.end.col; + if (end > start) + clip_invert_area(row, start, row, end, 0); + } + } + # endif + + /* + * Called from outside to clear selected region from the display + */ + void + clip_clear_selection(VimClipboard *cbd) + { + + if (cbd->state == SELECT_CLEARED) + return; + + clip_invert_area((int)cbd->start.lnum, cbd->start.col, (int)cbd->end.lnum, + cbd->end.col, CLIP_CLEAR); + cbd->state = SELECT_CLEARED; + } + + /* + * Clear the selection if any lines from "row1" to "row2" are inside of it. + */ + void + clip_may_clear_selection(int row1, int row2) + { + if (clip_star.state == SELECT_DONE + && row2 >= clip_star.start.lnum + && row1 <= clip_star.end.lnum) + clip_clear_selection(&clip_star); + } + + /* + * Called before the screen is scrolled up or down. Adjusts the line numbers + * of the selection. Call with big number when clearing the screen. + */ + void + clip_scroll_selection( + int rows) /* negative for scroll down */ + { + int lnum; + + if (clip_star.state == SELECT_CLEARED) + return; + + lnum = clip_star.start.lnum - rows; + if (lnum <= 0) + clip_star.start.lnum = 0; + else if (lnum >= screen_Rows) /* scrolled off of the screen */ + clip_star.state = SELECT_CLEARED; + else + clip_star.start.lnum = lnum; + + lnum = clip_star.end.lnum - rows; + if (lnum < 0) /* scrolled off of the screen */ + clip_star.state = SELECT_CLEARED; + else if (lnum >= screen_Rows) + clip_star.end.lnum = screen_Rows - 1; + else + clip_star.end.lnum = lnum; + } + + /* + * Invert a region of the display between a starting and ending row and column + * Values for "how": + * CLIP_CLEAR: undo inversion + * CLIP_SET: set inversion + * CLIP_TOGGLE: set inversion if pos1 < pos2, undo inversion otherwise. + * 0: invert (GUI only). + */ + static void + clip_invert_area( + int row1, + int col1, + int row2, + int col2, + int how) + { + int invert = FALSE; + + if (how == CLIP_SET) + invert = TRUE; + + /* Swap the from and to positions so the from is always before */ + if (clip_compare_pos(row1, col1, row2, col2) > 0) + { + int tmp_row, tmp_col; + + tmp_row = row1; + tmp_col = col1; + row1 = row2; + col1 = col2; + row2 = tmp_row; + col2 = tmp_col; + } + else if (how == CLIP_TOGGLE) + invert = TRUE; + + /* If all on the same line, do it the easy way */ + if (row1 == row2) + { + clip_invert_rectangle(row1, col1, 1, col2 - col1, invert); + } + else + { + /* Handle a piece of the first line */ + if (col1 > 0) + { + clip_invert_rectangle(row1, col1, 1, (int)Columns - col1, invert); + row1++; + } + + /* Handle a piece of the last line */ + if (col2 < Columns - 1) + { + clip_invert_rectangle(row2, 0, 1, col2, invert); + row2--; + } + + /* Handle the rectangle thats left */ + if (row2 >= row1) + clip_invert_rectangle(row1, 0, row2 - row1 + 1, (int)Columns, + invert); + } + } + + /* + * Invert or un-invert a rectangle of the screen. + * "invert" is true if the result is inverted. + */ + static void + clip_invert_rectangle( + int row, + int col, + int height, + int width, + int invert) + { + #ifdef FEAT_GUI + if (gui.in_use) + gui_mch_invert_rectangle(row, col, height, width); + else + #endif + screen_draw_rectangle(row, col, height, width, invert); + } + + /* + * Copy the currently selected area into the '*' register so it will be + * available for pasting. + * When "both" is TRUE also copy to the '+' register. + */ + void + clip_copy_modeless_selection(int both UNUSED) + { + char_u *buffer; + char_u *bufp; + int row; + int start_col; + int end_col; + int line_end_col; + int add_newline_flag = FALSE; + int len; + char_u *p; + int row1 = clip_star.start.lnum; + int col1 = clip_star.start.col; + int row2 = clip_star.end.lnum; + int col2 = clip_star.end.col; + + /* Can't use ScreenLines unless initialized */ + if (ScreenLines == NULL) + return; + + /* + * Make sure row1 <= row2, and if row1 == row2 that col1 <= col2. + */ + if (row1 > row2) + { + row = row1; row1 = row2; row2 = row; + row = col1; col1 = col2; col2 = row; + } + else if (row1 == row2 && col1 > col2) + { + row = col1; col1 = col2; col2 = row; + } + /* correct starting point for being on right halve of double-wide char */ + p = ScreenLines + LineOffset[row1]; + if (enc_dbcs != 0) + col1 -= (*mb_head_off)(p, p + col1); + else if (enc_utf8 && p[col1] == 0) + --col1; + + /* Create a temporary buffer for storing the text */ + len = (row2 - row1 + 1) * Columns + 1; + if (enc_dbcs != 0) + len *= 2; /* max. 2 bytes per display cell */ + else if (enc_utf8) + len *= MB_MAXBYTES; + buffer = alloc(len); + if (buffer == NULL) /* out of memory */ + return; + + /* Process each row in the selection */ + for (bufp = buffer, row = row1; row <= row2; row++) + { + if (row == row1) + start_col = col1; + else + start_col = 0; + + if (row == row2) + end_col = col2; + else + end_col = Columns; + + line_end_col = clip_get_line_end(row); + + /* See if we need to nuke some trailing whitespace */ + if (end_col >= Columns && (row < row2 || end_col > line_end_col)) + { + /* Get rid of trailing whitespace */ + end_col = line_end_col; + if (end_col < start_col) + end_col = start_col; + + /* If the last line extended to the end, add an extra newline */ + if (row == row2) + add_newline_flag = TRUE; + } + + /* If after the first row, we need to always add a newline */ + if (row > row1 && !LineWraps[row - 1]) + *bufp++ = NL; + + if (row < screen_Rows && end_col <= screen_Columns) + { + if (enc_dbcs != 0) + { + int i; + + p = ScreenLines + LineOffset[row]; + for (i = start_col; i < end_col; ++i) + if (enc_dbcs == DBCS_JPNU && p[i] == 0x8e) + { + /* single-width double-byte char */ + *bufp++ = 0x8e; + *bufp++ = ScreenLines2[LineOffset[row] + i]; + } + else + { + *bufp++ = p[i]; + if (MB_BYTE2LEN(p[i]) == 2) + *bufp++ = p[++i]; + } + } + else if (enc_utf8) + { + int off; + int i; + int ci; + + off = LineOffset[row]; + for (i = start_col; i < end_col; ++i) + { + /* The base character is either in ScreenLinesUC[] or + * ScreenLines[]. */ + if (ScreenLinesUC[off + i] == 0) + *bufp++ = ScreenLines[off + i]; + else + { + bufp += utf_char2bytes(ScreenLinesUC[off + i], bufp); + for (ci = 0; ci < Screen_mco; ++ci) + { + /* Add a composing character. */ + if (ScreenLinesC[ci][off + i] == 0) + break; + bufp += utf_char2bytes(ScreenLinesC[ci][off + i], + bufp); + } + } + /* Skip right halve of double-wide character. */ + if (ScreenLines[off + i + 1] == 0) + ++i; + } + } + else + { + STRNCPY(bufp, ScreenLines + LineOffset[row] + start_col, + end_col - start_col); + bufp += end_col - start_col; + } + } + } + + /* Add a newline at the end if the selection ended there */ + if (add_newline_flag) + *bufp++ = NL; + + /* First cleanup any old selection and become the owner. */ + clip_free_selection(&clip_star); + clip_own_selection(&clip_star); + + /* Yank the text into the '*' register. */ + clip_yank_selection(MCHAR, buffer, (long)(bufp - buffer), &clip_star); + + /* Make the register contents available to the outside world. */ + clip_gen_set_selection(&clip_star); + + #ifdef FEAT_X11 + if (both) + { + /* Do the same for the '+' register. */ + clip_free_selection(&clip_plus); + clip_own_selection(&clip_plus); + clip_yank_selection(MCHAR, buffer, (long)(bufp - buffer), &clip_plus); + clip_gen_set_selection(&clip_plus); + } + #endif + vim_free(buffer); + } + + /* + * Find the starting and ending positions of the word at the given row and + * column. Only white-separated words are recognized here. + */ + #define CHAR_CLASS(c) (c <= ' ' ? ' ' : vim_iswordc(c)) + + static void + clip_get_word_boundaries(VimClipboard *cb, int row, int col) + { + int start_class; + int temp_col; + char_u *p; + int mboff; + + if (row >= screen_Rows || col >= screen_Columns || ScreenLines == NULL) + return; + + p = ScreenLines + LineOffset[row]; + /* Correct for starting in the right halve of a double-wide char */ + if (enc_dbcs != 0) + col -= dbcs_screen_head_off(p, p + col); + else if (enc_utf8 && p[col] == 0) + --col; + start_class = CHAR_CLASS(p[col]); + + temp_col = col; + for ( ; temp_col > 0; temp_col--) + if (enc_dbcs != 0 + && (mboff = dbcs_screen_head_off(p, p + temp_col - 1)) > 0) + temp_col -= mboff; + else if (CHAR_CLASS(p[temp_col - 1]) != start_class + && !(enc_utf8 && p[temp_col - 1] == 0)) + break; + cb->word_start_col = temp_col; + + temp_col = col; + for ( ; temp_col < screen_Columns; temp_col++) + if (enc_dbcs != 0 && dbcs_ptr2cells(p + temp_col) == 2) + ++temp_col; + else if (CHAR_CLASS(p[temp_col]) != start_class + && !(enc_utf8 && p[temp_col] == 0)) + break; + cb->word_end_col = temp_col; + } + + /* + * Find the column position for the last non-whitespace character on the given + * line. + */ + static int + clip_get_line_end(int row) + { + int i; + + if (row >= screen_Rows || ScreenLines == NULL) + return 0; + for (i = screen_Columns; i > 0; i--) + if (ScreenLines[LineOffset[row] + i - 1] != ' ') + break; + return i; + } + + /* + * Update the currently selected region by adding and/or subtracting from the + * beginning or end and inverting the changed area(s). + */ + static void + clip_update_modeless_selection( + VimClipboard *cb, + int row1, + int col1, + int row2, + int col2) + { + /* See if we changed at the beginning of the selection */ + if (row1 != cb->start.lnum || col1 != (int)cb->start.col) + { + clip_invert_area(row1, col1, (int)cb->start.lnum, cb->start.col, + CLIP_TOGGLE); + cb->start.lnum = row1; + cb->start.col = col1; + } + + /* See if we changed at the end of the selection */ + if (row2 != cb->end.lnum || col2 != (int)cb->end.col) + { + clip_invert_area((int)cb->end.lnum, cb->end.col, row2, col2, + CLIP_TOGGLE); + cb->end.lnum = row2; + cb->end.col = col2; + } + } + + int + clip_gen_own_selection(VimClipboard *cbd) + { + #ifdef FEAT_XCLIPBOARD + # ifdef FEAT_GUI + if (gui.in_use) + return clip_mch_own_selection(cbd); + else + # endif + return clip_xterm_own_selection(cbd); + #else + return clip_mch_own_selection(cbd); + #endif + } + + void + clip_gen_lose_selection(VimClipboard *cbd) + { + #ifdef FEAT_XCLIPBOARD + # ifdef FEAT_GUI + if (gui.in_use) + clip_mch_lose_selection(cbd); + else + # endif + clip_xterm_lose_selection(cbd); + #else + clip_mch_lose_selection(cbd); + #endif + } + + void + clip_gen_set_selection(VimClipboard *cbd) + { + if (!clip_did_set_selection) + { + /* Updating postponed, so that accessing the system clipboard won't + * hang Vim when accessing it many times (e.g. on a :g command). */ + if ((cbd == &clip_plus && (clip_unnamed_saved & CLIP_UNNAMED_PLUS)) + || (cbd == &clip_star && (clip_unnamed_saved & CLIP_UNNAMED))) + { + clipboard_needs_update = TRUE; + return; + } + } + #ifdef FEAT_XCLIPBOARD + # ifdef FEAT_GUI + if (gui.in_use) + clip_mch_set_selection(cbd); + else + # endif + clip_xterm_set_selection(cbd); + #else + clip_mch_set_selection(cbd); + #endif + } + + void + clip_gen_request_selection(VimClipboard *cbd) + { + #ifdef FEAT_XCLIPBOARD + # ifdef FEAT_GUI + if (gui.in_use) + clip_mch_request_selection(cbd); + else + # endif + clip_xterm_request_selection(cbd); + #else + clip_mch_request_selection(cbd); + #endif + } + + #if (defined(FEAT_X11) && defined(USE_SYSTEM)) || defined(PROTO) + int + clip_gen_owner_exists(VimClipboard *cbd UNUSED) + { + #ifdef FEAT_XCLIPBOARD + # ifdef FEAT_GUI_GTK + if (gui.in_use) + return clip_gtk_owner_exists(cbd); + else + # endif + return clip_x11_owner_exists(cbd); + #else + return TRUE; + #endif + } + #endif + + #endif /* FEAT_CLIPBOARD */ + + /***************************************************************************** + * Functions that handle the input buffer. + * This is used for any GUI version, and the unix terminal version. + * + * For Unix, the input characters are buffered to be able to check for a + * CTRL-C. This should be done with signals, but I don't know how to do that + * in a portable way for a tty in RAW mode. + * + * For the client-server code in the console the received keys are put in the + * input buffer. + */ + + #if defined(USE_INPUT_BUF) || defined(PROTO) + + /* + * Internal typeahead buffer. Includes extra space for long key code + * descriptions which would otherwise overflow. The buffer is considered full + * when only this extra space (or part of it) remains. + */ + #if defined(FEAT_JOB_CHANNEL) || defined(FEAT_CLIENTSERVER) + /* + * NetBeans stuffs debugger commands into the input buffer. + * This requires a larger buffer... + * (Madsen) Go with this for remote input as well ... + */ + # define INBUFLEN 4096 + #else + # define INBUFLEN 250 + #endif + + static char_u inbuf[INBUFLEN + MAX_KEY_CODE_LEN]; + static int inbufcount = 0; /* number of chars in inbuf[] */ + + /* + * vim_is_input_buf_full(), vim_is_input_buf_empty(), add_to_input_buf(), and + * trash_input_buf() are functions for manipulating the input buffer. These + * are used by the gui_* calls when a GUI is used to handle keyboard input. + */ + + int + vim_is_input_buf_full(void) + { + return (inbufcount >= INBUFLEN); + } + + int + vim_is_input_buf_empty(void) + { + return (inbufcount == 0); + } + + #if defined(FEAT_OLE) || defined(PROTO) + int + vim_free_in_input_buf(void) + { + return (INBUFLEN - inbufcount); + } + #endif + + #if defined(FEAT_GUI_GTK) || defined(PROTO) + int + vim_used_in_input_buf(void) + { + return inbufcount; + } + #endif + + /* + * Return the current contents of the input buffer and make it empty. + * The returned pointer must be passed to set_input_buf() later. + */ + char_u * + get_input_buf(void) + { + garray_T *gap; + + /* We use a growarray to store the data pointer and the length. */ + gap = ALLOC_ONE(garray_T); + if (gap != NULL) + { + /* Add one to avoid a zero size. */ + gap->ga_data = alloc(inbufcount + 1); + if (gap->ga_data != NULL) + mch_memmove(gap->ga_data, inbuf, (size_t)inbufcount); + gap->ga_len = inbufcount; + } + trash_input_buf(); + return (char_u *)gap; + } + + /* + * Restore the input buffer with a pointer returned from get_input_buf(). + * The allocated memory is freed, this only works once! + */ + void + set_input_buf(char_u *p) + { + garray_T *gap = (garray_T *)p; + + if (gap != NULL) + { + if (gap->ga_data != NULL) + { + mch_memmove(inbuf, gap->ga_data, gap->ga_len); + inbufcount = gap->ga_len; + vim_free(gap->ga_data); + } + vim_free(gap); + } + } + + /* + * Add the given bytes to the input buffer + * Special keys start with CSI. A real CSI must have been translated to + * CSI KS_EXTRA KE_CSI. K_SPECIAL doesn't require translation. + */ + void + add_to_input_buf(char_u *s, int len) + { + if (inbufcount + len > INBUFLEN + MAX_KEY_CODE_LEN) + return; /* Shouldn't ever happen! */ + + #ifdef FEAT_HANGULIN + if ((State & (INSERT|CMDLINE)) && hangul_input_state_get()) + if ((len = hangul_input_process(s, len)) == 0) + return; + #endif + + while (len--) + inbuf[inbufcount++] = *s++; + } + + /* + * Add "str[len]" to the input buffer while escaping CSI bytes. + */ + void + add_to_input_buf_csi(char_u *str, int len) + { + int i; + char_u buf[2]; + + for (i = 0; i < len; ++i) + { + add_to_input_buf(str + i, 1); + if (str[i] == CSI) + { + /* Turn CSI into K_CSI. */ + buf[0] = KS_EXTRA; + buf[1] = (int)KE_CSI; + add_to_input_buf(buf, 2); + } + } + } + + #if defined(FEAT_HANGULIN) || defined(PROTO) + void + push_raw_key(char_u *s, int len) + { + char_u *tmpbuf; + char_u *inp = s; + + /* use the conversion result if possible */ + tmpbuf = hangul_string_convert(s, &len); + if (tmpbuf != NULL) + inp = tmpbuf; + + for (; len--; inp++) + { + inbuf[inbufcount++] = *inp; + if (*inp == CSI) + { + /* Turn CSI into K_CSI. */ + inbuf[inbufcount++] = KS_EXTRA; + inbuf[inbufcount++] = (int)KE_CSI; + } + } + vim_free(tmpbuf); + } + #endif + + /* Remove everything from the input buffer. Called when ^C is found */ + void + trash_input_buf(void) + { + inbufcount = 0; + } + + /* + * Read as much data from the input buffer as possible up to maxlen, and store + * it in buf. + */ + int + read_from_input_buf(char_u *buf, long maxlen) + { + if (inbufcount == 0) /* if the buffer is empty, fill it */ + fill_input_buf(TRUE); + if (maxlen > inbufcount) + maxlen = inbufcount; + mch_memmove(buf, inbuf, (size_t)maxlen); + inbufcount -= maxlen; + if (inbufcount) + mch_memmove(inbuf, inbuf + maxlen, (size_t)inbufcount); + return (int)maxlen; + } + + void + fill_input_buf(int exit_on_error UNUSED) + { + #if defined(UNIX) || defined(VMS) || defined(MACOS_X) + int len; + int try; + static int did_read_something = FALSE; + static char_u *rest = NULL; /* unconverted rest of previous read */ + static int restlen = 0; + int unconverted; + #endif + + #ifdef FEAT_GUI + if (gui.in_use + # ifdef NO_CONSOLE_INPUT + /* Don't use the GUI input when the window hasn't been opened yet. + * We get here from ui_inchar() when we should try reading from stdin. */ + && !no_console_input() + # endif + ) + { + gui_mch_update(); + return; + } + #endif + #if defined(UNIX) || defined(VMS) || defined(MACOS_X) + if (vim_is_input_buf_full()) + return; + /* + * Fill_input_buf() is only called when we really need a character. + * If we can't get any, but there is some in the buffer, just return. + * If we can't get any, and there isn't any in the buffer, we give up and + * exit Vim. + */ + # ifdef __BEOS__ + /* + * On the BeBox version (for now), all input is secretly performed within + * beos_select() which is called from RealWaitForChar(). + */ + while (!vim_is_input_buf_full() && RealWaitForChar(read_cmd_fd, 0, NULL)) + ; + len = inbufcount; + inbufcount = 0; + # else + + if (rest != NULL) + { + /* Use remainder of previous call, starts with an invalid character + * that may become valid when reading more. */ + if (restlen > INBUFLEN - inbufcount) + unconverted = INBUFLEN - inbufcount; + else + unconverted = restlen; + mch_memmove(inbuf + inbufcount, rest, unconverted); + if (unconverted == restlen) + VIM_CLEAR(rest); + else + { + restlen -= unconverted; + mch_memmove(rest, rest + unconverted, restlen); + } + inbufcount += unconverted; + } + else + unconverted = 0; + + len = 0; /* to avoid gcc warning */ + for (try = 0; try < 100; ++try) + { + size_t readlen = (size_t)((INBUFLEN - inbufcount) + / input_conv.vc_factor); + # ifdef VMS + len = vms_read((char *)inbuf + inbufcount, readlen); + # else + len = read(read_cmd_fd, (char *)inbuf + inbufcount, readlen); + # endif + + if (len > 0 || got_int) + break; + /* + * If reading stdin results in an error, continue reading stderr. + * This helps when using "foo | xargs vim". + */ + if (!did_read_something && !isatty(read_cmd_fd) && read_cmd_fd == 0) + { + int m = cur_tmode; + + /* We probably set the wrong file descriptor to raw mode. Switch + * back to cooked mode, use another descriptor and set the mode to + * what it was. */ + settmode(TMODE_COOK); + #ifdef HAVE_DUP + /* Use stderr for stdin, also works for shell commands. */ + close(0); + vim_ignored = dup(2); + #else + read_cmd_fd = 2; /* read from stderr instead of stdin */ + #endif + settmode(m); + } + if (!exit_on_error) + return; + } + # endif + if (len <= 0 && !got_int) + read_error_exit(); + if (len > 0) + did_read_something = TRUE; + if (got_int) + { + /* Interrupted, pretend a CTRL-C was typed. */ + inbuf[0] = 3; + inbufcount = 1; + } + else + { + /* + * May perform conversion on the input characters. + * Include the unconverted rest of the previous call. + * If there is an incomplete char at the end it is kept for the next + * time, reading more bytes should make conversion possible. + * Don't do this in the unlikely event that the input buffer is too + * small ("rest" still contains more bytes). + */ + if (input_conv.vc_type != CONV_NONE) + { + inbufcount -= unconverted; + len = convert_input_safe(inbuf + inbufcount, + len + unconverted, INBUFLEN - inbufcount, + rest == NULL ? &rest : NULL, &restlen); + } + while (len-- > 0) + { + /* + * if a CTRL-C was typed, remove it from the buffer and set got_int + */ + if (inbuf[inbufcount] == 3 && ctrl_c_interrupts) + { + /* remove everything typed before the CTRL-C */ + mch_memmove(inbuf, inbuf + inbufcount, (size_t)(len + 1)); + inbufcount = 0; + got_int = TRUE; + } + ++inbufcount; + } + } + #endif /* UNIX or VMS*/ + } + #endif /* defined(UNIX) || defined(FEAT_GUI) || defined(VMS) */ + + /* + * Exit because of an input read error. + */ + void + read_error_exit(void) + { + if (silent_mode) /* Normal way to exit for "ex -s" */ + getout(0); + STRCPY(IObuff, _("Vim: Error reading input, exiting...\n")); + preserve_exit(); + } + + #if defined(CURSOR_SHAPE) || defined(PROTO) + /* + * May update the shape of the cursor. + */ + void + ui_cursor_shape_forced(int forced) + { + # ifdef FEAT_GUI + if (gui.in_use) + gui_update_cursor_later(); + else + # endif + term_cursor_mode(forced); + + # ifdef MCH_CURSOR_SHAPE + mch_update_cursor(); + # endif + + # ifdef FEAT_CONCEAL + conceal_check_cursor_line(); + # endif + } + + void + ui_cursor_shape(void) + { + ui_cursor_shape_forced(FALSE); + } + #endif + + /* + * Check bounds for column number + */ + int + check_col(int col) + { + if (col < 0) + return 0; + if (col >= (int)screen_Columns) + return (int)screen_Columns - 1; + return col; + } + + /* + * Check bounds for row number + */ + int + check_row(int row) + { + if (row < 0) + return 0; + if (row >= (int)screen_Rows) + return (int)screen_Rows - 1; + return row; + } + + /* + * Stuff for the X clipboard. Shared between VMS and Unix. + */ + + #if defined(FEAT_XCLIPBOARD) || defined(FEAT_GUI_X11) || defined(PROTO) + # include + # include + + /* + * Open the application context (if it hasn't been opened yet). + * Used for Motif and Athena GUI and the xterm clipboard. + */ + void + open_app_context(void) + { + if (app_context == NULL) + { + XtToolkitInitialize(); + app_context = XtCreateApplicationContext(); + } + } + + static Atom vim_atom; /* Vim's own special selection format */ + static Atom vimenc_atom; /* Vim's extended selection format */ + static Atom utf8_atom; + static Atom compound_text_atom; + static Atom text_atom; + static Atom targets_atom; + static Atom timestamp_atom; /* Used to get a timestamp */ + + void + x11_setup_atoms(Display *dpy) + { + vim_atom = XInternAtom(dpy, VIM_ATOM_NAME, False); + vimenc_atom = XInternAtom(dpy, VIMENC_ATOM_NAME,False); + utf8_atom = XInternAtom(dpy, "UTF8_STRING", False); + compound_text_atom = XInternAtom(dpy, "COMPOUND_TEXT", False); + text_atom = XInternAtom(dpy, "TEXT", False); + targets_atom = XInternAtom(dpy, "TARGETS", False); + clip_star.sel_atom = XA_PRIMARY; + clip_plus.sel_atom = XInternAtom(dpy, "CLIPBOARD", False); + timestamp_atom = XInternAtom(dpy, "TIMESTAMP", False); + } + + /* + * X Selection stuff, for cutting and pasting text to other windows. + */ + + static Boolean clip_x11_convert_selection_cb(Widget w, Atom *sel_atom, Atom *target, Atom *type, XtPointer *value, long_u *length, int *format); + static void clip_x11_lose_ownership_cb(Widget w, Atom *sel_atom); + static void clip_x11_notify_cb(Widget w, Atom *sel_atom, Atom *target); + + /* + * Property callback to get a timestamp for XtOwnSelection. + */ + static void + clip_x11_timestamp_cb( + Widget w, + XtPointer n UNUSED, + XEvent *event, + Boolean *cont UNUSED) + { + Atom actual_type; + int format; + unsigned long nitems, bytes_after; + unsigned char *prop=NULL; + XPropertyEvent *xproperty=&event->xproperty; + + /* Must be a property notify, state can't be Delete (True), has to be + * one of the supported selection types. */ + if (event->type != PropertyNotify || xproperty->state + || (xproperty->atom != clip_star.sel_atom + && xproperty->atom != clip_plus.sel_atom)) + return; + + if (XGetWindowProperty(xproperty->display, xproperty->window, + xproperty->atom, 0, 0, False, timestamp_atom, &actual_type, &format, + &nitems, &bytes_after, &prop)) + return; + + if (prop) + XFree(prop); + + /* Make sure the property type is "TIMESTAMP" and it's 32 bits. */ + if (actual_type != timestamp_atom || format != 32) + return; + + /* Get the selection, using the event timestamp. */ + if (XtOwnSelection(w, xproperty->atom, xproperty->time, + clip_x11_convert_selection_cb, clip_x11_lose_ownership_cb, + clip_x11_notify_cb) == OK) + { + /* Set the "owned" flag now, there may have been a call to + * lose_ownership_cb in between. */ + if (xproperty->atom == clip_plus.sel_atom) + clip_plus.owned = TRUE; + else + clip_star.owned = TRUE; + } + } + + void + x11_setup_selection(Widget w) + { + XtAddEventHandler(w, PropertyChangeMask, False, + /*(XtEventHandler)*/clip_x11_timestamp_cb, (XtPointer)NULL); + } + + static void + clip_x11_request_selection_cb( + Widget w UNUSED, + XtPointer success, + Atom *sel_atom, + Atom *type, + XtPointer value, + long_u *length, + int *format) + { + int motion_type = MAUTO; + long_u len; + char_u *p; + char **text_list = NULL; + VimClipboard *cbd; + char_u *tmpbuf = NULL; + + if (*sel_atom == clip_plus.sel_atom) + cbd = &clip_plus; + else + cbd = &clip_star; + + if (value == NULL || *length == 0) + { + clip_free_selection(cbd); /* nothing received, clear register */ + *(int *)success = FALSE; + return; + } + p = (char_u *)value; + len = *length; + if (*type == vim_atom) + { + motion_type = *p++; + len--; + } + + else if (*type == vimenc_atom) + { + char_u *enc; + vimconv_T conv; + int convlen; + + motion_type = *p++; + --len; + + enc = p; + p += STRLEN(p) + 1; + len -= p - enc; + + /* If the encoding of the text is different from 'encoding', attempt + * converting it. */ + conv.vc_type = CONV_NONE; + convert_setup(&conv, enc, p_enc); + if (conv.vc_type != CONV_NONE) + { + convlen = len; /* Need to use an int here. */ + tmpbuf = string_convert(&conv, p, &convlen); + len = convlen; + if (tmpbuf != NULL) + p = tmpbuf; + convert_setup(&conv, NULL, NULL); + } + } + + else if (*type == compound_text_atom + || *type == utf8_atom + || (enc_dbcs != 0 && *type == text_atom)) + { + XTextProperty text_prop; + int n_text = 0; + int status; + + text_prop.value = (unsigned char *)value; + text_prop.encoding = *type; + text_prop.format = *format; + text_prop.nitems = len; + #if defined(X_HAVE_UTF8_STRING) + if (*type == utf8_atom) + status = Xutf8TextPropertyToTextList(X_DISPLAY, &text_prop, + &text_list, &n_text); + else + #endif + status = XmbTextPropertyToTextList(X_DISPLAY, &text_prop, + &text_list, &n_text); + if (status != Success || n_text < 1) + { + *(int *)success = FALSE; + return; + } + p = (char_u *)text_list[0]; + len = STRLEN(p); + } + clip_yank_selection(motion_type, p, (long)len, cbd); + + if (text_list != NULL) + XFreeStringList(text_list); + vim_free(tmpbuf); + XtFree((char *)value); + *(int *)success = TRUE; + } + + void + clip_x11_request_selection( + Widget myShell, + Display *dpy, + VimClipboard *cbd) + { + XEvent event; + Atom type; + static int success; + int i; + time_t start_time; + int timed_out = FALSE; + + for (i = 0; i < 6; i++) + { + switch (i) + { + case 0: type = vimenc_atom; break; + case 1: type = vim_atom; break; + case 2: type = utf8_atom; break; + case 3: type = compound_text_atom; break; + case 4: type = text_atom; break; + default: type = XA_STRING; + } + if (type == utf8_atom + # if defined(X_HAVE_UTF8_STRING) + && !enc_utf8 + # endif + ) + /* Only request utf-8 when 'encoding' is utf8 and + * Xutf8TextPropertyToTextList is available. */ + continue; + success = MAYBE; + XtGetSelectionValue(myShell, cbd->sel_atom, type, + clip_x11_request_selection_cb, (XtPointer)&success, CurrentTime); + + /* Make sure the request for the selection goes out before waiting for + * a response. */ + XFlush(dpy); + + /* + * Wait for result of selection request, otherwise if we type more + * characters, then they will appear before the one that requested the + * paste! Don't worry, we will catch up with any other events later. + */ + start_time = time(NULL); + while (success == MAYBE) + { + if (XCheckTypedEvent(dpy, PropertyNotify, &event) + || XCheckTypedEvent(dpy, SelectionNotify, &event) + || XCheckTypedEvent(dpy, SelectionRequest, &event)) + { + /* This is where clip_x11_request_selection_cb() should be + * called. It may actually happen a bit later, so we loop + * until "success" changes. + * We may get a SelectionRequest here and if we don't handle + * it we hang. KDE klipper does this, for example. + * We need to handle a PropertyNotify for large selections. */ + XtDispatchEvent(&event); + continue; + } + + /* Time out after 2 to 3 seconds to avoid that we hang when the + * other process doesn't respond. Note that the SelectionNotify + * event may still come later when the selection owner comes back + * to life and the text gets inserted unexpectedly. Don't know + * why that happens or how to avoid that :-(. */ + if (time(NULL) > start_time + 2) + { + timed_out = TRUE; + break; + } + + /* Do we need this? Probably not. */ + XSync(dpy, False); + + /* Wait for 1 msec to avoid that we eat up all CPU time. */ + ui_delay(1L, TRUE); + } + + if (success == TRUE) + return; + + /* don't do a retry with another type after timing out, otherwise we + * hang for 15 seconds. */ + if (timed_out) + break; + } + + /* Final fallback position - use the X CUT_BUFFER0 store */ + yank_cut_buffer0(dpy, cbd); + } + + static Boolean + clip_x11_convert_selection_cb( + Widget w UNUSED, + Atom *sel_atom, + Atom *target, + Atom *type, + XtPointer *value, + long_u *length, + int *format) + { + static char_u *save_result = NULL; + static long_u save_length = 0; + char_u *string; + int motion_type; + VimClipboard *cbd; + int i; + + if (*sel_atom == clip_plus.sel_atom) + cbd = &clip_plus; + else + cbd = &clip_star; + + if (!cbd->owned) + return False; /* Shouldn't ever happen */ + + /* requestor wants to know what target types we support */ + if (*target == targets_atom) + { + static Atom array[7]; + + *value = (XtPointer)array; + i = 0; + array[i++] = targets_atom; + array[i++] = vimenc_atom; + array[i++] = vim_atom; + if (enc_utf8) + array[i++] = utf8_atom; + array[i++] = XA_STRING; + array[i++] = text_atom; + array[i++] = compound_text_atom; + + *type = XA_ATOM; + /* This used to be: *format = sizeof(Atom) * 8; but that caused + * crashes on 64 bit machines. (Peter Derr) */ + *format = 32; + *length = i; + return True; + } + + if ( *target != XA_STRING + && *target != vimenc_atom + && (*target != utf8_atom || !enc_utf8) + && *target != vim_atom + && *target != text_atom + && *target != compound_text_atom) + return False; + + clip_get_selection(cbd); + motion_type = clip_convert_selection(&string, length, cbd); + if (motion_type < 0) + return False; + + /* For our own format, the first byte contains the motion type */ + if (*target == vim_atom) + (*length)++; + + /* Our own format with encoding: motion 'encoding' NUL text */ + if (*target == vimenc_atom) + *length += STRLEN(p_enc) + 2; + + if (save_length < *length || save_length / 2 >= *length) + *value = XtRealloc((char *)save_result, (Cardinal)*length + 1); + else + *value = save_result; + if (*value == NULL) + { + vim_free(string); + return False; + } + save_result = (char_u *)*value; + save_length = *length; + + if (*target == XA_STRING || (*target == utf8_atom && enc_utf8)) + { + mch_memmove(save_result, string, (size_t)(*length)); + *type = *target; + } + else if (*target == compound_text_atom || *target == text_atom) + { + XTextProperty text_prop; + char *string_nt = (char *)save_result; + int conv_result; + + /* create NUL terminated string which XmbTextListToTextProperty wants */ + mch_memmove(string_nt, string, (size_t)*length); + string_nt[*length] = NUL; + conv_result = XmbTextListToTextProperty(X_DISPLAY, (char **)&string_nt, + 1, XCompoundTextStyle, &text_prop); + if (conv_result != Success) + { + vim_free(string); + return False; + } + *value = (XtPointer)(text_prop.value); /* from plain text */ + *length = text_prop.nitems; + *type = compound_text_atom; + XtFree((char *)save_result); + save_result = (char_u *)*value; + save_length = *length; + } + else if (*target == vimenc_atom) + { + int l = STRLEN(p_enc); + + save_result[0] = motion_type; + STRCPY(save_result + 1, p_enc); + mch_memmove(save_result + l + 2, string, (size_t)(*length - l - 2)); + *type = vimenc_atom; + } + else + { + save_result[0] = motion_type; + mch_memmove(save_result + 1, string, (size_t)(*length - 1)); + *type = vim_atom; + } + *format = 8; /* 8 bits per char */ + vim_free(string); + return True; + } + + static void + clip_x11_lose_ownership_cb(Widget w UNUSED, Atom *sel_atom) + { + if (*sel_atom == clip_plus.sel_atom) + clip_lose_selection(&clip_plus); + else + clip_lose_selection(&clip_star); + } + + void + clip_x11_lose_selection(Widget myShell, VimClipboard *cbd) + { + XtDisownSelection(myShell, cbd->sel_atom, + XtLastTimestampProcessed(XtDisplay(myShell))); + } + + static void + clip_x11_notify_cb(Widget w UNUSED, Atom *sel_atom UNUSED, Atom *target UNUSED) + { + /* To prevent automatically freeing the selection value. */ + } + + int + clip_x11_own_selection(Widget myShell, VimClipboard *cbd) + { + /* When using the GUI we have proper timestamps, use the one of the last + * event. When in the console we don't get events (the terminal gets + * them), Get the time by a zero-length append, clip_x11_timestamp_cb will + * be called with the current timestamp. */ + #ifdef FEAT_GUI + if (gui.in_use) + { + if (XtOwnSelection(myShell, cbd->sel_atom, + XtLastTimestampProcessed(XtDisplay(myShell)), + clip_x11_convert_selection_cb, clip_x11_lose_ownership_cb, + clip_x11_notify_cb) == False) + return FAIL; + } + else + #endif + { + if (!XChangeProperty(XtDisplay(myShell), XtWindow(myShell), + cbd->sel_atom, timestamp_atom, 32, PropModeAppend, NULL, 0)) + return FAIL; + } + /* Flush is required in a terminal as nothing else is doing it. */ + XFlush(XtDisplay(myShell)); + return OK; + } + + /* + * Send the current selection to the clipboard. Do nothing for X because we + * will fill in the selection only when requested by another app. + */ + void + clip_x11_set_selection(VimClipboard *cbd UNUSED) + { + } + + #if (defined(FEAT_X11) && defined(FEAT_XCLIPBOARD) && defined(USE_SYSTEM)) \ + || defined(PROTO) + int + clip_x11_owner_exists(VimClipboard *cbd) + { + return XGetSelectionOwner(X_DISPLAY, cbd->sel_atom) != None; + } + #endif + #endif + + #if defined(FEAT_XCLIPBOARD) || defined(FEAT_GUI_X11) \ + || defined(FEAT_GUI_GTK) || defined(PROTO) + /* + * Get the contents of the X CUT_BUFFER0 and put it in "cbd". + */ + void + yank_cut_buffer0(Display *dpy, VimClipboard *cbd) + { + int nbytes = 0; + char_u *buffer = (char_u *)XFetchBuffer(dpy, &nbytes, 0); + + if (nbytes > 0) + { + int done = FALSE; + + /* CUT_BUFFER0 is supposed to be always latin1. Convert to 'enc' when + * using a multi-byte encoding. Conversion between two 8-bit + * character sets usually fails and the text might actually be in + * 'enc' anyway. */ + if (has_mbyte) + { + char_u *conv_buf; + vimconv_T vc; + + vc.vc_type = CONV_NONE; + if (convert_setup(&vc, (char_u *)"latin1", p_enc) == OK) + { + conv_buf = string_convert(&vc, buffer, &nbytes); + if (conv_buf != NULL) + { + clip_yank_selection(MCHAR, conv_buf, (long)nbytes, cbd); + vim_free(conv_buf); + done = TRUE; + } + convert_setup(&vc, NULL, NULL); + } + } + if (!done) /* use the text without conversion */ + clip_yank_selection(MCHAR, buffer, (long)nbytes, cbd); + XFree((void *)buffer); + if (p_verbose > 0) + { + verbose_enter(); + verb_msg(_("Used CUT_BUFFER0 instead of empty selection")); + verbose_leave(); + } + } + } + #endif + + #if defined(FEAT_MOUSE) || defined(PROTO) + + /* + * Move the cursor to the specified row and column on the screen. + * Change current window if necessary. Returns an integer with the + * CURSOR_MOVED bit set if the cursor has moved or unset otherwise. + * + * The MOUSE_FOLD_CLOSE bit is set when clicked on the '-' in a fold column. + * The MOUSE_FOLD_OPEN bit is set when clicked on the '+' in a fold column. + * + * If flags has MOUSE_FOCUS, then the current window will not be changed, and + * if the mouse is outside the window then the text will scroll, or if the + * mouse was previously on a status line, then the status line may be dragged. + * + * If flags has MOUSE_MAY_VIS, then VIsual mode will be started before the + * cursor is moved unless the cursor was on a status line. + * This function returns one of IN_UNKNOWN, IN_BUFFER, IN_STATUS_LINE or + * IN_SEP_LINE depending on where the cursor was clicked. + * + * If flags has MOUSE_MAY_STOP_VIS, then Visual mode will be stopped, unless + * the mouse is on the status line of the same window. + * + * If flags has MOUSE_DID_MOVE, nothing is done if the mouse didn't move since + * the last call. + * + * If flags has MOUSE_SETPOS, nothing is done, only the current position is + * remembered. + */ + int + jump_to_mouse( + int flags, + int *inclusive, /* used for inclusive operator, can be NULL */ + int which_button) /* MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE */ + { + static int on_status_line = 0; /* #lines below bottom of window */ + static int on_sep_line = 0; /* on separator right of window */ + #ifdef FEAT_MENU + static int in_winbar = FALSE; + #endif + static int prev_row = -1; + static int prev_col = -1; + static win_T *dragwin = NULL; /* window being dragged */ + static int did_drag = FALSE; /* drag was noticed */ + + win_T *wp, *old_curwin; + pos_T old_cursor; + int count; + int first; + int row = mouse_row; + int col = mouse_col; + #ifdef FEAT_FOLDING + int mouse_char; + #endif + + mouse_past_bottom = FALSE; + mouse_past_eol = FALSE; + + if (flags & MOUSE_RELEASED) + { + /* On button release we may change window focus if positioned on a + * status line and no dragging happened. */ + if (dragwin != NULL && !did_drag) + flags &= ~(MOUSE_FOCUS | MOUSE_DID_MOVE); + dragwin = NULL; + did_drag = FALSE; + } + + if ((flags & MOUSE_DID_MOVE) + && prev_row == mouse_row + && prev_col == mouse_col) + { + retnomove: + /* before moving the cursor for a left click which is NOT in a status + * line, stop Visual mode */ + if (on_status_line) + return IN_STATUS_LINE; + if (on_sep_line) + return IN_SEP_LINE; + #ifdef FEAT_MENU + if (in_winbar) + { + /* A quick second click may arrive as a double-click, but we use it + * as a second click in the WinBar. */ + if ((mod_mask & MOD_MASK_MULTI_CLICK) && !(flags & MOUSE_RELEASED)) + { + wp = mouse_find_win(&row, &col); + if (wp == NULL) + return IN_UNKNOWN; + winbar_click(wp, col); + } + return IN_OTHER_WIN | MOUSE_WINBAR; + } + #endif + if (flags & MOUSE_MAY_STOP_VIS) + { + end_visual_mode(); + redraw_curbuf_later(INVERTED); /* delete the inversion */ + } + #if defined(FEAT_CMDWIN) && defined(FEAT_CLIPBOARD) + /* Continue a modeless selection in another window. */ + if (cmdwin_type != 0 && row < curwin->w_winrow) + return IN_OTHER_WIN; + #endif + return IN_BUFFER; + } + + prev_row = mouse_row; + prev_col = mouse_col; + + if (flags & MOUSE_SETPOS) + goto retnomove; /* ugly goto... */ + + #ifdef FEAT_FOLDING + /* Remember the character under the mouse, it might be a '-' or '+' in the + * fold column. */ + if (row >= 0 && row < Rows && col >= 0 && col <= Columns + && ScreenLines != NULL) + mouse_char = ScreenLines[LineOffset[row] + col]; + else + mouse_char = ' '; + #endif + + old_curwin = curwin; + old_cursor = curwin->w_cursor; + + if (!(flags & MOUSE_FOCUS)) + { + if (row < 0 || col < 0) /* check if it makes sense */ + return IN_UNKNOWN; + + /* find the window where the row is in */ + wp = mouse_find_win(&row, &col); + if (wp == NULL) + return IN_UNKNOWN; + dragwin = NULL; + + #ifdef FEAT_MENU + if (row == -1) + { + /* A click in the window toolbar does not enter another window or + * change Visual highlighting. */ + winbar_click(wp, col); + in_winbar = TRUE; + return IN_OTHER_WIN | MOUSE_WINBAR; + } + in_winbar = FALSE; + #endif + + /* + * winpos and height may change in win_enter()! + */ + if (row >= wp->w_height) /* In (or below) status line */ + { + on_status_line = row - wp->w_height + 1; + dragwin = wp; + } + else + on_status_line = 0; + if (col >= wp->w_width) /* In separator line */ + { + on_sep_line = col - wp->w_width + 1; + dragwin = wp; + } + else + on_sep_line = 0; + + /* The rightmost character of the status line might be a vertical + * separator character if there is no connecting window to the right. */ + if (on_status_line && on_sep_line) + { + if (stl_connected(wp)) + on_sep_line = 0; + else + on_status_line = 0; + } + + /* Before jumping to another buffer, or moving the cursor for a left + * click, stop Visual mode. */ + if (VIsual_active + && (wp->w_buffer != curwin->w_buffer + || (!on_status_line && !on_sep_line + #ifdef FEAT_FOLDING + && ( + # ifdef FEAT_RIGHTLEFT + wp->w_p_rl ? col < wp->w_width - wp->w_p_fdc : + # endif + col >= wp->w_p_fdc + # ifdef FEAT_CMDWIN + + (cmdwin_type == 0 && wp == curwin ? 0 : 1) + # endif + ) + #endif + && (flags & MOUSE_MAY_STOP_VIS)))) + { + end_visual_mode(); + redraw_curbuf_later(INVERTED); /* delete the inversion */ + } + #ifdef FEAT_CMDWIN + if (cmdwin_type != 0 && wp != curwin) + { + /* A click outside the command-line window: Use modeless + * selection if possible. Allow dragging the status lines. */ + on_sep_line = 0; + # ifdef FEAT_CLIPBOARD + if (on_status_line) + return IN_STATUS_LINE; + return IN_OTHER_WIN; + # else + row = 0; + col += wp->w_wincol; + wp = curwin; + # endif + } + #endif + /* Only change window focus when not clicking on or dragging the + * status line. Do change focus when releasing the mouse button + * (MOUSE_FOCUS was set above if we dragged first). */ + if (dragwin == NULL || (flags & MOUSE_RELEASED)) + win_enter(wp, TRUE); /* can make wp invalid! */ + + if (curwin != old_curwin) + { + #ifdef CHECK_DOUBLE_CLICK + /* set topline, to be able to check for double click ourselves */ + set_mouse_topline(curwin); + #endif + #ifdef FEAT_TERMINAL + /* when entering a terminal window may change state */ + term_win_entered(); + #endif + } + if (on_status_line) /* In (or below) status line */ + { + /* Don't use start_arrow() if we're in the same window */ + if (curwin == old_curwin) + return IN_STATUS_LINE; + else + return IN_STATUS_LINE | CURSOR_MOVED; + } + if (on_sep_line) /* In (or below) status line */ + { + /* Don't use start_arrow() if we're in the same window */ + if (curwin == old_curwin) + return IN_SEP_LINE; + else + return IN_SEP_LINE | CURSOR_MOVED; + } + + curwin->w_cursor.lnum = curwin->w_topline; + #ifdef FEAT_GUI + /* remember topline, needed for double click */ + gui_prev_topline = curwin->w_topline; + # ifdef FEAT_DIFF + gui_prev_topfill = curwin->w_topfill; + # endif + #endif + } + else if (on_status_line && which_button == MOUSE_LEFT) + { + if (dragwin != NULL) + { + /* Drag the status line */ + count = row - dragwin->w_winrow - dragwin->w_height + 1 + - on_status_line; + win_drag_status_line(dragwin, count); + did_drag |= count; + } + return IN_STATUS_LINE; /* Cursor didn't move */ + } + else if (on_sep_line && which_button == MOUSE_LEFT) + { + if (dragwin != NULL) + { + /* Drag the separator column */ + count = col - dragwin->w_wincol - dragwin->w_width + 1 + - on_sep_line; + win_drag_vsep_line(dragwin, count); + did_drag |= count; + } + return IN_SEP_LINE; /* Cursor didn't move */ + } + #ifdef FEAT_MENU + else if (in_winbar) + { + /* After a click on the window toolbar don't start Visual mode. */ + return IN_OTHER_WIN | MOUSE_WINBAR; + } + #endif + else /* keep_window_focus must be TRUE */ + { + /* before moving the cursor for a left click, stop Visual mode */ + if (flags & MOUSE_MAY_STOP_VIS) + { + end_visual_mode(); + redraw_curbuf_later(INVERTED); /* delete the inversion */ + } + + #if defined(FEAT_CMDWIN) && defined(FEAT_CLIPBOARD) + /* Continue a modeless selection in another window. */ + if (cmdwin_type != 0 && row < curwin->w_winrow) + return IN_OTHER_WIN; + #endif + + row -= W_WINROW(curwin); + col -= curwin->w_wincol; + + /* + * When clicking beyond the end of the window, scroll the screen. + * Scroll by however many rows outside the window we are. + */ + if (row < 0) + { + count = 0; + for (first = TRUE; curwin->w_topline > 1; ) + { + #ifdef FEAT_DIFF + if (curwin->w_topfill < diff_check(curwin, curwin->w_topline)) + ++count; + else + #endif + count += plines(curwin->w_topline - 1); + if (!first && count > -row) + break; + first = FALSE; + #ifdef FEAT_FOLDING + (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); + #endif + #ifdef FEAT_DIFF + if (curwin->w_topfill < diff_check(curwin, curwin->w_topline)) + ++curwin->w_topfill; + else + #endif + { + --curwin->w_topline; + #ifdef FEAT_DIFF + curwin->w_topfill = 0; + #endif + } + } + #ifdef FEAT_DIFF + check_topfill(curwin, FALSE); + #endif + curwin->w_valid &= + ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); + redraw_later(VALID); + row = 0; + } + else if (row >= curwin->w_height) + { + count = 0; + for (first = TRUE; curwin->w_topline < curbuf->b_ml.ml_line_count; ) + { + #ifdef FEAT_DIFF + if (curwin->w_topfill > 0) + ++count; + else + #endif + count += plines(curwin->w_topline); + if (!first && count > row - curwin->w_height + 1) + break; + first = FALSE; + #ifdef FEAT_FOLDING + if (hasFolding(curwin->w_topline, NULL, &curwin->w_topline) + && curwin->w_topline == curbuf->b_ml.ml_line_count) + break; + #endif + #ifdef FEAT_DIFF + if (curwin->w_topfill > 0) + --curwin->w_topfill; + else + #endif + { + ++curwin->w_topline; + #ifdef FEAT_DIFF + curwin->w_topfill = + diff_check_fill(curwin, curwin->w_topline); + #endif + } + } + #ifdef FEAT_DIFF + check_topfill(curwin, FALSE); + #endif + redraw_later(VALID); + curwin->w_valid &= + ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); + row = curwin->w_height - 1; + } + else if (row == 0) + { + /* When dragging the mouse, while the text has been scrolled up as + * far as it goes, moving the mouse in the top line should scroll + * the text down (done later when recomputing w_topline). */ + if (mouse_dragging > 0 + && curwin->w_cursor.lnum + == curwin->w_buffer->b_ml.ml_line_count + && curwin->w_cursor.lnum == curwin->w_topline) + curwin->w_valid &= ~(VALID_TOPLINE); + } + } + + #ifdef FEAT_FOLDING + /* Check for position outside of the fold column. */ + if ( + # ifdef FEAT_RIGHTLEFT + curwin->w_p_rl ? col < curwin->w_width - curwin->w_p_fdc : + # endif + col >= curwin->w_p_fdc + # ifdef FEAT_CMDWIN + + (cmdwin_type == 0 ? 0 : 1) + # endif + ) + mouse_char = ' '; + #endif + + /* compute the position in the buffer line from the posn on the screen */ + if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) + mouse_past_bottom = TRUE; + + /* Start Visual mode before coladvance(), for when 'sel' != "old" */ + if ((flags & MOUSE_MAY_VIS) && !VIsual_active) + { + check_visual_highlight(); + VIsual = old_cursor; + VIsual_active = TRUE; + VIsual_reselect = TRUE; + /* if 'selectmode' contains "mouse", start Select mode */ + may_start_select('o'); + setmouse(); + if (p_smd && msg_silent == 0) + redraw_cmdline = TRUE; /* show visual mode later */ + } + + curwin->w_curswant = col; + curwin->w_set_curswant = FALSE; /* May still have been TRUE */ + if (coladvance(col) == FAIL) /* Mouse click beyond end of line */ + { + if (inclusive != NULL) + *inclusive = TRUE; + mouse_past_eol = TRUE; + } + else if (inclusive != NULL) + *inclusive = FALSE; + + count = IN_BUFFER; + if (curwin != old_curwin || curwin->w_cursor.lnum != old_cursor.lnum + || curwin->w_cursor.col != old_cursor.col) + count |= CURSOR_MOVED; /* Cursor has moved */ + +-#ifdef FEAT_FOLDING ++# ifdef FEAT_FOLDING + if (mouse_char == '+') + count |= MOUSE_FOLD_OPEN; + else if (mouse_char != ' ') + count |= MOUSE_FOLD_CLOSE; +-#endif ++# endif + + return count; + } ++#endif ++ ++// Functions also used for popup windows. ++#if defined(FEAT_MOUSE) || defined(FEAT_TEXT_PROP) || defined(PROTO) + + /* + * Compute the position in the buffer line from the posn on the screen in + * window "win". + * Returns TRUE if the position is below the last line. + */ + int + mouse_comp_pos( + win_T *win, + int *rowp, + int *colp, + linenr_T *lnump) + { + int col = *colp; + int row = *rowp; + linenr_T lnum; + int retval = FALSE; + int off; + int count; + + #ifdef FEAT_RIGHTLEFT + if (win->w_p_rl) + col = win->w_width - 1 - col; + #endif + + lnum = win->w_topline; + + while (row > 0) + { + #ifdef FEAT_DIFF + /* Don't include filler lines in "count" */ + if (win->w_p_diff + # ifdef FEAT_FOLDING + && !hasFoldingWin(win, lnum, NULL, NULL, TRUE, NULL) + # endif + ) + { + if (lnum == win->w_topline) + row -= win->w_topfill; + else + row -= diff_check_fill(win, lnum); + count = plines_win_nofill(win, lnum, TRUE); + } + else + #endif + count = plines_win(win, lnum, TRUE); + if (count > row) + break; /* Position is in this buffer line. */ + #ifdef FEAT_FOLDING + (void)hasFoldingWin(win, lnum, NULL, &lnum, TRUE, NULL); + #endif + if (lnum == win->w_buffer->b_ml.ml_line_count) + { + retval = TRUE; + break; /* past end of file */ + } + row -= count; + ++lnum; + } + + if (!retval) + { + /* Compute the column without wrapping. */ + off = win_col_off(win) - win_col_off2(win); + if (col < off) + col = off; + col += row * (win->w_width - off); + /* add skip column (for long wrapping line) */ + col += win->w_skipcol; + } + + if (!win->w_p_wrap) + col += win->w_leftcol; + + /* skip line number and fold column in front of the line */ + col -= win_col_off(win); + if (col < 0) + { + #ifdef FEAT_NETBEANS_INTG + netbeans_gutter_click(lnum); + #endif + col = 0; + } + + *colp = col; + *rowp = row; + *lnump = lnum; + return retval; + } + + /* + * Find the window at screen position "*rowp" and "*colp". The positions are + * updated to become relative to the top-left of the window. + * Returns NULL when something is wrong. + */ + win_T * +-mouse_find_win(int *rowp, int *colp UNUSED) ++mouse_find_win(int *rowp, int *colp) + { + frame_T *fp; + win_T *wp; + + fp = topframe; + *rowp -= firstwin->w_winrow; + for (;;) + { + if (fp->fr_layout == FR_LEAF) + break; + if (fp->fr_layout == FR_ROW) + { + for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next) + { + if (*colp < fp->fr_width) + break; + *colp -= fp->fr_width; + } + } + else /* fr_layout == FR_COL */ + { + for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next) + { + if (*rowp < fp->fr_height) + break; + *rowp -= fp->fr_height; + } + } + } + /* When using a timer that closes a window the window might not actually + * exist. */ + FOR_ALL_WINDOWS(wp) + if (wp == fp->fr_win) + { + #ifdef FEAT_MENU + *rowp -= wp->w_winbar_height; + #endif + return wp; + } + return NULL; + } + + #if defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_MAC) \ + || defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MSWIN) \ + || defined(FEAT_GUI_PHOTON) || defined(FEAT_TERM_POPUP_MENU) \ + || defined(PROTO) + /* + * Translate window coordinates to buffer position without any side effects + */ + int + get_fpos_of_mouse(pos_T *mpos) + { + win_T *wp; + int row = mouse_row; + int col = mouse_col; + + if (row < 0 || col < 0) /* check if it makes sense */ + return IN_UNKNOWN; + + /* find the window where the row is in */ + wp = mouse_find_win(&row, &col); + if (wp == NULL) + return IN_UNKNOWN; + /* + * winpos and height may change in win_enter()! + */ + if (row >= wp->w_height) /* In (or below) status line */ + return IN_STATUS_LINE; + if (col >= wp->w_width) /* In vertical separator line */ + return IN_SEP_LINE; + + if (wp != curwin) + return IN_UNKNOWN; + + /* compute the position in the buffer line from the posn on the screen */ + if (mouse_comp_pos(curwin, &row, &col, &mpos->lnum)) + return IN_STATUS_LINE; /* past bottom */ + + mpos->col = vcol2col(wp, mpos->lnum, col); + + if (mpos->col > 0) + --mpos->col; + mpos->coladd = 0; + return IN_BUFFER; + } + #endif + + #if defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_MAC) \ + || defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MSWIN) \ + || defined(FEAT_GUI_PHOTON) || defined(FEAT_BEVAL) \ + || defined(FEAT_TERM_POPUP_MENU) || defined(PROTO) + /* + * Convert a virtual (screen) column to a character column. + * The first column is one. + */ + int + vcol2col(win_T *wp, linenr_T lnum, int vcol) + { + /* try to advance to the specified column */ + int count = 0; + char_u *ptr; + char_u *line; + + line = ptr = ml_get_buf(wp->w_buffer, lnum, FALSE); + while (count < vcol && *ptr != NUL) + { + count += win_lbr_chartabsize(wp, line, ptr, count, NULL); + MB_PTR_ADV(ptr); + } + return (int)(ptr - line); + } + #endif + + #endif /* FEAT_MOUSE */ + + #if defined(FEAT_GUI) || defined(MSWIN) || defined(PROTO) + /* + * Called when focus changed. Used for the GUI or for systems where this can + * be done in the console (Win32). + */ + void + ui_focus_change( + int in_focus) /* TRUE if focus gained. */ + { + static time_t last_time = (time_t)0; + int need_redraw = FALSE; + + /* When activated: Check if any file was modified outside of Vim. + * Only do this when not done within the last two seconds (could get + * several events in a row). */ + if (in_focus && last_time + 2 < time(NULL)) + { + need_redraw = check_timestamps( + # ifdef FEAT_GUI + gui.in_use + # else + FALSE + # endif + ); + last_time = time(NULL); + } + + /* + * Fire the focus gained/lost autocommand. + */ + need_redraw |= apply_autocmds(in_focus ? EVENT_FOCUSGAINED + : EVENT_FOCUSLOST, NULL, NULL, FALSE, curbuf); + + if (need_redraw) + { + /* Something was executed, make sure the cursor is put back where it + * belongs. */ + need_wait_return = FALSE; + + if (State & CMDLINE) + redrawcmdline(); + else if (State == HITRETURN || State == SETWSIZE || State == ASKMORE + || State == EXTERNCMD || State == CONFIRM || exmode_active) + repeat_message(); + else if ((State & NORMAL) || (State & INSERT)) + { + if (must_redraw != 0) + update_screen(0); + setcursor(); + } + cursor_on(); /* redrawing may have switched it off */ + out_flush_cursor(FALSE, TRUE); + # ifdef FEAT_GUI + if (gui.in_use) + gui_update_scrollbars(FALSE); + # endif + } + #ifdef FEAT_TITLE + /* File may have been changed from 'readonly' to 'noreadonly' */ + if (need_maketitle) + maketitle(); + #endif + } + #endif + + #if defined(HAVE_INPUT_METHOD) || defined(PROTO) + /* + * Save current Input Method status to specified place. + */ + void + im_save_status(long *psave) + { + /* Don't save when 'imdisable' is set or "xic" is NULL, IM is always + * disabled then (but might start later). + * Also don't save when inside a mapping, vgetc_im_active has not been set + * then. + * And don't save when the keys were stuffed (e.g., for a "." command). + * And don't save when the GUI is running but our window doesn't have + * input focus (e.g., when a find dialog is open). */ + if (!p_imdisable && KeyTyped && !KeyStuffed + # ifdef FEAT_XIM + && xic != NULL + # endif + # ifdef FEAT_GUI + && (!gui.in_use || gui.in_focus) + # endif + ) + { + /* Do save when IM is on, or IM is off and saved status is on. */ + if (vgetc_im_active) + *psave = B_IMODE_IM; + else if (*psave == B_IMODE_IM) + *psave = B_IMODE_NONE; + } + } + #endif diff --git a/src/test/resources/unparser/diff/error05.txt b/src/test/resources/unparser/diff/error05.txt new file mode 100644 index 00000000..52851808 --- /dev/null +++ b/src/test/resources/unparser/diff/error05.txt @@ -0,0 +1,7569 @@ +textDiff: + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + /* + * os_win32.c + * + * Used for both the console version and the Win32 GUI. A lot of code is for + * the console version only, so there is a lot of "#ifndef FEAT_GUI_MSWIN". + * + * Win32 (Windows NT and Windows 95) system-dependent routines. + * Portions lifted from the Win32 SDK samples, the MSDOS-dependent code, + * NetHack 3.1.3, GNU Emacs 19.30, and Vile 5.5. + * + * George V. Reilly wrote most of this. + * Roger Knobbe did the initial port of Vim 3.0. + */ + + #include "vim.h" + + #ifdef FEAT_MZSCHEME + # include "if_mzsch.h" + #endif + + #include + #include + #include + + /* cproto fails on missing include files */ + #ifndef PROTO + # include + #endif + + #undef chdir + #ifdef __GNUC__ + # ifndef __MINGW32__ + # include + # endif + #else + # include + #endif + + #ifndef PROTO + # if defined(FEAT_TITLE) && !defined(FEAT_GUI_MSWIN) + # include + # endif + #endif + + #ifdef FEAT_JOB_CHANNEL + # include + #endif + + #ifdef __MINGW32__ + # ifndef FROM_LEFT_1ST_BUTTON_PRESSED + # define FROM_LEFT_1ST_BUTTON_PRESSED 0x0001 + # endif + # ifndef RIGHTMOST_BUTTON_PRESSED + # define RIGHTMOST_BUTTON_PRESSED 0x0002 + # endif + # ifndef FROM_LEFT_2ND_BUTTON_PRESSED + # define FROM_LEFT_2ND_BUTTON_PRESSED 0x0004 + # endif + # ifndef FROM_LEFT_3RD_BUTTON_PRESSED + # define FROM_LEFT_3RD_BUTTON_PRESSED 0x0008 + # endif + # ifndef FROM_LEFT_4TH_BUTTON_PRESSED + # define FROM_LEFT_4TH_BUTTON_PRESSED 0x0010 + # endif + + /* + * EventFlags + */ + # ifndef MOUSE_MOVED + # define MOUSE_MOVED 0x0001 + # endif + # ifndef DOUBLE_CLICK + # define DOUBLE_CLICK 0x0002 + # endif + #endif + + /* Record all output and all keyboard & mouse input */ + /* #define MCH_WRITE_DUMP */ + + #ifdef MCH_WRITE_DUMP + FILE* fdDump = NULL; + #endif + + /* + * When generating prototypes for Win32 on Unix, these lines make the syntax + * errors disappear. They do not need to be correct. + */ + #ifdef PROTO + #define WINAPI + typedef char * LPCSTR; + typedef char * LPWSTR; + typedef int ACCESS_MASK; + typedef int BOOL; + typedef int COLORREF; + typedef int CONSOLE_CURSOR_INFO; + typedef int COORD; + typedef int DWORD; + typedef int HANDLE; + typedef int LPHANDLE; + typedef int HDC; + typedef int HFONT; + typedef int HICON; + typedef int HINSTANCE; + typedef int HWND; + typedef int INPUT_RECORD; + typedef int INT; + typedef int KEY_EVENT_RECORD; + typedef int LOGFONT; + typedef int LPBOOL; + typedef int LPCTSTR; + typedef int LPDWORD; + typedef int LPSTR; + typedef int LPTSTR; + typedef int LPVOID; + typedef int MOUSE_EVENT_RECORD; + typedef int PACL; + typedef int PDWORD; + typedef int PHANDLE; + typedef int PRINTDLG; + typedef int PSECURITY_DESCRIPTOR; + typedef int PSID; + typedef int SECURITY_INFORMATION; + typedef int SHORT; + typedef int SMALL_RECT; + typedef int TEXTMETRIC; + typedef int TOKEN_INFORMATION_CLASS; + typedef int TRUSTEE; + typedef int WORD; + typedef int WCHAR; + typedef void VOID; + typedef int BY_HANDLE_FILE_INFORMATION; + typedef int SE_OBJECT_TYPE; + typedef int PSNSECINFO; + typedef int PSNSECINFOW; + typedef int STARTUPINFO; + typedef int PROCESS_INFORMATION; + typedef int LPSECURITY_ATTRIBUTES; + # define __stdcall /* empty */ + #endif + + #if defined(__BORLANDC__) + /* Strangely Borland uses a non-standard name. */ + # define wcsicmp(a, b) wcscmpi((a), (b)) + #endif + +-#ifndef FEAT_GUI_MSWIN ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + /* Win32 Console handles for input and output */ + static HANDLE g_hConIn = INVALID_HANDLE_VALUE; + static HANDLE g_hConOut = INVALID_HANDLE_VALUE; + + /* Win32 Screen buffer,coordinate,console I/O information */ + static SMALL_RECT g_srScrollRegion; + static COORD g_coord; /* 0-based, but external coords are 1-based */ + + /* The attribute of the screen when the editor was started */ + static WORD g_attrDefault = 7; /* lightgray text on black background */ + static WORD g_attrCurrent; + + static int g_fCBrkPressed = FALSE; /* set by ctrl-break interrupt */ + static int g_fCtrlCPressed = FALSE; /* set when ctrl-C or ctrl-break detected */ + static int g_fForceExit = FALSE; /* set when forcefully exiting */ + + static void scroll(unsigned cLines); + static void set_scroll_region(unsigned left, unsigned top, + unsigned right, unsigned bottom); + static void set_scroll_region_tb(unsigned top, unsigned bottom); + static void set_scroll_region_lr(unsigned left, unsigned right); + static void insert_lines(unsigned cLines); + static void delete_lines(unsigned cLines); + static void gotoxy(unsigned x, unsigned y); + static void standout(void); + static int s_cursor_visible = TRUE; + static int did_create_conin = FALSE; +-#else ++#endif ++#ifdef FEAT_GUI_MSWIN + static int s_dont_use_vimrun = TRUE; + static int need_vimrun_warning = FALSE; + static char *vimrun_path = "vimrun "; + #endif + + static int win32_getattrs(char_u *name); + static int win32_setattrs(char_u *name, int attrs); + static int win32_set_archive(char_u *name); + + static int conpty_working = 0; + static int conpty_stable = 0; + static void vtp_flag_init(); + +-#ifndef FEAT_GUI_MSWIN ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + static int vtp_working = 0; + static void vtp_init(); + static void vtp_exit(); + static int vtp_printf(char *format, ...); + static void vtp_sgr_bulk(int arg); + static void vtp_sgr_bulks(int argc, int *argv); + + static guicolor_T save_console_bg_rgb; + static guicolor_T save_console_fg_rgb; + + static int g_color_index_bg = 0; + static int g_color_index_fg = 7; + + # ifdef FEAT_TERMGUICOLORS + static int default_console_color_bg = 0x000000; // black + static int default_console_color_fg = 0xc0c0c0; // white + # endif + + # ifdef FEAT_TERMGUICOLORS + # define USE_VTP (vtp_working && is_term_win32() && (p_tgc || (!p_tgc && t_colors >= 256))) + # else + # define USE_VTP 0 + # endif + + static void set_console_color_rgb(void); + static void reset_console_color_rgb(void); + #endif + + /* This flag is newly created from Windows 10 */ + #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING + # define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 + #endif + +-#ifndef FEAT_GUI_MSWIN ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + static int suppress_winsize = 1; /* don't fiddle with console */ + #endif + + static char_u *exe_path = NULL; + + static BOOL win8_or_later = FALSE; + +-#ifndef FEAT_GUI_MSWIN ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + /* Dynamic loading for portability */ + typedef struct _DYN_CONSOLE_SCREEN_BUFFER_INFOEX + { + ULONG cbSize; + COORD dwSize; + COORD dwCursorPosition; + WORD wAttributes; + SMALL_RECT srWindow; + COORD dwMaximumWindowSize; + WORD wPopupAttributes; + BOOL bFullscreenSupported; + COLORREF ColorTable[16]; + } DYN_CONSOLE_SCREEN_BUFFER_INFOEX, *PDYN_CONSOLE_SCREEN_BUFFER_INFOEX; + typedef BOOL (WINAPI *PfnGetConsoleScreenBufferInfoEx)(HANDLE, PDYN_CONSOLE_SCREEN_BUFFER_INFOEX); + static PfnGetConsoleScreenBufferInfoEx pGetConsoleScreenBufferInfoEx; + typedef BOOL (WINAPI *PfnSetConsoleScreenBufferInfoEx)(HANDLE, PDYN_CONSOLE_SCREEN_BUFFER_INFOEX); + static PfnSetConsoleScreenBufferInfoEx pSetConsoleScreenBufferInfoEx; + static BOOL has_csbiex = FALSE; + #endif + + /* + * Get version number including build number + */ + typedef BOOL (WINAPI *PfnRtlGetVersion)(LPOSVERSIONINFOW); + # define MAKE_VER(major, minor, build) \ + (((major) << 24) | ((minor) << 16) | (build)) + + static DWORD + get_build_number(void) + { + OSVERSIONINFOW osver = {sizeof(OSVERSIONINFOW)}; + HMODULE hNtdll; + PfnRtlGetVersion pRtlGetVersion; + DWORD ver = MAKE_VER(0, 0, 0); + + hNtdll = GetModuleHandle("ntdll.dll"); + if (hNtdll != NULL) + { + pRtlGetVersion = + (PfnRtlGetVersion)GetProcAddress(hNtdll, "RtlGetVersion"); + pRtlGetVersion(&osver); + ver = MAKE_VER(min(osver.dwMajorVersion, 255), + min(osver.dwMinorVersion, 255), + min(osver.dwBuildNumber, 32767)); + } + return ver; + } + +-#ifndef FEAT_GUI_MSWIN ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + /* + * Version of ReadConsoleInput() that works with IME. + * Works around problems on Windows 8. + */ + static BOOL + read_console_input( + HANDLE hInput, + INPUT_RECORD *lpBuffer, + DWORD nLength, + LPDWORD lpEvents) + { + enum + { + IRSIZE = 10 + }; + static INPUT_RECORD s_irCache[IRSIZE]; + static DWORD s_dwIndex = 0; + static DWORD s_dwMax = 0; + DWORD dwEvents; + int head; + int tail; + int i; + + if (nLength == -2) + return (s_dwMax > 0) ? TRUE : FALSE; + + if (!win8_or_later) + { + if (nLength == -1) + return PeekConsoleInputW(hInput, lpBuffer, 1, lpEvents); + return ReadConsoleInputW(hInput, lpBuffer, 1, &dwEvents); + } + + if (s_dwMax == 0) + { + if (nLength == -1) + return PeekConsoleInputW(hInput, lpBuffer, 1, lpEvents); + if (!ReadConsoleInputW(hInput, s_irCache, IRSIZE, &dwEvents)) + return FALSE; + s_dwIndex = 0; + s_dwMax = dwEvents; + if (dwEvents == 0) + { + *lpEvents = 0; + return TRUE; + } + + if (s_dwMax > 1) + { + head = 0; + tail = s_dwMax - 1; + while (head != tail) + { + if (s_irCache[head].EventType == WINDOW_BUFFER_SIZE_EVENT + && s_irCache[head + 1].EventType + == WINDOW_BUFFER_SIZE_EVENT) + { + /* Remove duplicate event to avoid flicker. */ + for (i = head; i < tail; ++i) + s_irCache[i] = s_irCache[i + 1]; + --tail; + continue; + } + head++; + } + s_dwMax = tail + 1; + } + } + + *lpBuffer = s_irCache[s_dwIndex]; + if (!(nLength == -1 || nLength == -2) && ++s_dwIndex >= s_dwMax) + s_dwMax = 0; + *lpEvents = 1; + return TRUE; + } + + /* + * Version of PeekConsoleInput() that works with IME. + */ + static BOOL + peek_console_input( + HANDLE hInput, + INPUT_RECORD *lpBuffer, + DWORD nLength, + LPDWORD lpEvents) + { + return read_console_input(hInput, lpBuffer, -1, lpEvents); + } + + # ifdef FEAT_CLIENTSERVER + static DWORD + msg_wait_for_multiple_objects( + DWORD nCount, + LPHANDLE pHandles, + BOOL fWaitAll, + DWORD dwMilliseconds, + DWORD dwWakeMask) + { + if (read_console_input(NULL, NULL, -2, NULL)) + return WAIT_OBJECT_0; + return MsgWaitForMultipleObjects(nCount, pHandles, fWaitAll, + dwMilliseconds, dwWakeMask); + } + # endif + + # ifndef FEAT_CLIENTSERVER + static DWORD + wait_for_single_object( + HANDLE hHandle, + DWORD dwMilliseconds) + { + if (read_console_input(NULL, NULL, -2, NULL)) + return WAIT_OBJECT_0; + return WaitForSingleObject(hHandle, dwMilliseconds); + } + # endif + #endif + + static void + get_exe_name(void) + { + /* Maximum length of $PATH is more than MAXPATHL. 8191 is often mentioned + * as the maximum length that works (plus a NUL byte). */ + #define MAX_ENV_PATH_LEN 8192 + char temp[MAX_ENV_PATH_LEN]; + char_u *p; + + if (exe_name == NULL) + { + /* store the name of the executable, may be used for $VIM */ + GetModuleFileName(NULL, temp, MAX_ENV_PATH_LEN - 1); + if (*temp != NUL) + exe_name = FullName_save((char_u *)temp, FALSE); + } + + if (exe_path == NULL && exe_name != NULL) + { + exe_path = vim_strnsave(exe_name, + (int)(gettail_sep(exe_name) - exe_name)); + if (exe_path != NULL) + { + /* Append our starting directory to $PATH, so that when doing + * "!xxd" it's found in our starting directory. Needed because + * SearchPath() also looks there. */ + p = mch_getenv("PATH"); + if (p == NULL + || STRLEN(p) + STRLEN(exe_path) + 2 < MAX_ENV_PATH_LEN) + { + if (p == NULL || *p == NUL) + temp[0] = NUL; + else + { + STRCPY(temp, p); + STRCAT(temp, ";"); + } + STRCAT(temp, exe_path); + vim_setenv((char_u *)"PATH", (char_u *)temp); + } + } + } + } + + /* + * Unescape characters in "p" that appear in "escaped". + */ + static void + unescape_shellxquote(char_u *p, char_u *escaped) + { + int l = (int)STRLEN(p); + int n; + + while (*p != NUL) + { + if (*p == '^' && vim_strchr(escaped, p[1]) != NULL) + mch_memmove(p, p + 1, l--); + n = (*mb_ptr2len)(p); + p += n; + l -= n; + } + } + + /* + * Load library "name". + */ + HINSTANCE + vimLoadLib(char *name) + { + HINSTANCE dll = NULL; + + /* NOTE: Do not use mch_dirname() and mch_chdir() here, they may call + * vimLoadLib() recursively, which causes a stack overflow. */ + if (exe_path == NULL) + get_exe_name(); + if (exe_path != NULL) + { + WCHAR old_dirw[MAXPATHL]; + + if (GetCurrentDirectoryW(MAXPATHL, old_dirw) != 0) + { + /* Change directory to where the executable is, both to make + * sure we find a .dll there and to avoid looking for a .dll + * in the current directory. */ + SetCurrentDirectory((LPCSTR)exe_path); + dll = LoadLibrary(name); + SetCurrentDirectoryW(old_dirw); + return dll; + } + } + return dll; + } + ++#if defined(VIMDLL) || defined(PROTO) ++/* ++ * Check if the current executable file is for the GUI subsystem. ++ */ ++ int ++mch_is_gui_executable(void) ++{ ++ PBYTE pImage = (PBYTE)GetModuleHandle(NULL); ++ PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)pImage; ++ PIMAGE_NT_HEADERS pPE; ++ ++ if (pDOS->e_magic != IMAGE_DOS_SIGNATURE) ++ return FALSE; ++ pPE = (PIMAGE_NT_HEADERS)(pImage + pDOS->e_lfanew); ++ if (pPE->Signature != IMAGE_NT_SIGNATURE) ++ return FALSE; ++ if (pPE->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI) ++ return TRUE; ++ return FALSE; ++} ++#endif ++ + #if defined(DYNAMIC_ICONV) || defined(DYNAMIC_GETTEXT) || defined(PROTO) + /* + * Get related information about 'funcname' which is imported by 'hInst'. + * If 'info' is 0, return the function address. + * If 'info' is 1, return the module name which the function is imported from. + */ + static void * + get_imported_func_info(HINSTANCE hInst, const char *funcname, int info) + { + PBYTE pImage = (PBYTE)hInst; + PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)hInst; + PIMAGE_NT_HEADERS pPE; + PIMAGE_IMPORT_DESCRIPTOR pImpDesc; + PIMAGE_THUNK_DATA pIAT; /* Import Address Table */ + PIMAGE_THUNK_DATA pINT; /* Import Name Table */ + PIMAGE_IMPORT_BY_NAME pImpName; + + if (pDOS->e_magic != IMAGE_DOS_SIGNATURE) + return NULL; + pPE = (PIMAGE_NT_HEADERS)(pImage + pDOS->e_lfanew); + if (pPE->Signature != IMAGE_NT_SIGNATURE) + return NULL; + pImpDesc = (PIMAGE_IMPORT_DESCRIPTOR)(pImage + + pPE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] + .VirtualAddress); + for (; pImpDesc->FirstThunk; ++pImpDesc) + { + if (!pImpDesc->OriginalFirstThunk) + continue; + pIAT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->FirstThunk); + pINT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->OriginalFirstThunk); + for (; pIAT->u1.Function; ++pIAT, ++pINT) + { + if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal)) + continue; + pImpName = (PIMAGE_IMPORT_BY_NAME)(pImage + + (UINT_PTR)(pINT->u1.AddressOfData)); + if (strcmp((char *)pImpName->Name, funcname) == 0) + { + switch (info) + { + case 0: + return (void *)pIAT->u1.Function; + case 1: + return (void *)(pImage + pImpDesc->Name); + default: + return NULL; + } + } + } + } + return NULL; + } + + /* + * Get the module handle which 'funcname' in 'hInst' is imported from. + */ + HINSTANCE + find_imported_module_by_funcname(HINSTANCE hInst, const char *funcname) + { + char *modulename; + + modulename = (char *)get_imported_func_info(hInst, funcname, 1); + if (modulename != NULL) + return GetModuleHandleA(modulename); + return NULL; + } + + /* + * Get the address of 'funcname' which is imported by 'hInst' DLL. + */ + void * + get_dll_import_func(HINSTANCE hInst, const char *funcname) + { + return get_imported_func_info(hInst, funcname, 0); + } + #endif + + #if defined(DYNAMIC_GETTEXT) || defined(PROTO) + # ifndef GETTEXT_DLL + # define GETTEXT_DLL "libintl.dll" + # define GETTEXT_DLL_ALT1 "libintl-8.dll" + # define GETTEXT_DLL_ALT2 "intl.dll" + # endif + /* Dummy functions */ + static char *null_libintl_gettext(const char *); + static char *null_libintl_ngettext(const char *, const char *, unsigned long n); + static char *null_libintl_textdomain(const char *); + static char *null_libintl_bindtextdomain(const char *, const char *); + static char *null_libintl_bind_textdomain_codeset(const char *, const char *); + static int null_libintl_wputenv(const wchar_t *); + + static HINSTANCE hLibintlDLL = NULL; + char *(*dyn_libintl_gettext)(const char *) = null_libintl_gettext; + char *(*dyn_libintl_ngettext)(const char *, const char *, unsigned long n) + = null_libintl_ngettext; + char *(*dyn_libintl_textdomain)(const char *) = null_libintl_textdomain; + char *(*dyn_libintl_bindtextdomain)(const char *, const char *) + = null_libintl_bindtextdomain; + char *(*dyn_libintl_bind_textdomain_codeset)(const char *, const char *) + = null_libintl_bind_textdomain_codeset; + int (*dyn_libintl_wputenv)(const wchar_t *) = null_libintl_wputenv; + + int + dyn_libintl_init(void) + { + int i; + static struct + { + char *name; + FARPROC *ptr; + } libintl_entry[] = + { + {"gettext", (FARPROC*)&dyn_libintl_gettext}, + {"ngettext", (FARPROC*)&dyn_libintl_ngettext}, + {"textdomain", (FARPROC*)&dyn_libintl_textdomain}, + {"bindtextdomain", (FARPROC*)&dyn_libintl_bindtextdomain}, + {NULL, NULL} + }; + HINSTANCE hmsvcrt; + + // No need to initialize twice. + if (hLibintlDLL != NULL) + return 1; + // Load gettext library (libintl.dll and other names). + hLibintlDLL = vimLoadLib(GETTEXT_DLL); + #ifdef GETTEXT_DLL_ALT1 + if (!hLibintlDLL) + hLibintlDLL = vimLoadLib(GETTEXT_DLL_ALT1); + #endif + #ifdef GETTEXT_DLL_ALT2 + if (!hLibintlDLL) + hLibintlDLL = vimLoadLib(GETTEXT_DLL_ALT2); + #endif + if (!hLibintlDLL) + { + if (p_verbose > 0) + { + verbose_enter(); + semsg(_(e_loadlib), GETTEXT_DLL); + verbose_leave(); + } + return 0; + } + for (i = 0; libintl_entry[i].name != NULL + && libintl_entry[i].ptr != NULL; ++i) + { + if ((*libintl_entry[i].ptr = (FARPROC)GetProcAddress(hLibintlDLL, + libintl_entry[i].name)) == NULL) + { + dyn_libintl_end(); + if (p_verbose > 0) + { + verbose_enter(); + semsg(_(e_loadfunc), libintl_entry[i].name); + verbose_leave(); + } + return 0; + } + } + + /* The bind_textdomain_codeset() function is optional. */ + dyn_libintl_bind_textdomain_codeset = (void *)GetProcAddress(hLibintlDLL, + "bind_textdomain_codeset"); + if (dyn_libintl_bind_textdomain_codeset == NULL) + dyn_libintl_bind_textdomain_codeset = + null_libintl_bind_textdomain_codeset; + + /* _wputenv() function for the libintl.dll is optional. */ + hmsvcrt = find_imported_module_by_funcname(hLibintlDLL, "getenv"); + if (hmsvcrt != NULL) + dyn_libintl_wputenv = (void *)GetProcAddress(hmsvcrt, "_wputenv"); + if (dyn_libintl_wputenv == NULL || dyn_libintl_wputenv == _wputenv) + dyn_libintl_wputenv = null_libintl_wputenv; + + return 1; + } + + void + dyn_libintl_end(void) + { + if (hLibintlDLL) + FreeLibrary(hLibintlDLL); + hLibintlDLL = NULL; + dyn_libintl_gettext = null_libintl_gettext; + dyn_libintl_ngettext = null_libintl_ngettext; + dyn_libintl_textdomain = null_libintl_textdomain; + dyn_libintl_bindtextdomain = null_libintl_bindtextdomain; + dyn_libintl_bind_textdomain_codeset = null_libintl_bind_textdomain_codeset; + dyn_libintl_wputenv = null_libintl_wputenv; + } + + static char * + null_libintl_gettext(const char *msgid) + { + return (char*)msgid; + } + + static char * + null_libintl_ngettext( + const char *msgid, + const char *msgid_plural, + unsigned long n) + { + return (char *)(n == 1 ? msgid : msgid_plural); + } + + static char * + null_libintl_bindtextdomain( + const char *domainname UNUSED, + const char *dirname UNUSED) + { + return NULL; + } + + static char * + null_libintl_bind_textdomain_codeset( + const char *domainname UNUSED, + const char *codeset UNUSED) + { + return NULL; + } + + static char * + null_libintl_textdomain(const char *domainname UNUSED) + { + return NULL; + } + + static int + null_libintl_wputenv(const wchar_t *envstring UNUSED) + { + return 0; + } + + #endif /* DYNAMIC_GETTEXT */ + + /* This symbol is not defined in older versions of the SDK or Visual C++ */ + + #ifndef VER_PLATFORM_WIN32_WINDOWS + # define VER_PLATFORM_WIN32_WINDOWS 1 + #endif + + DWORD g_PlatformId; + + #ifdef HAVE_ACL + # ifndef PROTO + # include + # endif + # ifndef PROTECTED_DACL_SECURITY_INFORMATION + # define PROTECTED_DACL_SECURITY_INFORMATION 0x80000000L + # endif + #endif + + #ifdef HAVE_ACL + /* + * Enables or disables the specified privilege. + */ + static BOOL + win32_enable_privilege(LPTSTR lpszPrivilege, BOOL bEnable) + { + BOOL bResult; + LUID luid; + HANDLE hToken; + TOKEN_PRIVILEGES tokenPrivileges; + + if (!OpenProcessToken(GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + return FALSE; + + if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) + { + CloseHandle(hToken); + return FALSE; + } + + tokenPrivileges.PrivilegeCount = 1; + tokenPrivileges.Privileges[0].Luid = luid; + tokenPrivileges.Privileges[0].Attributes = bEnable ? + SE_PRIVILEGE_ENABLED : 0; + + bResult = AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, + sizeof(TOKEN_PRIVILEGES), NULL, NULL); + + CloseHandle(hToken); + + return bResult && GetLastError() == ERROR_SUCCESS; + } + #endif + + /* + * Set g_PlatformId to VER_PLATFORM_WIN32_NT (NT) or + * VER_PLATFORM_WIN32_WINDOWS (Win95). + */ + void + PlatformId(void) + { + static int done = FALSE; + + if (!done) + { + OSVERSIONINFO ovi; + + ovi.dwOSVersionInfoSize = sizeof(ovi); + GetVersionEx(&ovi); + + g_PlatformId = ovi.dwPlatformId; + + if ((ovi.dwMajorVersion == 6 && ovi.dwMinorVersion >= 2) + || ovi.dwMajorVersion > 6) + win8_or_later = TRUE; + + #ifdef HAVE_ACL + /* Enable privilege for getting or setting SACLs. */ + win32_enable_privilege(SE_SECURITY_NAME, TRUE); + #endif + done = TRUE; + } + } + +-#ifndef FEAT_GUI_MSWIN ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + + #define SHIFT (SHIFT_PRESSED) + #define CTRL (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED) + #define ALT (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED) + #define ALT_GR (RIGHT_ALT_PRESSED | LEFT_CTRL_PRESSED) + + + /* When uChar.AsciiChar is 0, then we need to look at wVirtualKeyCode. + * We map function keys to their ANSI terminal equivalents, as produced + * by ANSI.SYS, for compatibility with the MS-DOS version of Vim. Any + * ANSI key with a value >= '\300' is nonstandard, but provided anyway + * so that the user can have access to all SHIFT-, CTRL-, and ALT- + * combinations of function/arrow/etc keys. + */ + + static const struct + { + WORD wVirtKey; + BOOL fAnsiKey; + int chAlone; + int chShift; + int chCtrl; + int chAlt; + } VirtKeyMap[] = + { + // Key ANSI alone shift ctrl alt + { VK_ESCAPE,FALSE, ESC, ESC, ESC, ESC, }, + + { VK_F1, TRUE, ';', 'T', '^', 'h', }, + { VK_F2, TRUE, '<', 'U', '_', 'i', }, + { VK_F3, TRUE, '=', 'V', '`', 'j', }, + { VK_F4, TRUE, '>', 'W', 'a', 'k', }, + { VK_F5, TRUE, '?', 'X', 'b', 'l', }, + { VK_F6, TRUE, '@', 'Y', 'c', 'm', }, + { VK_F7, TRUE, 'A', 'Z', 'd', 'n', }, + { VK_F8, TRUE, 'B', '[', 'e', 'o', }, + { VK_F9, TRUE, 'C', '\\', 'f', 'p', }, + { VK_F10, TRUE, 'D', ']', 'g', 'q', }, + { VK_F11, TRUE, '\205', '\207', '\211', '\213', }, + { VK_F12, TRUE, '\206', '\210', '\212', '\214', }, + + { VK_HOME, TRUE, 'G', '\302', 'w', '\303', }, + { VK_UP, TRUE, 'H', '\304', '\305', '\306', }, + { VK_PRIOR, TRUE, 'I', '\307', '\204', '\310', }, // PgUp + { VK_LEFT, TRUE, 'K', '\311', 's', '\312', }, + { VK_RIGHT, TRUE, 'M', '\313', 't', '\314', }, + { VK_END, TRUE, 'O', '\315', 'u', '\316', }, + { VK_DOWN, TRUE, 'P', '\317', '\320', '\321', }, + { VK_NEXT, TRUE, 'Q', '\322', 'v', '\323', }, // PgDn + { VK_INSERT,TRUE, 'R', '\324', '\325', '\326', }, + { VK_DELETE,TRUE, 'S', '\327', '\330', '\331', }, + { VK_BACK, TRUE, 'x', 'y', 'z', '{', }, // Backspace + + { VK_SNAPSHOT,TRUE, 0, 0, 0, 'r', }, // PrtScrn + + #if 0 + // Most people don't have F13-F20, but what the hell... + { VK_F13, TRUE, '\332', '\333', '\334', '\335', }, + { VK_F14, TRUE, '\336', '\337', '\340', '\341', }, + { VK_F15, TRUE, '\342', '\343', '\344', '\345', }, + { VK_F16, TRUE, '\346', '\347', '\350', '\351', }, + { VK_F17, TRUE, '\352', '\353', '\354', '\355', }, + { VK_F18, TRUE, '\356', '\357', '\360', '\361', }, + { VK_F19, TRUE, '\362', '\363', '\364', '\365', }, + { VK_F20, TRUE, '\366', '\367', '\370', '\371', }, + #endif + { VK_ADD, TRUE, 'N', 'N', 'N', 'N', }, // keyp '+' + { VK_SUBTRACT, TRUE,'J', 'J', 'J', 'J', }, // keyp '-' + // { VK_DIVIDE, TRUE,'N', 'N', 'N', 'N', }, // keyp '/' + { VK_MULTIPLY, TRUE,'7', '7', '7', '7', }, // keyp '*' + + { VK_NUMPAD0,TRUE, '\332', '\333', '\334', '\335', }, + { VK_NUMPAD1,TRUE, '\336', '\337', '\340', '\341', }, + { VK_NUMPAD2,TRUE, '\342', '\343', '\344', '\345', }, + { VK_NUMPAD3,TRUE, '\346', '\347', '\350', '\351', }, + { VK_NUMPAD4,TRUE, '\352', '\353', '\354', '\355', }, + { VK_NUMPAD5,TRUE, '\356', '\357', '\360', '\361', }, + { VK_NUMPAD6,TRUE, '\362', '\363', '\364', '\365', }, + { VK_NUMPAD7,TRUE, '\366', '\367', '\370', '\371', }, + { VK_NUMPAD8,TRUE, '\372', '\373', '\374', '\375', }, + // Sorry, out of number space! + { VK_NUMPAD9,TRUE, '\376', '\377', '|', '}', }, + }; + + + #ifdef _MSC_VER + // The ToAscii bug destroys several registers. Need to turn off optimization + // or the GetConsoleKeyboardLayoutName hack will fail in non-debug versions + # pragma warning(push) + # pragma warning(disable: 4748) + # pragma optimize("", off) + #endif + + #if defined(__GNUC__) && !defined(__MINGW32__) && !defined(__CYGWIN__) + # define UChar UnicodeChar + #else + # define UChar uChar.UnicodeChar + #endif + + /* The return code indicates key code size. */ + static int + #ifdef __BORLANDC__ + __stdcall + #endif + win32_kbd_patch_key( + KEY_EVENT_RECORD *pker) + { + UINT uMods = pker->dwControlKeyState; + static int s_iIsDead = 0; + static WORD awAnsiCode[2]; + static BYTE abKeystate[256]; + + + if (s_iIsDead == 2) + { + pker->UChar = (WCHAR) awAnsiCode[1]; + s_iIsDead = 0; + return 1; + } + + if (pker->UChar != 0) + return 1; + + vim_memset(abKeystate, 0, sizeof (abKeystate)); + + /* Clear any pending dead keys */ + ToUnicode(VK_SPACE, MapVirtualKey(VK_SPACE, 0), abKeystate, awAnsiCode, 2, 0); + + if (uMods & SHIFT_PRESSED) + abKeystate[VK_SHIFT] = 0x80; + if (uMods & CAPSLOCK_ON) + abKeystate[VK_CAPITAL] = 1; + + if ((uMods & ALT_GR) == ALT_GR) + { + abKeystate[VK_CONTROL] = abKeystate[VK_LCONTROL] = + abKeystate[VK_MENU] = abKeystate[VK_RMENU] = 0x80; + } + + s_iIsDead = ToUnicode(pker->wVirtualKeyCode, pker->wVirtualScanCode, + abKeystate, awAnsiCode, 2, 0); + + if (s_iIsDead > 0) + pker->UChar = (WCHAR) awAnsiCode[0]; + + return s_iIsDead; + } + + #ifdef _MSC_VER + /* MUST switch optimization on again here, otherwise a call to + * decode_key_event() may crash (e.g. when hitting caps-lock) */ + # pragma optimize("", on) + # pragma warning(pop) + + # if (_MSC_VER < 1100) + /* MUST turn off global optimisation for this next function, or + * pressing ctrl-minus in insert mode crashes Vim when built with + * VC4.1. -- negri. */ + # pragma optimize("g", off) + # endif + #endif + + static BOOL g_fJustGotFocus = FALSE; + + /* + * Decode a KEY_EVENT into one or two keystrokes + */ + static BOOL + decode_key_event( + KEY_EVENT_RECORD *pker, + WCHAR *pch, + WCHAR *pch2, + int *pmodifiers, + BOOL fDoPost) + { + int i; + const int nModifs = pker->dwControlKeyState & (SHIFT | ALT | CTRL); + + *pch = *pch2 = NUL; + g_fJustGotFocus = FALSE; + + /* ignore key up events */ + if (!pker->bKeyDown) + return FALSE; + + /* ignore some keystrokes */ + switch (pker->wVirtualKeyCode) + { + /* modifiers */ + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: /* Alt key */ + return FALSE; + + default: + break; + } + + /* special cases */ + if ((nModifs & CTRL) != 0 && (nModifs & ~CTRL) == 0 && pker->UChar == NUL) + { + /* Ctrl-6 is Ctrl-^ */ + if (pker->wVirtualKeyCode == '6') + { + *pch = Ctrl_HAT; + return TRUE; + } + /* Ctrl-2 is Ctrl-@ */ + else if (pker->wVirtualKeyCode == '2') + { + *pch = NUL; + return TRUE; + } + /* Ctrl-- is Ctrl-_ */ + else if (pker->wVirtualKeyCode == 0xBD) + { + *pch = Ctrl__; + return TRUE; + } + } + + /* Shift-TAB */ + if (pker->wVirtualKeyCode == VK_TAB && (nModifs & SHIFT_PRESSED)) + { + *pch = K_NUL; + *pch2 = '\017'; + return TRUE; + } + + for (i = sizeof(VirtKeyMap) / sizeof(VirtKeyMap[0]); --i >= 0; ) + { + if (VirtKeyMap[i].wVirtKey == pker->wVirtualKeyCode) + { + if (nModifs == 0) + *pch = VirtKeyMap[i].chAlone; + else if ((nModifs & SHIFT) != 0 && (nModifs & ~SHIFT) == 0) + *pch = VirtKeyMap[i].chShift; + else if ((nModifs & CTRL) != 0 && (nModifs & ~CTRL) == 0) + *pch = VirtKeyMap[i].chCtrl; + else if ((nModifs & ALT) != 0 && (nModifs & ~ALT) == 0) + *pch = VirtKeyMap[i].chAlt; + + if (*pch != 0) + { + if (VirtKeyMap[i].fAnsiKey) + { + *pch2 = *pch; + *pch = K_NUL; + } + + return TRUE; + } + } + } + + i = win32_kbd_patch_key(pker); + + if (i < 0) + *pch = NUL; + else + { + *pch = (i > 0) ? pker->UChar : NUL; + + if (pmodifiers != NULL) + { + /* Pass on the ALT key as a modifier, but only when not combined + * with CTRL (which is ALTGR). */ + if ((nModifs & ALT) != 0 && (nModifs & CTRL) == 0) + *pmodifiers |= MOD_MASK_ALT; + + /* Pass on SHIFT only for special keys, because we don't know when + * it's already included with the character. */ + if ((nModifs & SHIFT) != 0 && *pch <= 0x20) + *pmodifiers |= MOD_MASK_SHIFT; + + /* Pass on CTRL only for non-special keys, because we don't know + * when it's already included with the character. And not when + * combined with ALT (which is ALTGR). */ + if ((nModifs & CTRL) != 0 && (nModifs & ALT) == 0 + && *pch >= 0x20 && *pch < 0x80) + *pmodifiers |= MOD_MASK_CTRL; + } + } + + return (*pch != NUL); + } + + #ifdef _MSC_VER + # pragma optimize("", on) + #endif + + #endif /* FEAT_GUI_MSWIN */ + + + #ifdef FEAT_MOUSE + + /* + * For the GUI the mouse handling is in gui_w32.c. + */ +-# ifdef FEAT_GUI_MSWIN ++# if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL) + void + mch_setmouse(int on UNUSED) + { + } + # else + static int g_fMouseAvail = FALSE; /* mouse present */ + static int g_fMouseActive = FALSE; /* mouse enabled */ + static int g_nMouseClick = -1; /* mouse status */ + static int g_xMouse; /* mouse x coordinate */ + static int g_yMouse; /* mouse y coordinate */ + + /* + * Enable or disable mouse input + */ + void + mch_setmouse(int on) + { + DWORD cmodein; + ++# ifdef VIMDLL ++ if (gui.in_use) ++ return; ++# endif + if (!g_fMouseAvail) + return; + + g_fMouseActive = on; + GetConsoleMode(g_hConIn, &cmodein); + + if (g_fMouseActive) + cmodein |= ENABLE_MOUSE_INPUT; + else + cmodein &= ~ENABLE_MOUSE_INPUT; + + SetConsoleMode(g_hConIn, cmodein); + } + + + #if defined(FEAT_BEVAL_TERM) || defined(PROTO) + /* + * Called when 'balloonevalterm' changed. + */ + void + mch_bevalterm_changed(void) + { + mch_setmouse(g_fMouseActive); + } + #endif + + /* + * Decode a MOUSE_EVENT. If it's a valid event, return MOUSE_LEFT, + * MOUSE_MIDDLE, or MOUSE_RIGHT for a click; MOUSE_DRAG for a mouse + * move with a button held down; and MOUSE_RELEASE after a MOUSE_DRAG + * or a MOUSE_LEFT, _MIDDLE, or _RIGHT. We encode the button type, + * the number of clicks, and the Shift/Ctrl/Alt modifiers in g_nMouseClick, + * and we return the mouse position in g_xMouse and g_yMouse. + * + * Every MOUSE_LEFT, _MIDDLE, or _RIGHT will be followed by zero or more + * MOUSE_DRAGs and one MOUSE_RELEASE. MOUSE_RELEASE will be followed only + * by MOUSE_LEFT, _MIDDLE, or _RIGHT. + * + * For multiple clicks, we send, say, MOUSE_LEFT/1 click, MOUSE_RELEASE, + * MOUSE_LEFT/2 clicks, MOUSE_RELEASE, MOUSE_LEFT/3 clicks, MOUSE_RELEASE, .... + * + * Windows will send us MOUSE_MOVED notifications whenever the mouse + * moves, even if it stays within the same character cell. We ignore + * all MOUSE_MOVED messages if the position hasn't really changed, and + * we ignore all MOUSE_MOVED messages where no button is held down (i.e., + * we're only interested in MOUSE_DRAG). + * + * All of this is complicated by the code that fakes MOUSE_MIDDLE on + * 2-button mouses by pressing the left & right buttons simultaneously. + * In practice, it's almost impossible to click both at the same time, + * so we need to delay a little. Also, we tend not to get MOUSE_RELEASE + * in such cases, if the user is clicking quickly. + */ + static BOOL + decode_mouse_event( + MOUSE_EVENT_RECORD *pmer) + { + static int s_nOldButton = -1; + static int s_nOldMouseClick = -1; + static int s_xOldMouse = -1; + static int s_yOldMouse = -1; + static linenr_T s_old_topline = 0; + #ifdef FEAT_DIFF + static int s_old_topfill = 0; + #endif + static int s_cClicks = 1; + static BOOL s_fReleased = TRUE; + static DWORD s_dwLastClickTime = 0; + static BOOL s_fNextIsMiddle = FALSE; + + static DWORD cButtons = 0; /* number of buttons supported */ + + const DWORD LEFT = FROM_LEFT_1ST_BUTTON_PRESSED; + const DWORD MIDDLE = FROM_LEFT_2ND_BUTTON_PRESSED; + const DWORD RIGHT = RIGHTMOST_BUTTON_PRESSED; + const DWORD LEFT_RIGHT = LEFT | RIGHT; + + int nButton; + + if (cButtons == 0 && !GetNumberOfConsoleMouseButtons(&cButtons)) + cButtons = 2; + + if (!g_fMouseAvail || !g_fMouseActive) + { + g_nMouseClick = -1; + return FALSE; + } + + /* get a spurious MOUSE_EVENT immediately after receiving focus; ignore */ + if (g_fJustGotFocus) + { + g_fJustGotFocus = FALSE; + return FALSE; + } + + /* unprocessed mouse click? */ + if (g_nMouseClick != -1) + return TRUE; + + nButton = -1; + g_xMouse = pmer->dwMousePosition.X; + g_yMouse = pmer->dwMousePosition.Y; + + if (pmer->dwEventFlags == MOUSE_MOVED) + { + /* Ignore MOUSE_MOVED events if (x, y) hasn't changed. (We get these + * events even when the mouse moves only within a char cell.) */ + if (s_xOldMouse == g_xMouse && s_yOldMouse == g_yMouse) + return FALSE; + } + + /* If no buttons are pressed... */ + if ((pmer->dwButtonState & ((1 << cButtons) - 1)) == 0) + { + nButton = MOUSE_RELEASE; + + /* If the last thing returned was MOUSE_RELEASE, ignore this */ + if (s_fReleased) + { + #ifdef FEAT_BEVAL_TERM + /* do return mouse move events when we want them */ + if (p_bevalterm) + nButton = MOUSE_DRAG; + else + #endif + return FALSE; + } + + s_fReleased = TRUE; + } + else /* one or more buttons pressed */ + { + /* on a 2-button mouse, hold down left and right buttons + * simultaneously to get MIDDLE. */ + + if (cButtons == 2 && s_nOldButton != MOUSE_DRAG) + { + DWORD dwLR = (pmer->dwButtonState & LEFT_RIGHT); + + /* if either left or right button only is pressed, see if the + * next mouse event has both of them pressed */ + if (dwLR == LEFT || dwLR == RIGHT) + { + for (;;) + { + /* wait a short time for next input event */ + if (WaitForSingleObject(g_hConIn, p_mouset / 3) + != WAIT_OBJECT_0) + break; + else + { + DWORD cRecords = 0; + INPUT_RECORD ir; + MOUSE_EVENT_RECORD* pmer2 = &ir.Event.MouseEvent; + + peek_console_input(g_hConIn, &ir, 1, &cRecords); + + if (cRecords == 0 || ir.EventType != MOUSE_EVENT + || !(pmer2->dwButtonState & LEFT_RIGHT)) + break; + else + { + if (pmer2->dwEventFlags != MOUSE_MOVED) + { + read_console_input(g_hConIn, &ir, 1, &cRecords); + + return decode_mouse_event(pmer2); + } + else if (s_xOldMouse == pmer2->dwMousePosition.X && + s_yOldMouse == pmer2->dwMousePosition.Y) + { + /* throw away spurious mouse move */ + read_console_input(g_hConIn, &ir, 1, &cRecords); + + /* are there any more mouse events in queue? */ + peek_console_input(g_hConIn, &ir, 1, &cRecords); + + if (cRecords==0 || ir.EventType != MOUSE_EVENT) + break; + } + else + break; + } + } + } + } + } + + if (s_fNextIsMiddle) + { + nButton = (pmer->dwEventFlags == MOUSE_MOVED) + ? MOUSE_DRAG : MOUSE_MIDDLE; + s_fNextIsMiddle = FALSE; + } + else if (cButtons == 2 && + ((pmer->dwButtonState & LEFT_RIGHT) == LEFT_RIGHT)) + { + nButton = MOUSE_MIDDLE; + + if (! s_fReleased && pmer->dwEventFlags != MOUSE_MOVED) + { + s_fNextIsMiddle = TRUE; + nButton = MOUSE_RELEASE; + } + } + else if ((pmer->dwButtonState & LEFT) == LEFT) + nButton = MOUSE_LEFT; + else if ((pmer->dwButtonState & MIDDLE) == MIDDLE) + nButton = MOUSE_MIDDLE; + else if ((pmer->dwButtonState & RIGHT) == RIGHT) + nButton = MOUSE_RIGHT; + + if (! s_fReleased && ! s_fNextIsMiddle + && nButton != s_nOldButton && s_nOldButton != MOUSE_DRAG) + return FALSE; + + s_fReleased = s_fNextIsMiddle; + } + + if (pmer->dwEventFlags == 0 || pmer->dwEventFlags == DOUBLE_CLICK) + { + /* button pressed or released, without mouse moving */ + if (nButton != -1 && nButton != MOUSE_RELEASE) + { + DWORD dwCurrentTime = GetTickCount(); + + if (s_xOldMouse != g_xMouse + || s_yOldMouse != g_yMouse + || s_nOldButton != nButton + || s_old_topline != curwin->w_topline + #ifdef FEAT_DIFF + || s_old_topfill != curwin->w_topfill + #endif + || (int)(dwCurrentTime - s_dwLastClickTime) > p_mouset) + { + s_cClicks = 1; + } + else if (++s_cClicks > 4) + { + s_cClicks = 1; + } + + s_dwLastClickTime = dwCurrentTime; + } + } + else if (pmer->dwEventFlags == MOUSE_MOVED) + { + if (nButton != -1 && nButton != MOUSE_RELEASE) + nButton = MOUSE_DRAG; + + s_cClicks = 1; + } + + if (nButton == -1) + return FALSE; + + if (nButton != MOUSE_RELEASE) + s_nOldButton = nButton; + + g_nMouseClick = nButton; + + if (pmer->dwControlKeyState & SHIFT_PRESSED) + g_nMouseClick |= MOUSE_SHIFT; + if (pmer->dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) + g_nMouseClick |= MOUSE_CTRL; + if (pmer->dwControlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) + g_nMouseClick |= MOUSE_ALT; + + if (nButton != MOUSE_DRAG && nButton != MOUSE_RELEASE) + SET_NUM_MOUSE_CLICKS(g_nMouseClick, s_cClicks); + + /* only pass on interesting (i.e., different) mouse events */ + if (s_xOldMouse == g_xMouse + && s_yOldMouse == g_yMouse + && s_nOldMouseClick == g_nMouseClick) + { + g_nMouseClick = -1; + return FALSE; + } + + s_xOldMouse = g_xMouse; + s_yOldMouse = g_yMouse; + s_old_topline = curwin->w_topline; + #ifdef FEAT_DIFF + s_old_topfill = curwin->w_topfill; + #endif + s_nOldMouseClick = g_nMouseClick; + + return TRUE; + } + + # endif /* FEAT_GUI_MSWIN */ + #endif /* FEAT_MOUSE */ + + + #ifdef MCH_CURSOR_SHAPE + /* + * Set the shape of the cursor. + * 'thickness' can be from 1 (thin) to 99 (block) + */ + static void + mch_set_cursor_shape(int thickness) + { + CONSOLE_CURSOR_INFO ConsoleCursorInfo; + ConsoleCursorInfo.dwSize = thickness; + ConsoleCursorInfo.bVisible = s_cursor_visible; + + SetConsoleCursorInfo(g_hConOut, &ConsoleCursorInfo); + if (s_cursor_visible) + SetConsoleCursorPosition(g_hConOut, g_coord); + } + + void + mch_update_cursor(void) + { + int idx; + int thickness; + ++# ifdef VIMDLL ++ if (gui.in_use) ++ return; ++# endif ++ + /* + * How the cursor is drawn depends on the current mode. + */ + idx = get_shape_idx(FALSE); + + if (shape_table[idx].shape == SHAPE_BLOCK) + thickness = 99; /* 100 doesn't work on W95 */ + else + thickness = shape_table[idx].percentage; + mch_set_cursor_shape(thickness); + } + #endif + +-#ifndef FEAT_GUI_MSWIN /* this isn't used for the GUI */ ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + /* + * Handle FOCUS_EVENT. + */ + static void + handle_focus_event(INPUT_RECORD ir) + { + g_fJustGotFocus = ir.Event.FocusEvent.bSetFocus; + ui_focus_change((int)g_fJustGotFocus); + } + + static void ResizeConBuf(HANDLE hConsole, COORD coordScreen); + + /* + * Wait until console input from keyboard or mouse is available, + * or the time is up. + * When "ignore_input" is TRUE even wait when input is available. + * Return TRUE if something is available FALSE if not. + */ + static int + WaitForChar(long msec, int ignore_input) + { + DWORD dwNow = 0, dwEndTime = 0; + INPUT_RECORD ir; + DWORD cRecords; + WCHAR ch, ch2; + #ifdef FEAT_TIMERS + int tb_change_cnt = typebuf.tb_change_cnt; + #endif + + if (msec > 0) + /* Wait until the specified time has elapsed. */ + dwEndTime = GetTickCount() + msec; + else if (msec < 0) + /* Wait forever. */ + dwEndTime = INFINITE; + + // We need to loop until the end of the time period, because + // we might get multiple unusable mouse events in that time. + for (;;) + { + // Only process messages when waiting. + if (msec != 0) + { + #ifdef MESSAGE_QUEUE + parse_queued_messages(); + #endif + #ifdef FEAT_MZSCHEME + mzvim_check_threads(); + #endif + #ifdef FEAT_CLIENTSERVER + serverProcessPendingMessages(); + #endif + } + + if (0 + #ifdef FEAT_MOUSE + || g_nMouseClick != -1 + #endif + #ifdef FEAT_CLIENTSERVER + || (!ignore_input && input_available()) + #endif + ) + return TRUE; + + if (msec > 0) + { + /* If the specified wait time has passed, return. Beware that + * GetTickCount() may wrap around (overflow). */ + dwNow = GetTickCount(); + if ((int)(dwNow - dwEndTime) >= 0) + break; + } + if (msec != 0) + { + DWORD dwWaitTime = dwEndTime - dwNow; + + #ifdef FEAT_JOB_CHANNEL + /* Check channel while waiting for input. */ + if (dwWaitTime > 100) + { + dwWaitTime = 100; + /* If there is readahead then parse_queued_messages() timed out + * and we should call it again soon. */ + if (channel_any_readahead()) + dwWaitTime = 10; + } + #endif + #ifdef FEAT_BEVAL_GUI + if (p_beval && dwWaitTime > 100) + /* The 'balloonexpr' may indirectly invoke a callback while + * waiting for a character, need to check often. */ + dwWaitTime = 100; + #endif + #ifdef FEAT_MZSCHEME + if (mzthreads_allowed() && p_mzq > 0 + && (msec < 0 || (long)dwWaitTime > p_mzq)) + dwWaitTime = p_mzq; /* don't wait longer than 'mzquantum' */ + #endif + #ifdef FEAT_TIMERS + // When waiting very briefly don't trigger timers. + if (dwWaitTime > 10) + { + long due_time; + + // Trigger timers and then get the time in msec until the next + // one is due. Wait up to that time. + due_time = check_due_timer(); + if (typebuf.tb_change_cnt != tb_change_cnt) + { + // timer may have used feedkeys(). + return FALSE; + } + if (due_time > 0 && dwWaitTime > (DWORD)due_time) + dwWaitTime = due_time; + } + #endif + if ( + #ifdef FEAT_CLIENTSERVER + // Wait for either an event on the console input or a + // message in the client-server window. + msg_wait_for_multiple_objects(1, &g_hConIn, FALSE, + dwWaitTime, QS_SENDMESSAGE) != WAIT_OBJECT_0 + #else + wait_for_single_object(g_hConIn, dwWaitTime) + != WAIT_OBJECT_0 + #endif + ) + continue; + } + + cRecords = 0; + peek_console_input(g_hConIn, &ir, 1, &cRecords); + + #ifdef FEAT_MBYTE_IME + if (State & CMDLINE && msg_row == Rows - 1) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (GetConsoleScreenBufferInfo(g_hConOut, &csbi)) + { + if (csbi.dwCursorPosition.Y != msg_row) + { + /* The screen is now messed up, must redraw the + * command line and later all the windows. */ + redraw_all_later(CLEAR); + cmdline_row -= (msg_row - csbi.dwCursorPosition.Y); + redrawcmd(); + } + } + } + #endif + + if (cRecords > 0) + { + if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown) + { + #ifdef FEAT_MBYTE_IME + /* Windows IME sends two '\n's with only one 'ENTER'. First: + * wVirtualKeyCode == 13. second: wVirtualKeyCode == 0 */ + if (ir.Event.KeyEvent.UChar == 0 + && ir.Event.KeyEvent.wVirtualKeyCode == 13) + { + read_console_input(g_hConIn, &ir, 1, &cRecords); + continue; + } + #endif + if (decode_key_event(&ir.Event.KeyEvent, &ch, &ch2, + NULL, FALSE)) + return TRUE; + } + + read_console_input(g_hConIn, &ir, 1, &cRecords); + + if (ir.EventType == FOCUS_EVENT) + handle_focus_event(ir); + else if (ir.EventType == WINDOW_BUFFER_SIZE_EVENT) + { + COORD dwSize = ir.Event.WindowBufferSizeEvent.dwSize; + + // Only call shell_resized() when the size actually change to + // avoid the screen is cleard. + if (dwSize.X != Columns || dwSize.Y != Rows) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(g_hConOut, &csbi); + dwSize.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + ResizeConBuf(g_hConOut, dwSize); + shell_resized(); + } + } + #ifdef FEAT_MOUSE + else if (ir.EventType == MOUSE_EVENT + && decode_mouse_event(&ir.Event.MouseEvent)) + return TRUE; + #endif + } + else if (msec == 0) + break; + } + + #ifdef FEAT_CLIENTSERVER + /* Something might have been received while we were waiting. */ + if (input_available()) + return TRUE; + #endif + + return FALSE; + } + +-#ifndef FEAT_GUI_MSWIN + /* + * return non-zero if a character is available + */ + int + mch_char_avail(void) + { ++# ifdef VIMDLL ++ if (gui.in_use) ++ return TRUE; ++# endif + return WaitForChar(0L, FALSE); + } + + # if defined(FEAT_TERMINAL) || defined(PROTO) + /* + * Check for any pending input or messages. + */ + int + mch_check_messages(void) + { ++# ifdef VIMDLL ++ if (gui.in_use) ++ return TRUE; ++# endif + return WaitForChar(0L, TRUE); + } + # endif +-#endif + + /* + * Create the console input. Used when reading stdin doesn't work. + */ + static void + create_conin(void) + { + g_hConIn = CreateFile("CONIN$", GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + (LPSECURITY_ATTRIBUTES) NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + did_create_conin = TRUE; + } + + /* + * Get a keystroke or a mouse event, use a blocking wait. + */ + static WCHAR + tgetch(int *pmodifiers, WCHAR *pch2) + { + WCHAR ch; + + for (;;) + { + INPUT_RECORD ir; + DWORD cRecords = 0; + + #ifdef FEAT_CLIENTSERVER + (void)WaitForChar(-1L, FALSE); + if (input_available()) + return 0; + # ifdef FEAT_MOUSE + if (g_nMouseClick != -1) + return 0; + # endif + #endif + if (read_console_input(g_hConIn, &ir, 1, &cRecords) == 0) + { + if (did_create_conin) + read_error_exit(); + create_conin(); + continue; + } + + if (ir.EventType == KEY_EVENT) + { + if (decode_key_event(&ir.Event.KeyEvent, &ch, pch2, + pmodifiers, TRUE)) + return ch; + } + else if (ir.EventType == FOCUS_EVENT) + handle_focus_event(ir); + else if (ir.EventType == WINDOW_BUFFER_SIZE_EVENT) + shell_resized(); + #ifdef FEAT_MOUSE + else if (ir.EventType == MOUSE_EVENT) + { + if (decode_mouse_event(&ir.Event.MouseEvent)) + return 0; + } + #endif + } + } + #endif /* !FEAT_GUI_MSWIN */ + + + /* + * mch_inchar(): low-level input function. + * Get one or more characters from the keyboard or the mouse. + * If time == 0, do not wait for characters. + * If time == n, wait a short time for characters. + * If time == -1, wait forever for characters. + * Returns the number of characters read into buf. + */ + int + mch_inchar( + char_u *buf UNUSED, + int maxlen UNUSED, + long time UNUSED, + int tb_change_cnt UNUSED) + { +-#ifndef FEAT_GUI_MSWIN /* this isn't used for the GUI */ ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + + int len; + int c; + #define TYPEAHEADLEN 20 + static char_u typeahead[TYPEAHEADLEN]; /* previously typed bytes. */ + static int typeaheadlen = 0; + ++# ifdef VIMDLL ++ if (gui.in_use) ++ return 0; ++# endif ++ + /* First use any typeahead that was kept because "buf" was too small. */ + if (typeaheadlen > 0) + goto theend; + + if (time >= 0) + { + if (!WaitForChar(time, FALSE)) /* no character available */ + return 0; + } + else /* time == -1, wait forever */ + { + mch_set_winsize_now(); /* Allow winsize changes from now on */ + + /* + * If there is no character available within 2 seconds (default) + * write the autoscript file to disk. Or cause the CursorHold event + * to be triggered. + */ + if (!WaitForChar(p_ut, FALSE)) + { + if (trigger_cursorhold() && maxlen >= 3) + { + buf[0] = K_SPECIAL; + buf[1] = KS_EXTRA; + buf[2] = (int)KE_CURSORHOLD; + return 3; + } + before_blocking(); + } + } + + /* + * Try to read as many characters as there are, until the buffer is full. + */ + + /* we will get at least one key. Get more if they are available. */ + g_fCBrkPressed = FALSE; + + #ifdef MCH_WRITE_DUMP + if (fdDump) + fputc('[', fdDump); + #endif + + /* Keep looping until there is something in the typeahead buffer and more + * to get and still room in the buffer (up to two bytes for a char and + * three bytes for a modifier). */ + while ((typeaheadlen == 0 || WaitForChar(0L, FALSE)) + && typeaheadlen + 5 <= TYPEAHEADLEN) + { + if (typebuf_changed(tb_change_cnt)) + { + /* "buf" may be invalid now if a client put something in the + * typeahead buffer and "buf" is in the typeahead buffer. */ + typeaheadlen = 0; + break; + } + #ifdef FEAT_MOUSE + if (g_nMouseClick != -1) + { + # ifdef MCH_WRITE_DUMP + if (fdDump) + fprintf(fdDump, "{%02x @ %d, %d}", + g_nMouseClick, g_xMouse, g_yMouse); + # endif + typeahead[typeaheadlen++] = ESC + 128; + typeahead[typeaheadlen++] = 'M'; + typeahead[typeaheadlen++] = g_nMouseClick; + typeahead[typeaheadlen++] = g_xMouse + '!'; + typeahead[typeaheadlen++] = g_yMouse + '!'; + g_nMouseClick = -1; + } + else + #endif + { + WCHAR ch2 = NUL; + int modifiers = 0; + + c = tgetch(&modifiers, &ch2); + + if (typebuf_changed(tb_change_cnt)) + { + /* "buf" may be invalid now if a client put something in the + * typeahead buffer and "buf" is in the typeahead buffer. */ + typeaheadlen = 0; + break; + } + + if (c == Ctrl_C && ctrl_c_interrupts) + { + #if defined(FEAT_CLIENTSERVER) + trash_input_buf(); + #endif + got_int = TRUE; + } + + #ifdef FEAT_MOUSE + if (g_nMouseClick == -1) + #endif + { + int n = 1; + + if (ch2 == NUL) + { + int i; + char_u *p; + WCHAR ch[2]; + + ch[0] = c; + if (c >= 0xD800 && c <= 0xDBFF) /* High surrogate */ + { + ch[1] = tgetch(&modifiers, &ch2); + n++; + } + p = utf16_to_enc(ch, &n); + if (p != NULL) + { + for (i = 0; i < n; i++) + typeahead[typeaheadlen + i] = p[i]; + vim_free(p); + } + } + else + typeahead[typeaheadlen] = c; + if (ch2 != NUL) + { + if (c == K_NUL) + { + switch (ch2) + { + case (WCHAR)'\324': // SHIFT+Insert + case (WCHAR)'\325': // CTRL+Insert + case (WCHAR)'\327': // SHIFT+Delete + case (WCHAR)'\330': // CTRL+Delete + typeahead[typeaheadlen + n] = (char_u)ch2; + n++; + break; + + default: + typeahead[typeaheadlen + n] = 3; + typeahead[typeaheadlen + n + 1] = (char_u)ch2; + n += 2; + break; + } + } + else + { + typeahead[typeaheadlen + n] = 3; + typeahead[typeaheadlen + n + 1] = (char_u)ch2; + n += 2; + } + } + + /* Use the ALT key to set the 8th bit of the character + * when it's one byte, the 8th bit isn't set yet and not + * using a double-byte encoding (would become a lead + * byte). */ + if ((modifiers & MOD_MASK_ALT) + && n == 1 + && (typeahead[typeaheadlen] & 0x80) == 0 + && !enc_dbcs + ) + { + n = (*mb_char2bytes)(typeahead[typeaheadlen] | 0x80, + typeahead + typeaheadlen); + modifiers &= ~MOD_MASK_ALT; + } + + if (modifiers != 0) + { + /* Prepend modifiers to the character. */ + mch_memmove(typeahead + typeaheadlen + 3, + typeahead + typeaheadlen, n); + typeahead[typeaheadlen++] = K_SPECIAL; + typeahead[typeaheadlen++] = (char_u)KS_MODIFIER; + typeahead[typeaheadlen++] = modifiers; + } + + typeaheadlen += n; + + #ifdef MCH_WRITE_DUMP + if (fdDump) + fputc(c, fdDump); + #endif + } + } + } + + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fputs("]\n", fdDump); + fflush(fdDump); + } + #endif + + theend: + /* Move typeahead to "buf", as much as fits. */ + len = 0; + while (len < maxlen && typeaheadlen > 0) + { + buf[len++] = typeahead[0]; + mch_memmove(typeahead, typeahead + 1, --typeaheadlen); + } + return len; + + #else /* FEAT_GUI_MSWIN */ + return 0; + #endif /* FEAT_GUI_MSWIN */ + } + + #ifndef PROTO + # ifndef __MINGW32__ + # include /* required for FindExecutable() */ + # endif + #endif + + /* + * If "use_path" is TRUE: Return TRUE if "name" is in $PATH. + * If "use_path" is FALSE: Return TRUE if "name" exists. + * When returning TRUE and "path" is not NULL save the path and set "*path" to + * the allocated memory. + * TODO: Should somehow check if it's really executable. + */ + static int + executable_exists(char *name, char_u **path, int use_path) + { + WCHAR *p; + WCHAR fnamew[_MAX_PATH]; + WCHAR *dumw; + WCHAR *wcurpath, *wnewpath; + long n; + + if (!use_path) + { + if (mch_getperm((char_u *)name) != -1 && !mch_isdir((char_u *)name)) + { + if (path != NULL) + { + if (mch_isFullName((char_u *)name)) + *path = vim_strsave((char_u *)name); + else + *path = FullName_save((char_u *)name, FALSE); + } + return TRUE; + } + return FALSE; + } + + p = enc_to_utf16((char_u *)name, NULL); + if (p == NULL) + return FALSE; + + wcurpath = _wgetenv(L"PATH"); + wnewpath = (WCHAR*)alloc((unsigned)(wcslen(wcurpath) + 3) + * sizeof(WCHAR)); + if (wnewpath == NULL) + return FALSE; + wcscpy(wnewpath, L".;"); + wcscat(wnewpath, wcurpath); + n = (long)SearchPathW(wnewpath, p, NULL, _MAX_PATH, fnamew, &dumw); + vim_free(wnewpath); + vim_free(p); + if (n == 0) + return FALSE; + if (GetFileAttributesW(fnamew) & FILE_ATTRIBUTE_DIRECTORY) + return FALSE; + if (path != NULL) + *path = utf16_to_enc(fnamew, NULL); + return TRUE; + } + + #if (defined(__MINGW32__) && __MSVCRT_VERSION__ >= 0x800) || \ + (defined(_MSC_VER) && _MSC_VER >= 1400) + /* + * Bad parameter handler. + * + * Certain MS CRT functions will intentionally crash when passed invalid + * parameters to highlight possible security holes. Setting this function as + * the bad parameter handler will prevent the crash. + * + * In debug builds the parameters contain CRT information that might help track + * down the source of a problem, but in non-debug builds the arguments are all + * NULL/0. Debug builds will also produce assert dialogs from the CRT, it is + * worth allowing these to make debugging of issues easier. + */ + static void + bad_param_handler(const wchar_t *expression, + const wchar_t *function, + const wchar_t *file, + unsigned int line, + uintptr_t pReserved) + { + } + + # define SET_INVALID_PARAM_HANDLER \ + ((void)_set_invalid_parameter_handler(bad_param_handler)) + #else + # define SET_INVALID_PARAM_HANDLER + #endif + + #ifdef FEAT_GUI_MSWIN + + /* + * GUI version of mch_init(). + */ +- void +-mch_init(void) ++ static void ++mch_init_g(void) + { + #ifndef __MINGW32__ + extern int _fmode; + #endif + + /* Silently handle invalid parameters to CRT functions */ + SET_INVALID_PARAM_HANDLER; + + /* Let critical errors result in a failure, not in a dialog box. Required + * for the timestamp test to work on removed floppies. */ + SetErrorMode(SEM_FAILCRITICALERRORS); + + _fmode = O_BINARY; /* we do our own CR-LF translation */ + + /* Specify window size. Is there a place to get the default from? */ + Rows = 25; + Columns = 80; + + /* Look for 'vimrun' */ + { + char_u vimrun_location[_MAX_PATH + 4]; + + /* First try in same directory as gvim.exe */ + STRCPY(vimrun_location, exe_name); + STRCPY(gettail(vimrun_location), "vimrun.exe"); + if (mch_getperm(vimrun_location) >= 0) + { + if (*skiptowhite(vimrun_location) != NUL) + { + /* Enclose path with white space in double quotes. */ + mch_memmove(vimrun_location + 1, vimrun_location, + STRLEN(vimrun_location) + 1); + *vimrun_location = '"'; + STRCPY(gettail(vimrun_location), "vimrun\" "); + } + else + STRCPY(gettail(vimrun_location), "vimrun "); + + vimrun_path = (char *)vim_strsave(vimrun_location); + s_dont_use_vimrun = FALSE; + } + else if (executable_exists("vimrun.exe", NULL, TRUE)) + s_dont_use_vimrun = FALSE; + + /* Don't give the warning for a missing vimrun.exe right now, but only + * when vimrun was supposed to be used. Don't bother people that do + * not need vimrun.exe. */ + if (s_dont_use_vimrun) + need_vimrun_warning = TRUE; + } + + /* + * If "finstr.exe" doesn't exist, use "grep -n" for 'grepprg'. + * Otherwise the default "findstr /n" is used. + */ + if (!executable_exists("findstr.exe", NULL, TRUE)) + set_option_value((char_u *)"grepprg", 0, (char_u *)"grep -n", 0); + + #ifdef FEAT_CLIPBOARD + win_clip_init(); + #endif + + vtp_flag_init(); + } + + +-#else /* FEAT_GUI_MSWIN */ ++#endif /* FEAT_GUI_MSWIN */ ++ ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + + #define SRWIDTH(sr) ((sr).Right - (sr).Left + 1) + #define SRHEIGHT(sr) ((sr).Bottom - (sr).Top + 1) + + /* + * ClearConsoleBuffer() + * Description: + * Clears the entire contents of the console screen buffer, using the + * specified attribute. + * Returns: + * TRUE on success + */ + static BOOL + ClearConsoleBuffer(WORD wAttribute) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; + COORD coord; + DWORD NumCells, dummy; + + if (!GetConsoleScreenBufferInfo(g_hConOut, &csbi)) + return FALSE; + + NumCells = csbi.dwSize.X * csbi.dwSize.Y; + coord.X = 0; + coord.Y = 0; + if (!FillConsoleOutputCharacter(g_hConOut, ' ', NumCells, + coord, &dummy)) + return FALSE; + if (!FillConsoleOutputAttribute(g_hConOut, wAttribute, NumCells, + coord, &dummy)) + return FALSE; + + return TRUE; + } + + /* + * FitConsoleWindow() + * Description: + * Checks if the console window will fit within given buffer dimensions. + * Also, if requested, will shrink the window to fit. + * Returns: + * TRUE on success + */ + static BOOL + FitConsoleWindow( + COORD dwBufferSize, + BOOL WantAdjust) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; + COORD dwWindowSize; + BOOL NeedAdjust = FALSE; + + if (GetConsoleScreenBufferInfo(g_hConOut, &csbi)) + { + /* + * A buffer resize will fail if the current console window does + * not lie completely within that buffer. To avoid this, we might + * have to move and possibly shrink the window. + */ + if (csbi.srWindow.Right >= dwBufferSize.X) + { + dwWindowSize.X = SRWIDTH(csbi.srWindow); + if (dwWindowSize.X > dwBufferSize.X) + dwWindowSize.X = dwBufferSize.X; + csbi.srWindow.Right = dwBufferSize.X - 1; + csbi.srWindow.Left = dwBufferSize.X - dwWindowSize.X; + NeedAdjust = TRUE; + } + if (csbi.srWindow.Bottom >= dwBufferSize.Y) + { + dwWindowSize.Y = SRHEIGHT(csbi.srWindow); + if (dwWindowSize.Y > dwBufferSize.Y) + dwWindowSize.Y = dwBufferSize.Y; + csbi.srWindow.Bottom = dwBufferSize.Y - 1; + csbi.srWindow.Top = dwBufferSize.Y - dwWindowSize.Y; + NeedAdjust = TRUE; + } + if (NeedAdjust && WantAdjust) + { + if (!SetConsoleWindowInfo(g_hConOut, TRUE, &csbi.srWindow)) + return FALSE; + } + return TRUE; + } + + return FALSE; + } + + typedef struct ConsoleBufferStruct + { + BOOL IsValid; + CONSOLE_SCREEN_BUFFER_INFO Info; + PCHAR_INFO Buffer; + COORD BufferSize; + PSMALL_RECT Regions; + int NumRegions; + } ConsoleBuffer; + + /* + * SaveConsoleBuffer() + * Description: + * Saves important information about the console buffer, including the + * actual buffer contents. The saved information is suitable for later + * restoration by RestoreConsoleBuffer(). + * Returns: + * TRUE if all information was saved; FALSE otherwise + * If FALSE, still sets cb->IsValid if buffer characteristics were saved. + */ + static BOOL + SaveConsoleBuffer( + ConsoleBuffer *cb) + { + DWORD NumCells; + COORD BufferCoord; + SMALL_RECT ReadRegion; + WORD Y, Y_incr; + int i, numregions; + + if (cb == NULL) + return FALSE; + + if (!GetConsoleScreenBufferInfo(g_hConOut, &cb->Info)) + { + cb->IsValid = FALSE; + return FALSE; + } + cb->IsValid = TRUE; + + /* + * Allocate a buffer large enough to hold the entire console screen + * buffer. If this ConsoleBuffer structure has already been initialized + * with a buffer of the correct size, then just use that one. + */ + if (!cb->IsValid || cb->Buffer == NULL || + cb->BufferSize.X != cb->Info.dwSize.X || + cb->BufferSize.Y != cb->Info.dwSize.Y) + { + cb->BufferSize.X = cb->Info.dwSize.X; + cb->BufferSize.Y = cb->Info.dwSize.Y; + NumCells = cb->BufferSize.X * cb->BufferSize.Y; + vim_free(cb->Buffer); + cb->Buffer = (PCHAR_INFO)alloc(NumCells * sizeof(CHAR_INFO)); + if (cb->Buffer == NULL) + return FALSE; + } + + /* + * We will now copy the console screen buffer into our buffer. + * ReadConsoleOutput() seems to be limited as far as how much you + * can read at a time. Empirically, this number seems to be about + * 12000 cells (rows * columns). Start at position (0, 0) and copy + * in chunks until it is all copied. The chunks will all have the + * same horizontal characteristics, so initialize them now. The + * height of each chunk will be (12000 / width). + */ + BufferCoord.X = 0; + ReadRegion.Left = 0; + ReadRegion.Right = cb->Info.dwSize.X - 1; + Y_incr = 12000 / cb->Info.dwSize.X; + + numregions = (cb->Info.dwSize.Y + Y_incr - 1) / Y_incr; + if (cb->Regions == NULL || numregions != cb->NumRegions) + { + cb->NumRegions = numregions; + vim_free(cb->Regions); + cb->Regions = (PSMALL_RECT)alloc(cb->NumRegions * sizeof(SMALL_RECT)); + if (cb->Regions == NULL) + { + VIM_CLEAR(cb->Buffer); + return FALSE; + } + } + + for (i = 0, Y = 0; i < cb->NumRegions; i++, Y += Y_incr) + { + /* + * Read into position (0, Y) in our buffer. + */ + BufferCoord.Y = Y; + /* + * Read the region whose top left corner is (0, Y) and whose bottom + * right corner is (width - 1, Y + Y_incr - 1). This should define + * a region of size width by Y_incr. Don't worry if this region is + * too large for the remaining buffer; it will be cropped. + */ + ReadRegion.Top = Y; + ReadRegion.Bottom = Y + Y_incr - 1; + if (!ReadConsoleOutputW(g_hConOut, /* output handle */ + cb->Buffer, /* our buffer */ + cb->BufferSize, /* dimensions of our buffer */ + BufferCoord, /* offset in our buffer */ + &ReadRegion)) /* region to save */ + { + VIM_CLEAR(cb->Buffer); + VIM_CLEAR(cb->Regions); + return FALSE; + } + cb->Regions[i] = ReadRegion; + } + + return TRUE; + } + + /* + * RestoreConsoleBuffer() + * Description: + * Restores important information about the console buffer, including the + * actual buffer contents, if desired. The information to restore is in + * the same format used by SaveConsoleBuffer(). + * Returns: + * TRUE on success + */ + static BOOL + RestoreConsoleBuffer( + ConsoleBuffer *cb, + BOOL RestoreScreen) + { + COORD BufferCoord; + SMALL_RECT WriteRegion; + int i; + + if (cb == NULL || !cb->IsValid) + return FALSE; + + /* + * Before restoring the buffer contents, clear the current buffer, and + * restore the cursor position and window information. Doing this now + * prevents old buffer contents from "flashing" onto the screen. + */ + if (RestoreScreen) + ClearConsoleBuffer(cb->Info.wAttributes); + + FitConsoleWindow(cb->Info.dwSize, TRUE); + if (!SetConsoleScreenBufferSize(g_hConOut, cb->Info.dwSize)) + return FALSE; + if (!SetConsoleTextAttribute(g_hConOut, cb->Info.wAttributes)) + return FALSE; + + if (!RestoreScreen) + { + /* + * No need to restore the screen buffer contents, so we're done. + */ + return TRUE; + } + + if (!SetConsoleCursorPosition(g_hConOut, cb->Info.dwCursorPosition)) + return FALSE; + if (!SetConsoleWindowInfo(g_hConOut, TRUE, &cb->Info.srWindow)) + return FALSE; + + /* + * Restore the screen buffer contents. + */ + if (cb->Buffer != NULL) + { + for (i = 0; i < cb->NumRegions; i++) + { + BufferCoord.X = cb->Regions[i].Left; + BufferCoord.Y = cb->Regions[i].Top; + WriteRegion = cb->Regions[i]; + if (!WriteConsoleOutputW(g_hConOut, /* output handle */ + cb->Buffer, /* our buffer */ + cb->BufferSize, /* dimensions of our buffer */ + BufferCoord, /* offset in our buffer */ + &WriteRegion)) /* region to restore */ + return FALSE; + } + } + + return TRUE; + } + + #define FEAT_RESTORE_ORIG_SCREEN + #ifdef FEAT_RESTORE_ORIG_SCREEN + static ConsoleBuffer g_cbOrig = { 0 }; + #endif + static ConsoleBuffer g_cbNonTermcap = { 0 }; + static ConsoleBuffer g_cbTermcap = { 0 }; + + #ifdef FEAT_TITLE +-#ifdef __BORLANDC__ +-typedef HWND (__stdcall *GETCONSOLEWINDOWPROC)(VOID); +-#else +-typedef HWND (WINAPI *GETCONSOLEWINDOWPROC)(VOID); +-#endif + char g_szOrigTitle[256] = { 0 }; + HWND g_hWnd = NULL; /* also used in os_mswin.c */ + static HICON g_hOrigIconSmall = NULL; + static HICON g_hOrigIcon = NULL; + static HICON g_hVimIcon = NULL; + static BOOL g_fCanChangeIcon = FALSE; + + /* ICON* are not defined in VC++ 4.0 */ + #ifndef ICON_SMALL + #define ICON_SMALL 0 + #endif + #ifndef ICON_BIG + #define ICON_BIG 1 + #endif + /* + * GetConsoleIcon() + * Description: + * Attempts to retrieve the small icon and/or the big icon currently in + * use by a given window. + * Returns: + * TRUE on success + */ + static BOOL + GetConsoleIcon( + HWND hWnd, + HICON *phIconSmall, + HICON *phIcon) + { + if (hWnd == NULL) + return FALSE; + + if (phIconSmall != NULL) + *phIconSmall = (HICON)SendMessage(hWnd, WM_GETICON, + (WPARAM)ICON_SMALL, (LPARAM)0); + if (phIcon != NULL) + *phIcon = (HICON)SendMessage(hWnd, WM_GETICON, + (WPARAM)ICON_BIG, (LPARAM)0); + return TRUE; + } + + /* + * SetConsoleIcon() + * Description: + * Attempts to change the small icon and/or the big icon currently in + * use by a given window. + * Returns: + * TRUE on success + */ + static BOOL + SetConsoleIcon( + HWND hWnd, + HICON hIconSmall, + HICON hIcon) + { + if (hWnd == NULL) + return FALSE; + + if (hIconSmall != NULL) + SendMessage(hWnd, WM_SETICON, + (WPARAM)ICON_SMALL, (LPARAM)hIconSmall); + if (hIcon != NULL) + SendMessage(hWnd, WM_SETICON, + (WPARAM)ICON_BIG, (LPARAM) hIcon); + return TRUE; + } + + /* + * SaveConsoleTitleAndIcon() + * Description: + * Saves the current console window title in g_szOrigTitle, for later + * restoration. Also, attempts to obtain a handle to the console window, + * and use it to save the small and big icons currently in use by the + * console window. This is not always possible on some versions of Windows; + * nor is it possible when running Vim remotely using Telnet (since the + * console window the user sees is owned by a remote process). + */ + static void + SaveConsoleTitleAndIcon(void) + { + /* Save the original title. */ + if (!GetConsoleTitle(g_szOrigTitle, sizeof(g_szOrigTitle))) + return; + + /* + * Obtain a handle to the console window using GetConsoleWindow() from + * KERNEL32.DLL; we need to handle in order to change the window icon. + * This function only exists on NT-based Windows, starting with Windows + * 2000. On older operating systems, we can't change the window icon + * anyway. + */ + g_hWnd = GetConsoleWindow(); + if (g_hWnd == NULL) + return; + + /* Save the original console window icon. */ + GetConsoleIcon(g_hWnd, &g_hOrigIconSmall, &g_hOrigIcon); + if (g_hOrigIconSmall == NULL || g_hOrigIcon == NULL) + return; + + /* Extract the first icon contained in the Vim executable. */ + if (mch_icon_load((HANDLE *)&g_hVimIcon) == FAIL || g_hVimIcon == NULL) + g_hVimIcon = ExtractIcon(NULL, (LPCSTR)exe_name, 0); + if (g_hVimIcon != NULL) + g_fCanChangeIcon = TRUE; + } + #endif + + static int g_fWindInitCalled = FALSE; + static int g_fTermcapMode = FALSE; + static CONSOLE_CURSOR_INFO g_cci; + static DWORD g_cmodein = 0; + static DWORD g_cmodeout = 0; + + /* + * non-GUI version of mch_init(). + */ +- void +-mch_init(void) ++ static void ++mch_init_c(void) + { + #ifndef FEAT_RESTORE_ORIG_SCREEN + CONSOLE_SCREEN_BUFFER_INFO csbi; + #endif + #ifndef __MINGW32__ + extern int _fmode; + #endif + + /* Silently handle invalid parameters to CRT functions */ + SET_INVALID_PARAM_HANDLER; + + /* Let critical errors result in a failure, not in a dialog box. Required + * for the timestamp test to work on removed floppies. */ + SetErrorMode(SEM_FAILCRITICALERRORS); + + _fmode = O_BINARY; /* we do our own CR-LF translation */ + out_flush(); + + /* Obtain handles for the standard Console I/O devices */ + if (read_cmd_fd == 0) + g_hConIn = GetStdHandle(STD_INPUT_HANDLE); + else + create_conin(); + g_hConOut = GetStdHandle(STD_OUTPUT_HANDLE); + + #ifdef FEAT_RESTORE_ORIG_SCREEN + /* Save the initial console buffer for later restoration */ + SaveConsoleBuffer(&g_cbOrig); + g_attrCurrent = g_attrDefault = g_cbOrig.Info.wAttributes; + #else + /* Get current text attributes */ + GetConsoleScreenBufferInfo(g_hConOut, &csbi); + g_attrCurrent = g_attrDefault = csbi.wAttributes; + #endif + if (cterm_normal_fg_color == 0) + cterm_normal_fg_color = (g_attrCurrent & 0xf) + 1; + if (cterm_normal_bg_color == 0) + cterm_normal_bg_color = ((g_attrCurrent >> 4) & 0xf) + 1; + + // Fg and Bg color index number at startup + g_color_index_fg = g_attrDefault & 0xf; + g_color_index_bg = (g_attrDefault >> 4) & 0xf; + + /* set termcap codes to current text attributes */ + update_tcap(g_attrCurrent); + + GetConsoleCursorInfo(g_hConOut, &g_cci); + GetConsoleMode(g_hConIn, &g_cmodein); + GetConsoleMode(g_hConOut, &g_cmodeout); + + #ifdef FEAT_TITLE + SaveConsoleTitleAndIcon(); + /* + * Set both the small and big icons of the console window to Vim's icon. + * Note that Vim presently only has one size of icon (32x32), but it + * automatically gets scaled down to 16x16 when setting the small icon. + */ + if (g_fCanChangeIcon) + SetConsoleIcon(g_hWnd, g_hVimIcon, g_hVimIcon); + #endif + + ui_get_shellsize(); + + #ifdef MCH_WRITE_DUMP + fdDump = fopen("dump", "wt"); + + if (fdDump) + { + time_t t; + + time(&t); + fputs(ctime(&t), fdDump); + fflush(fdDump); + } + #endif + + g_fWindInitCalled = TRUE; + + #ifdef FEAT_MOUSE + g_fMouseAvail = GetSystemMetrics(SM_MOUSEPRESENT); + #endif + + #ifdef FEAT_CLIPBOARD + win_clip_init(); + #endif + + vtp_flag_init(); + vtp_init(); + } + + /* + * non-GUI version of mch_exit(). + * Shut down and exit with status `r' + * Careful: mch_exit() may be called before mch_init()! + */ +- void +-mch_exit(int r) ++ static void ++mch_exit_c(int r) + { + exiting = TRUE; + + vtp_exit(); + + stoptermcap(); + if (g_fWindInitCalled) + settmode(TMODE_COOK); + + ml_close_all(TRUE); /* remove all memfiles */ + + if (g_fWindInitCalled) + { + #ifdef FEAT_TITLE + mch_restore_title(SAVE_RESTORE_BOTH); + /* + * Restore both the small and big icons of the console window to + * what they were at startup. Don't do this when the window is + * closed, Vim would hang here. + */ + if (g_fCanChangeIcon && !g_fForceExit) + SetConsoleIcon(g_hWnd, g_hOrigIconSmall, g_hOrigIcon); + #endif + + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + time_t t; + + time(&t); + fputs(ctime(&t), fdDump); + fclose(fdDump); + } + fdDump = NULL; + #endif + } + + SetConsoleCursorInfo(g_hConOut, &g_cci); + SetConsoleMode(g_hConIn, g_cmodein); + SetConsoleMode(g_hConOut, g_cmodeout); + + #ifdef DYNAMIC_GETTEXT + dyn_libintl_end(); + #endif + + exit(r); + } + #endif /* !FEAT_GUI_MSWIN */ + ++ void ++mch_init(void) ++{ ++#ifdef VIMDLL ++ if (gui.starting) ++ mch_init_g(); ++ else ++ mch_init_c(); ++#elif defined(FEAT_GUI_MSWIN) ++ mch_init_g(); ++#else ++ mch_init_c(); ++#endif ++} ++ ++ void ++mch_exit(int r) ++{ ++#ifdef VIMDLL ++ if (gui.starting || gui.in_use) ++ mch_exit_g(r); ++ else ++ mch_exit_c(r); ++#elif defined(FEAT_GUI_MSWIN) ++ mch_exit_g(r); ++#else ++ mch_exit_c(r); ++#endif ++} ++ + /* + * Do we have an interactive window? + */ + int + mch_check_win( + int argc UNUSED, + char **argv UNUSED) + { + get_exe_name(); + +-#ifdef FEAT_GUI_MSWIN ++#if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL) + return OK; /* GUI always has a tty */ + #else ++# ifdef VIMDLL ++ if (gui.in_use) ++ return OK; ++# endif + if (isatty(1)) + return OK; + return FAIL; + #endif + } + + /* + * Set the case of the file name, if it already exists. + * When "len" is > 0, also expand short to long filenames. + */ + void + fname_case( + char_u *name, + int len) + { + int flen; + WCHAR *p; + WCHAR buf[_MAX_PATH + 1]; + + flen = (int)STRLEN(name); + if (flen == 0) + return; + + slash_adjust(name); + + p = enc_to_utf16(name, NULL); + if (p == NULL) + return; + + if (GetLongPathNameW(p, buf, _MAX_PATH)) + { + char_u *q = utf16_to_enc(buf, NULL); + + if (q != NULL) + { + if (len > 0 || flen >= (int)STRLEN(q)) + vim_strncpy(name, q, (len > 0) ? len - 1 : flen); + vim_free(q); + } + } + vim_free(p); + } + + + /* + * Insert user name in s[len]. + */ + int + mch_get_user_name( + char_u *s, + int len) + { + WCHAR wszUserName[256 + 1]; /* UNLEN is 256 */ + DWORD wcch = sizeof(wszUserName) / sizeof(WCHAR); + + if (GetUserNameW(wszUserName, &wcch)) + { + char_u *p = utf16_to_enc(wszUserName, NULL); + + if (p != NULL) + { + vim_strncpy(s, p, len - 1); + vim_free(p); + return OK; + } + } + s[0] = NUL; + return FAIL; + } + + + /* + * Insert host name in s[len]. + */ + void + mch_get_host_name( + char_u *s, + int len) + { + WCHAR wszHostName[256 + 1]; + DWORD wcch = sizeof(wszHostName) / sizeof(WCHAR); + + if (GetComputerNameW(wszHostName, &wcch)) + { + char_u *p = utf16_to_enc(wszHostName, NULL); + + if (p != NULL) + { + vim_strncpy(s, p, len - 1); + vim_free(p); + return; + } + } + } + + + /* + * return process ID + */ + long + mch_get_pid(void) + { + return (long)GetCurrentProcessId(); + } + + + /* + * Get name of current directory into buffer 'buf' of length 'len' bytes. + * Return OK for success, FAIL for failure. + */ + int + mch_dirname( + char_u *buf, + int len) + { + WCHAR wbuf[_MAX_PATH + 1]; + + /* + * Originally this was: + * return (getcwd(buf, len) != NULL ? OK : FAIL); + * But the Win32s known bug list says that getcwd() doesn't work + * so use the Win32 system call instead. + */ + if (GetCurrentDirectoryW(_MAX_PATH, wbuf) != 0) + { + WCHAR wcbuf[_MAX_PATH + 1]; + char_u *p = NULL; + + if (GetLongPathNameW(wbuf, wcbuf, _MAX_PATH) != 0) + { + p = utf16_to_enc(wcbuf, NULL); + if (STRLEN(p) >= (size_t)len) + { + // long path name is too long, fall back to short one + vim_free(p); + p = NULL; + } + } + if (p == NULL) + p = utf16_to_enc(wbuf, NULL); + + if (p != NULL) + { + vim_strncpy(buf, p, len - 1); + vim_free(p); + return OK; + } + } + return FAIL; + } + + /* + * Get file permissions for "name". + * Return mode_t or -1 for error. + */ + long + mch_getperm(char_u *name) + { + stat_T st; + int n; + + n = mch_stat((char *)name, &st); + return n == 0 ? (long)(unsigned short)st.st_mode : -1L; + } + + + /* + * Set file permission for "name" to "perm". + * + * Return FAIL for failure, OK otherwise. + */ + int + mch_setperm(char_u *name, long perm) + { + long n; + WCHAR *p; + + p = enc_to_utf16(name, NULL); + if (p == NULL) + return FAIL; + + n = _wchmod(p, perm); + vim_free(p); + if (n == -1) + return FAIL; + + win32_set_archive(name); + + return OK; + } + + /* + * Set hidden flag for "name". + */ + void + mch_hide(char_u *name) + { + int attrs = win32_getattrs(name); + if (attrs == -1) + return; + + attrs |= FILE_ATTRIBUTE_HIDDEN; + win32_setattrs(name, attrs); + } + + /* + * Return TRUE if file "name" exists and is hidden. + */ + int + mch_ishidden(char_u *name) + { + int f = win32_getattrs(name); + + if (f == -1) + return FALSE; /* file does not exist at all */ + + return (f & FILE_ATTRIBUTE_HIDDEN) != 0; + } + + /* + * return TRUE if "name" is a directory + * return FALSE if "name" is not a directory or upon error + */ + int + mch_isdir(char_u *name) + { + int f = win32_getattrs(name); + + if (f == -1) + return FALSE; /* file does not exist at all */ + + return (f & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + + /* + * return TRUE if "name" is a directory, NOT a symlink to a directory + * return FALSE if "name" is not a directory + * return FALSE for error + */ + int + mch_isrealdir(char_u *name) + { + return mch_isdir(name) && !mch_is_symbolic_link(name); + } + + /* + * Create directory "name". + * Return 0 on success, -1 on error. + */ + int + mch_mkdir(char_u *name) + { + WCHAR *p; + int retval; + + p = enc_to_utf16(name, NULL); + if (p == NULL) + return -1; + retval = _wmkdir(p); + vim_free(p); + return retval; + } + + /* + * Delete directory "name". + * Return 0 on success, -1 on error. + */ + int + mch_rmdir(char_u *name) + { + WCHAR *p; + int retval; + + p = enc_to_utf16(name, NULL); + if (p == NULL) + return -1; + retval = _wrmdir(p); + vim_free(p); + return retval; + } + + /* + * Return TRUE if file "fname" has more than one link. + */ + int + mch_is_hard_link(char_u *fname) + { + BY_HANDLE_FILE_INFORMATION info; + + return win32_fileinfo(fname, &info) == FILEINFO_OK + && info.nNumberOfLinks > 1; + } + + /* + * Return TRUE if "name" is a symbolic link (or a junction). + */ + int + mch_is_symbolic_link(char_u *name) + { + HANDLE hFind; + int res = FALSE; + DWORD fileFlags = 0, reparseTag = 0; + WCHAR *wn; + WIN32_FIND_DATAW findDataW; + + wn = enc_to_utf16(name, NULL); + if (wn == NULL) + return FALSE; + + hFind = FindFirstFileW(wn, &findDataW); + vim_free(wn); + if (hFind != INVALID_HANDLE_VALUE) + { + fileFlags = findDataW.dwFileAttributes; + reparseTag = findDataW.dwReserved0; + FindClose(hFind); + } + + if ((fileFlags & FILE_ATTRIBUTE_REPARSE_POINT) + && (reparseTag == IO_REPARSE_TAG_SYMLINK + || reparseTag == IO_REPARSE_TAG_MOUNT_POINT)) + res = TRUE; + + return res; + } + + /* + * Return TRUE if file "fname" has more than one link or if it is a symbolic + * link. + */ + int + mch_is_linked(char_u *fname) + { + if (mch_is_hard_link(fname) || mch_is_symbolic_link(fname)) + return TRUE; + return FALSE; + } + + /* + * Get the by-handle-file-information for "fname". + * Returns FILEINFO_OK when OK. + * returns FILEINFO_ENC_FAIL when enc_to_utf16() failed. + * Returns FILEINFO_READ_FAIL when CreateFile() failed. + * Returns FILEINFO_INFO_FAIL when GetFileInformationByHandle() failed. + */ + int + win32_fileinfo(char_u *fname, BY_HANDLE_FILE_INFORMATION *info) + { + HANDLE hFile; + int res = FILEINFO_READ_FAIL; + WCHAR *wn; + + wn = enc_to_utf16(fname, NULL); + if (wn == NULL) + return FILEINFO_ENC_FAIL; + + hFile = CreateFileW(wn, // file name + GENERIC_READ, // access mode + FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode + NULL, // security descriptor + OPEN_EXISTING, // creation disposition + FILE_FLAG_BACKUP_SEMANTICS, // file attributes + NULL); // handle to template file + vim_free(wn); + + if (hFile != INVALID_HANDLE_VALUE) + { + if (GetFileInformationByHandle(hFile, info) != 0) + res = FILEINFO_OK; + else + res = FILEINFO_INFO_FAIL; + CloseHandle(hFile); + } + + return res; + } + + /* + * get file attributes for `name' + * -1 : error + * else FILE_ATTRIBUTE_* defined in winnt.h + */ + static int + win32_getattrs(char_u *name) + { + int attr; + WCHAR *p; + + p = enc_to_utf16(name, NULL); + if (p == NULL) + return INVALID_FILE_ATTRIBUTES; + + attr = GetFileAttributesW(p); + vim_free(p); + + return attr; + } + + /* + * set file attributes for `name' to `attrs' + * + * return -1 for failure, 0 otherwise + */ + static int + win32_setattrs(char_u *name, int attrs) + { + int res; + WCHAR *p; + + p = enc_to_utf16(name, NULL); + if (p == NULL) + return -1; + + res = SetFileAttributesW(p, attrs); + vim_free(p); + + return res ? 0 : -1; + } + + /* + * Set archive flag for "name". + */ + static int + win32_set_archive(char_u *name) + { + int attrs = win32_getattrs(name); + if (attrs == -1) + return -1; + + attrs |= FILE_ATTRIBUTE_ARCHIVE; + return win32_setattrs(name, attrs); + } + + /* + * Return TRUE if file or directory "name" is writable (not readonly). + * Strange semantics of Win32: a readonly directory is writable, but you can't + * delete a file. Let's say this means it is writable. + */ + int + mch_writable(char_u *name) + { + int attrs = win32_getattrs(name); + + return (attrs != -1 && (!(attrs & FILE_ATTRIBUTE_READONLY) + || (attrs & FILE_ATTRIBUTE_DIRECTORY))); + } + + /* + * Return TRUE if "name" can be executed, FALSE if not. + * If "use_path" is FALSE only check if "name" is executable. + * When returning TRUE and "path" is not NULL save the path and set "*path" to + * the allocated memory. + */ + int + mch_can_exe(char_u *name, char_u **path, int use_path) + { + // WinNT and later can use _MAX_PATH wide characters for a pathname, which + // means that the maximum pathname is _MAX_PATH * 3 bytes when 'enc' is + // UTF-8. + char_u buf[_MAX_PATH * 3]; + int len = (int)STRLEN(name); + char_u *p, *saved; + + if (len >= sizeof(buf)) // safety check + return FALSE; + + // Try using the name directly when a Unix-shell like 'shell'. + if (strstr((char *)gettail(p_sh), "sh") != NULL) + if (executable_exists((char *)name, path, use_path)) + return TRUE; + + /* + * Loop over all extensions in $PATHEXT. + */ + p = mch_getenv("PATHEXT"); + if (p == NULL) + p = (char_u *)".com;.exe;.bat;.cmd"; + saved = vim_strsave(p); + if (saved == NULL) + return FALSE; + p = saved; + while (*p) + { + char_u *tmp = vim_strchr(p, ';'); + + if (tmp != NULL) + *tmp = NUL; + if (_stricoll((char *)name + len - STRLEN(p), (char *)p) == 0 + && executable_exists((char *)name, path, use_path)) + { + vim_free(saved); + return TRUE; + } + if (tmp == NULL) + break; + p = tmp + 1; + } + vim_free(saved); + + vim_strncpy(buf, name, sizeof(buf) - 1); + p = mch_getenv("PATHEXT"); + if (p == NULL) + p = (char_u *)".com;.exe;.bat;.cmd"; + while (*p) + { + if (p[0] == '.' && (p[1] == NUL || p[1] == ';')) + { + /* A single "." means no extension is added. */ + buf[len] = NUL; + ++p; + if (*p) + ++p; + } + else + copy_option_part(&p, buf + len, sizeof(buf) - len, ";"); + if (executable_exists((char *)buf, path, use_path)) + return TRUE; + } + return FALSE; + } + + /* + * Check what "name" is: + * NODE_NORMAL: file or directory (or doesn't exist) + * NODE_WRITABLE: writable device, socket, fifo, etc. + * NODE_OTHER: non-writable things + */ + int + mch_nodetype(char_u *name) + { + HANDLE hFile; + int type; + WCHAR *wn; + + /* We can't open a file with a name "\\.\con" or "\\.\prn" and trying to + * read from it later will cause Vim to hang. Thus return NODE_WRITABLE + * here. */ + if (STRNCMP(name, "\\\\.\\", 4) == 0) + return NODE_WRITABLE; + + wn = enc_to_utf16(name, NULL); + if (wn == NULL) + return NODE_NORMAL; + + hFile = CreateFileW(wn, // file name + GENERIC_WRITE, // access mode + 0, // share mode + NULL, // security descriptor + OPEN_EXISTING, // creation disposition + 0, // file attributes + NULL); // handle to template file + vim_free(wn); + if (hFile == INVALID_HANDLE_VALUE) + return NODE_NORMAL; + + type = GetFileType(hFile); + CloseHandle(hFile); + if (type == FILE_TYPE_CHAR) + return NODE_WRITABLE; + if (type == FILE_TYPE_DISK) + return NODE_NORMAL; + return NODE_OTHER; + } + + #ifdef HAVE_ACL + struct my_acl + { + PSECURITY_DESCRIPTOR pSecurityDescriptor; + PSID pSidOwner; + PSID pSidGroup; + PACL pDacl; + PACL pSacl; + }; + #endif + + /* + * Return a pointer to the ACL of file "fname" in allocated memory. + * Return NULL if the ACL is not available for whatever reason. + */ + vim_acl_T + mch_get_acl(char_u *fname) + { + #ifndef HAVE_ACL + return (vim_acl_T)NULL; + #else + struct my_acl *p = NULL; + DWORD err; + + p = (struct my_acl *)alloc_clear((unsigned)sizeof(struct my_acl)); + if (p != NULL) + { + WCHAR *wn; + + wn = enc_to_utf16(fname, NULL); + if (wn == NULL) + return NULL; + + // Try to retrieve the entire security descriptor. + err = GetNamedSecurityInfoW( + wn, // Abstract filename + SE_FILE_OBJECT, // File Object + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION | + SACL_SECURITY_INFORMATION, + &p->pSidOwner, // Ownership information. + &p->pSidGroup, // Group membership. + &p->pDacl, // Discretionary information. + &p->pSacl, // For auditing purposes. + &p->pSecurityDescriptor); + if (err == ERROR_ACCESS_DENIED || + err == ERROR_PRIVILEGE_NOT_HELD) + { + // Retrieve only DACL. + (void)GetNamedSecurityInfoW( + wn, + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + &p->pDacl, + NULL, + &p->pSecurityDescriptor); + } + if (p->pSecurityDescriptor == NULL) + { + mch_free_acl((vim_acl_T)p); + p = NULL; + } + vim_free(wn); + } + + return (vim_acl_T)p; + #endif + } + + #ifdef HAVE_ACL + /* + * Check if "acl" contains inherited ACE. + */ + static BOOL + is_acl_inherited(PACL acl) + { + DWORD i; + ACL_SIZE_INFORMATION acl_info; + PACCESS_ALLOWED_ACE ace; + + acl_info.AceCount = 0; + GetAclInformation(acl, &acl_info, sizeof(acl_info), AclSizeInformation); + for (i = 0; i < acl_info.AceCount; i++) + { + GetAce(acl, i, (LPVOID *)&ace); + if (ace->Header.AceFlags & INHERITED_ACE) + return TRUE; + } + return FALSE; + } + #endif + + /* + * Set the ACL of file "fname" to "acl" (unless it's NULL). + * Errors are ignored. + * This must only be called with "acl" equal to what mch_get_acl() returned. + */ + void + mch_set_acl(char_u *fname, vim_acl_T acl) + { + #ifdef HAVE_ACL + struct my_acl *p = (struct my_acl *)acl; + SECURITY_INFORMATION sec_info = 0; + WCHAR *wn; + + if (p == NULL) + return; + + wn = enc_to_utf16(fname, NULL); + if (wn == NULL) + return; + + // Set security flags + if (p->pSidOwner) + sec_info |= OWNER_SECURITY_INFORMATION; + if (p->pSidGroup) + sec_info |= GROUP_SECURITY_INFORMATION; + if (p->pDacl) + { + sec_info |= DACL_SECURITY_INFORMATION; + // Do not inherit its parent's DACL. + // If the DACL is inherited, Cygwin permissions would be changed. + if (!is_acl_inherited(p->pDacl)) + sec_info |= PROTECTED_DACL_SECURITY_INFORMATION; + } + if (p->pSacl) + sec_info |= SACL_SECURITY_INFORMATION; + + (void)SetNamedSecurityInfoW( + wn, // Abstract filename + SE_FILE_OBJECT, // File Object + sec_info, + p->pSidOwner, // Ownership information. + p->pSidGroup, // Group membership. + p->pDacl, // Discretionary information. + p->pSacl // For auditing purposes. + ); + vim_free(wn); + #endif + } + + void + mch_free_acl(vim_acl_T acl) + { + #ifdef HAVE_ACL + struct my_acl *p = (struct my_acl *)acl; + + if (p != NULL) + { + LocalFree(p->pSecurityDescriptor); // Free the memory just in case + vim_free(p); + } + #endif + } + +-#ifndef FEAT_GUI_MSWIN ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + + /* + * handler for ctrl-break, ctrl-c interrupts, and fatal events. + */ + static BOOL WINAPI + handler_routine( + DWORD dwCtrlType) + { + INPUT_RECORD ir; + DWORD out; + + switch (dwCtrlType) + { + case CTRL_C_EVENT: + if (ctrl_c_interrupts) + g_fCtrlCPressed = TRUE; + return TRUE; + + case CTRL_BREAK_EVENT: + g_fCBrkPressed = TRUE; + ctrl_break_was_pressed = TRUE; + /* ReadConsoleInput is blocking, send a key event to continue. */ + ir.EventType = KEY_EVENT; + ir.Event.KeyEvent.bKeyDown = TRUE; + ir.Event.KeyEvent.wRepeatCount = 1; + ir.Event.KeyEvent.wVirtualKeyCode = VK_CANCEL; + ir.Event.KeyEvent.wVirtualScanCode = 0; + ir.Event.KeyEvent.dwControlKeyState = 0; + ir.Event.KeyEvent.uChar.UnicodeChar = 0; + WriteConsoleInput(g_hConIn, &ir, 1, &out); + return TRUE; + + /* fatal events: shut down gracefully */ + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + windgoto((int)Rows - 1, 0); + g_fForceExit = TRUE; + + vim_snprintf((char *)IObuff, IOSIZE, _("Vim: Caught %s event\n"), + (dwCtrlType == CTRL_CLOSE_EVENT + ? _("close") + : dwCtrlType == CTRL_LOGOFF_EVENT + ? _("logoff") + : _("shutdown"))); + #ifdef DEBUG + OutputDebugString(IObuff); + #endif + + preserve_exit(); /* output IObuff, preserve files and exit */ + + return TRUE; /* not reached */ + + default: + return FALSE; + } + } + + + /* + * set the tty in (raw) ? "raw" : "cooked" mode + */ + void + mch_settmode(int tmode) + { + DWORD cmodein; + DWORD cmodeout; + BOOL bEnableHandler; + ++# ifdef VIMDLL ++ if (gui.in_use) ++ return; ++# endif + GetConsoleMode(g_hConIn, &cmodein); + GetConsoleMode(g_hConOut, &cmodeout); + if (tmode == TMODE_RAW) + { + cmodein &= ~(ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | + ENABLE_ECHO_INPUT); + #ifdef FEAT_MOUSE + if (g_fMouseActive) + cmodein |= ENABLE_MOUSE_INPUT; + #endif + cmodeout &= ~( + #ifdef FEAT_TERMGUICOLORS +- /* Do not turn off the ENABLE_PROCESSRD_OUTPUT flag when using ++ /* Do not turn off the ENABLE_PROCESSED_OUTPUT flag when using + * VTP. */ + ((vtp_working) ? 0 : ENABLE_PROCESSED_OUTPUT) | + #else + ENABLE_PROCESSED_OUTPUT | + #endif + ENABLE_WRAP_AT_EOL_OUTPUT); + bEnableHandler = TRUE; + } + else /* cooked */ + { + cmodein |= (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | + ENABLE_ECHO_INPUT); + cmodeout |= (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); + bEnableHandler = FALSE; + } + SetConsoleMode(g_hConIn, cmodein); + SetConsoleMode(g_hConOut, cmodeout); + SetConsoleCtrlHandler(handler_routine, bEnableHandler); + + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fprintf(fdDump, "mch_settmode(%s, in = %x, out = %x)\n", + tmode == TMODE_RAW ? "raw" : + tmode == TMODE_COOK ? "cooked" : "normal", + cmodein, cmodeout); + fflush(fdDump); + } + #endif + } + + + /* + * Get the size of the current window in `Rows' and `Columns' + * Return OK when size could be determined, FAIL otherwise. + */ + int + mch_get_shellsize(void) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; + ++# ifdef VIMDLL ++ if (gui.in_use) ++ return OK; ++# endif + if (!g_fTermcapMode && g_cbTermcap.IsValid) + { + /* + * For some reason, we are trying to get the screen dimensions + * even though we are not in termcap mode. The 'Rows' and 'Columns' + * variables are really intended to mean the size of Vim screen + * while in termcap mode. + */ + Rows = g_cbTermcap.Info.dwSize.Y; + Columns = g_cbTermcap.Info.dwSize.X; + } + else if (GetConsoleScreenBufferInfo(g_hConOut, &csbi)) + { + Rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + Columns = csbi.srWindow.Right - csbi.srWindow.Left + 1; + } + else + { + Rows = 25; + Columns = 80; + } + return OK; + } + + /* + * Resize console buffer to 'COORD' + */ + static void + ResizeConBuf( + HANDLE hConsole, + COORD coordScreen) + { + if (!SetConsoleScreenBufferSize(hConsole, coordScreen)) + { + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fprintf(fdDump, "SetConsoleScreenBufferSize failed: %lx\n", + GetLastError()); + fflush(fdDump); + } + #endif + } + } + + /* + * Resize console window size to 'srWindowRect' + */ + static void + ResizeWindow( + HANDLE hConsole, + SMALL_RECT srWindowRect) + { + if (!SetConsoleWindowInfo(hConsole, TRUE, &srWindowRect)) + { + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fprintf(fdDump, "SetConsoleWindowInfo failed: %lx\n", + GetLastError()); + fflush(fdDump); + } + #endif + } + } + + /* + * Set a console window to `xSize' * `ySize' + */ + static void + ResizeConBufAndWindow( + HANDLE hConsole, + int xSize, + int ySize) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; /* hold current console buffer info */ + SMALL_RECT srWindowRect; /* hold the new console size */ + COORD coordScreen; + static int resized = FALSE; + + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fprintf(fdDump, "ResizeConBufAndWindow(%d, %d)\n", xSize, ySize); + fflush(fdDump); + } + #endif + + /* get the largest size we can size the console window to */ + coordScreen = GetLargestConsoleWindowSize(hConsole); + + /* define the new console window size and scroll position */ + srWindowRect.Left = srWindowRect.Top = (SHORT) 0; + srWindowRect.Right = (SHORT) (min(xSize, coordScreen.X) - 1); + srWindowRect.Bottom = (SHORT) (min(ySize, coordScreen.Y) - 1); + + if (GetConsoleScreenBufferInfo(g_hConOut, &csbi)) + { + int sx, sy; + + sx = csbi.srWindow.Right - csbi.srWindow.Left + 1; + sy = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + if (sy < ySize || sx < xSize) + { + /* + * Increasing number of lines/columns, do buffer first. + * Use the maximal size in x and y direction. + */ + if (sy < ySize) + coordScreen.Y = ySize; + else + coordScreen.Y = sy; + if (sx < xSize) + coordScreen.X = xSize; + else + coordScreen.X = sx; + SetConsoleScreenBufferSize(hConsole, coordScreen); + } + } + + // define the new console buffer size + coordScreen.X = xSize; + coordScreen.Y = ySize; + + // In the new console call API, only the first time in reverse order + if (!vtp_working || resized) + { + ResizeWindow(hConsole, srWindowRect); + ResizeConBuf(hConsole, coordScreen); + } + else + { + ResizeConBuf(hConsole, coordScreen); + ResizeWindow(hConsole, srWindowRect); + resized = TRUE; + } + } + + + /* + * Set the console window to `Rows' * `Columns' + */ + void + mch_set_shellsize(void) + { + COORD coordScreen; + ++# ifdef VIMDLL ++ if (gui.in_use) ++ return; ++# endif + /* Don't change window size while still starting up */ + if (suppress_winsize != 0) + { + suppress_winsize = 2; + return; + } + + if (term_console) + { + coordScreen = GetLargestConsoleWindowSize(g_hConOut); + + /* Clamp Rows and Columns to reasonable values */ + if (Rows > coordScreen.Y) + Rows = coordScreen.Y; + if (Columns > coordScreen.X) + Columns = coordScreen.X; + + ResizeConBufAndWindow(g_hConOut, Columns, Rows); + } + } + + /* + * Rows and/or Columns has changed. + */ + void + mch_new_shellsize(void) + { ++# ifdef VIMDLL ++ if (gui.in_use) ++ return; ++# endif + set_scroll_region(0, 0, Columns - 1, Rows - 1); + } + + + /* + * Called when started up, to set the winsize that was delayed. + */ + void + mch_set_winsize_now(void) + { + if (suppress_winsize == 2) + { + suppress_winsize = 0; + mch_set_shellsize(); + shell_resized(); + } + suppress_winsize = 0; + } + #endif /* FEAT_GUI_MSWIN */ + + static BOOL + vim_create_process( + char *cmd, + BOOL inherit_handles, + DWORD flags, + STARTUPINFO *si, + PROCESS_INFORMATION *pi, + LPVOID *env, + char *cwd) + { + BOOL ret = FALSE; + WCHAR *wcmd, *wcwd = NULL; + + wcmd = enc_to_utf16((char_u *)cmd, NULL); + if (wcmd == NULL) + return FALSE; + if (cwd != NULL) + { + wcwd = enc_to_utf16((char_u *)cwd, NULL); + if (wcwd == NULL) + goto theend; + } + + ret = CreateProcessW( + NULL, // Executable name + wcmd, // Command to execute + NULL, // Process security attributes + NULL, // Thread security attributes + inherit_handles, // Inherit handles + flags, // Creation flags + env, // Environment + wcwd, // Current directory + (LPSTARTUPINFOW)si, // Startup information + pi); // Process information + theend: + vim_free(wcmd); + vim_free(wcwd); + return ret; + } + + + static HINSTANCE + vim_shell_execute( + char *cmd, + INT n_show_cmd) + { + HINSTANCE ret; + WCHAR *wcmd; + + wcmd = enc_to_utf16((char_u *)cmd, NULL); + if (wcmd == NULL) + return (HINSTANCE) 0; + + ret = ShellExecuteW(NULL, NULL, wcmd, NULL, NULL, n_show_cmd); + vim_free(wcmd); + return ret; + } + + + #if defined(FEAT_GUI_MSWIN) || defined(PROTO) + + /* + * Specialised version of system() for Win32 GUI mode. + * This version proceeds as follows: + * 1. Create a console window for use by the subprocess + * 2. Run the subprocess (it gets the allocated console by default) + * 3. Wait for the subprocess to terminate and get its exit code + * 4. Prompt the user to press a key to close the console window + */ + static int + mch_system_classic(char *cmd, int options) + { + STARTUPINFO si; + PROCESS_INFORMATION pi; + DWORD ret = 0; + HWND hwnd = GetFocus(); + + si.cb = sizeof(si); + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.lpTitle = NULL; + si.dwFlags = STARTF_USESHOWWINDOW; + /* + * It's nicer to run a filter command in a minimized window. + * Don't activate the window to keep focus on Vim. + */ + if (options & SHELL_DOOUT) + si.wShowWindow = SW_SHOWMINNOACTIVE; + else + si.wShowWindow = SW_SHOWNORMAL; + si.cbReserved2 = 0; + si.lpReserved2 = NULL; + + /* Now, run the command */ + vim_create_process(cmd, FALSE, + CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE, + &si, &pi, NULL, NULL); + + /* Wait for the command to terminate before continuing */ + { + #ifdef FEAT_GUI + int delay = 1; + + /* Keep updating the window while waiting for the shell to finish. */ + for (;;) + { + MSG msg; + + if (pPeekMessage(&msg, (HWND)NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + pDispatchMessage(&msg); + delay = 1; + continue; + } + if (WaitForSingleObject(pi.hProcess, delay) != WAIT_TIMEOUT) + break; + + /* We start waiting for a very short time and then increase it, so + * that we respond quickly when the process is quick, and don't + * consume too much overhead when it's slow. */ + if (delay < 50) + delay += 10; + } + #else + WaitForSingleObject(pi.hProcess, INFINITE); + #endif + + /* Get the command exit code */ + GetExitCodeProcess(pi.hProcess, &ret); + } + + /* Close the handles to the subprocess, so that it goes away */ + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + /* Try to get input focus back. Doesn't always work though. */ + PostMessage(hwnd, WM_SETFOCUS, 0, 0); + + return ret; + } + + /* + * Thread launched by the gui to send the current buffer data to the + * process. This way avoid to hang up vim totally if the children + * process take a long time to process the lines. + */ + static unsigned int __stdcall + sub_process_writer(LPVOID param) + { + HANDLE g_hChildStd_IN_Wr = param; + linenr_T lnum = curbuf->b_op_start.lnum; + DWORD len = 0; + DWORD l; + char_u *lp = ml_get(lnum); + char_u *s; + int written = 0; + + for (;;) + { + l = (DWORD)STRLEN(lp + written); + if (l == 0) + len = 0; + else if (lp[written] == NL) + { + /* NL -> NUL translation */ + WriteFile(g_hChildStd_IN_Wr, "", 1, &len, NULL); + } + else + { + s = vim_strchr(lp + written, NL); + WriteFile(g_hChildStd_IN_Wr, (char *)lp + written, + s == NULL ? l : (DWORD)(s - (lp + written)), + &len, NULL); + } + if (len == (int)l) + { + /* Finished a line, add a NL, unless this line should not have + * one. */ + if (lnum != curbuf->b_op_end.lnum + || (!curbuf->b_p_bin + && curbuf->b_p_fixeol) + || (lnum != curbuf->b_no_eol_lnum + && (lnum != curbuf->b_ml.ml_line_count + || curbuf->b_p_eol))) + { + WriteFile(g_hChildStd_IN_Wr, "\n", 1, + (LPDWORD)&vim_ignored, NULL); + } + + ++lnum; + if (lnum > curbuf->b_op_end.lnum) + break; + + lp = ml_get(lnum); + written = 0; + } + else if (len > 0) + written += len; + } + + /* finished all the lines, close pipe */ + CloseHandle(g_hChildStd_IN_Wr); + return 0; + } + + + # define BUFLEN 100 /* length for buffer, stolen from unix version */ + + /* + * This function read from the children's stdout and write the + * data on screen or in the buffer accordingly. + */ + static void + dump_pipe(int options, + HANDLE g_hChildStd_OUT_Rd, + garray_T *ga, + char_u buffer[], + DWORD *buffer_off) + { + DWORD availableBytes = 0; + DWORD i; + int ret; + DWORD len; + DWORD toRead; + int repeatCount; + + /* we query the pipe to see if there is any data to read + * to avoid to perform a blocking read */ + ret = PeekNamedPipe(g_hChildStd_OUT_Rd, /* pipe to query */ + NULL, /* optional buffer */ + 0, /* buffer size */ + NULL, /* number of read bytes */ + &availableBytes, /* available bytes total */ + NULL); /* byteLeft */ + + repeatCount = 0; + /* We got real data in the pipe, read it */ + while (ret != 0 && availableBytes > 0) + { + repeatCount++; + toRead = (DWORD)(BUFLEN - *buffer_off); + toRead = availableBytes < toRead ? availableBytes : toRead; + ReadFile(g_hChildStd_OUT_Rd, buffer + *buffer_off, toRead , &len, NULL); + + /* If we haven't read anything, there is a problem */ + if (len == 0) + break; + + availableBytes -= len; + + if (options & SHELL_READ) + { + /* Do NUL -> NL translation, append NL separated + * lines to the current buffer. */ + for (i = 0; i < len; ++i) + { + if (buffer[i] == NL) + append_ga_line(ga); + else if (buffer[i] == NUL) + ga_append(ga, NL); + else + ga_append(ga, buffer[i]); + } + } + else if (has_mbyte) + { + int l; + int c; + char_u *p; + + len += *buffer_off; + buffer[len] = NUL; + + /* Check if the last character in buffer[] is + * incomplete, keep these bytes for the next + * round. */ + for (p = buffer; p < buffer + len; p += l) + { + l = MB_CPTR2LEN(p); + if (l == 0) + l = 1; /* NUL byte? */ + else if (MB_BYTE2LEN(*p) != l) + break; + } + if (p == buffer) /* no complete character */ + { + /* avoid getting stuck at an illegal byte */ + if (len >= 12) + ++p; + else + { + *buffer_off = len; + return; + } + } + c = *p; + *p = NUL; + msg_puts((char *)buffer); + if (p < buffer + len) + { + *p = c; + *buffer_off = (DWORD)((buffer + len) - p); + mch_memmove(buffer, p, *buffer_off); + return; + } + *buffer_off = 0; + } + else + { + buffer[len] = NUL; + msg_puts((char *)buffer); + } + + windgoto(msg_row, msg_col); + cursor_on(); + out_flush(); + } + } + + /* + * Version of system to use for windows NT > 5.0 (Win2K), use pipe + * for communication and doesn't open any new window. + */ + static int + mch_system_piped(char *cmd, int options) + { + STARTUPINFO si; + PROCESS_INFORMATION pi; + DWORD ret = 0; + + HANDLE g_hChildStd_IN_Rd = NULL; + HANDLE g_hChildStd_IN_Wr = NULL; + HANDLE g_hChildStd_OUT_Rd = NULL; + HANDLE g_hChildStd_OUT_Wr = NULL; + + char_u buffer[BUFLEN + 1]; /* reading buffer + size */ + DWORD len; + + /* buffer used to receive keys */ + char_u ta_buf[BUFLEN + 1]; /* TypeAHead */ + int ta_len = 0; /* valid bytes in ta_buf[] */ + + DWORD i; + int c; + int noread_cnt = 0; + garray_T ga; + int delay = 1; + DWORD buffer_off = 0; /* valid bytes in buffer[] */ + char *p = NULL; + + SECURITY_ATTRIBUTES saAttr; + + /* Set the bInheritHandle flag so pipe handles are inherited. */ + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0) + /* Ensure the read handle to the pipe for STDOUT is not inherited. */ + || ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) + /* Create a pipe for the child process's STDIN. */ + || ! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0) + /* Ensure the write handle to the pipe for STDIN is not inherited. */ + || ! SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) ) + { + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_IN_Wr); + CloseHandle(g_hChildStd_OUT_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + msg_puts(_("\nCannot create pipes\n")); + } + + si.cb = sizeof(si); + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.lpTitle = NULL; + si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + + /* set-up our file redirection */ + si.hStdError = g_hChildStd_OUT_Wr; + si.hStdOutput = g_hChildStd_OUT_Wr; + si.hStdInput = g_hChildStd_IN_Rd; + si.wShowWindow = SW_HIDE; + si.cbReserved2 = 0; + si.lpReserved2 = NULL; + + if (options & SHELL_READ) + ga_init2(&ga, 1, BUFLEN); + + if (cmd != NULL) + { + p = (char *)vim_strsave((char_u *)cmd); + if (p != NULL) + unescape_shellxquote((char_u *)p, p_sxe); + else + p = cmd; + } + + /* Now, run the command. + * About "Inherit handles" being TRUE: this command can be litigious, + * handle inheritance was deactivated for pending temp file, but, if we + * deactivate it, the pipes don't work for some reason. */ + vim_create_process(p, TRUE, CREATE_DEFAULT_ERROR_MODE, + &si, &pi, NULL, NULL); + + if (p != cmd) + vim_free(p); + + /* Close our unused side of the pipes */ + CloseHandle(g_hChildStd_IN_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + + if (options & SHELL_WRITE) + { + HANDLE thread = (HANDLE) + _beginthreadex(NULL, /* security attributes */ + 0, /* default stack size */ + sub_process_writer, /* function to be executed */ + g_hChildStd_IN_Wr, /* parameter */ + 0, /* creation flag, start immediately */ + NULL); /* we don't care about thread id */ + CloseHandle(thread); + g_hChildStd_IN_Wr = NULL; + } + + /* Keep updating the window while waiting for the shell to finish. */ + for (;;) + { + MSG msg; + + if (pPeekMessage(&msg, (HWND)NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + pDispatchMessage(&msg); + } + + /* write pipe information in the window */ + if ((options & (SHELL_READ|SHELL_WRITE)) + # ifdef FEAT_GUI + || gui.in_use + # endif + ) + { + len = 0; + if (!(options & SHELL_EXPAND) + && ((options & + (SHELL_READ|SHELL_WRITE|SHELL_COOKED)) + != (SHELL_READ|SHELL_WRITE|SHELL_COOKED) + # ifdef FEAT_GUI + || gui.in_use + # endif + ) + && (ta_len > 0 || noread_cnt > 4)) + { + if (ta_len == 0) + { + /* Get extra characters when we don't have any. Reset the + * counter and timer. */ + noread_cnt = 0; + len = ui_inchar(ta_buf, BUFLEN, 10L, 0); + } + if (ta_len > 0 || len > 0) + { + /* + * For pipes: Check for CTRL-C: send interrupt signal to + * child. Check for CTRL-D: EOF, close pipe to child. + */ + if (len == 1 && cmd != NULL) + { + if (ta_buf[ta_len] == Ctrl_C) + { + /* Learn what exit code is expected, for + * now put 9 as SIGKILL */ + TerminateProcess(pi.hProcess, 9); + } + if (ta_buf[ta_len] == Ctrl_D) + { + CloseHandle(g_hChildStd_IN_Wr); + g_hChildStd_IN_Wr = NULL; + } + } + + /* replace K_BS by and K_DEL by */ + for (i = ta_len; i < ta_len + len; ++i) + { + if (ta_buf[i] == CSI && len - i > 2) + { + c = TERMCAP2KEY(ta_buf[i + 1], ta_buf[i + 2]); + if (c == K_DEL || c == K_KDEL || c == K_BS) + { + mch_memmove(ta_buf + i + 1, ta_buf + i + 3, + (size_t)(len - i - 2)); + if (c == K_DEL || c == K_KDEL) + ta_buf[i] = DEL; + else + ta_buf[i] = Ctrl_H; + len -= 2; + } + } + else if (ta_buf[i] == '\r') + ta_buf[i] = '\n'; + if (has_mbyte) + i += (*mb_ptr2len_len)(ta_buf + i, + ta_len + len - i) - 1; + } + + /* + * For pipes: echo the typed characters. For a pty this + * does not seem to work. + */ + for (i = ta_len; i < ta_len + len; ++i) + { + if (ta_buf[i] == '\n' || ta_buf[i] == '\b') + msg_putchar(ta_buf[i]); + else if (has_mbyte) + { + int l = (*mb_ptr2len)(ta_buf + i); + + msg_outtrans_len(ta_buf + i, l); + i += l - 1; + } + else + msg_outtrans_len(ta_buf + i, 1); + } + windgoto(msg_row, msg_col); + out_flush(); + + ta_len += len; + + /* + * Write the characters to the child, unless EOF has been + * typed for pipes. Write one character at a time, to + * avoid losing too much typeahead. When writing buffer + * lines, drop the typed characters (only check for + * CTRL-C). + */ + if (options & SHELL_WRITE) + ta_len = 0; + else if (g_hChildStd_IN_Wr != NULL) + { + WriteFile(g_hChildStd_IN_Wr, (char*)ta_buf, + 1, &len, NULL); + // if we are typing in, we want to keep things reactive + delay = 1; + if (len > 0) + { + ta_len -= len; + mch_memmove(ta_buf, ta_buf + len, ta_len); + } + } + } + } + } + + if (ta_len) + ui_inchar_undo(ta_buf, ta_len); + + if (WaitForSingleObject(pi.hProcess, delay) != WAIT_TIMEOUT) + { + dump_pipe(options, g_hChildStd_OUT_Rd, &ga, buffer, &buffer_off); + break; + } + + ++noread_cnt; + dump_pipe(options, g_hChildStd_OUT_Rd, &ga, buffer, &buffer_off); + + /* We start waiting for a very short time and then increase it, so + * that we respond quickly when the process is quick, and don't + * consume too much overhead when it's slow. */ + if (delay < 50) + delay += 10; + } + + /* Close the pipe */ + CloseHandle(g_hChildStd_OUT_Rd); + if (g_hChildStd_IN_Wr != NULL) + CloseHandle(g_hChildStd_IN_Wr); + + WaitForSingleObject(pi.hProcess, INFINITE); + + /* Get the command exit code */ + GetExitCodeProcess(pi.hProcess, &ret); + + if (options & SHELL_READ) + { + if (ga.ga_len > 0) + { + append_ga_line(&ga); + /* remember that the NL was missing */ + curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; + } + else + curbuf->b_no_eol_lnum = 0; + ga_clear(&ga); + } + + /* Close the handles to the subprocess, so that it goes away */ + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + return ret; + } + + static int +-mch_system(char *cmd, int options) ++mch_system_g(char *cmd, int options) + { + /* if we can pipe and the shelltemp option is off */ + if (!p_stmp) + return mch_system_piped(cmd, options); + else + return mch_system_classic(cmd, options); + } +-#else ++#endif + ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + static int +-mch_system(char *cmd, int options) ++mch_system_c(char *cmd, int options) + { + int ret; + WCHAR *wcmd; + + wcmd = enc_to_utf16((char_u *)cmd, NULL); + if (wcmd == NULL) + return -1; + + ret = _wsystem(wcmd); + vim_free(wcmd); + return ret; + } + + #endif + ++ static int ++mch_system(char *cmd, int options) ++{ ++#ifdef VIMDLL ++ if (gui.in_use) ++ return mch_system_g(cmd, options); ++ else ++ return mch_system_c(cmd, options); ++#elif defined(FEAT_GUI_MSWIN) ++ return mch_system_g(cmd, options); ++#else ++ return mch_system_c(cmd, options); ++#endif ++} ++ + #if defined(FEAT_GUI) && defined(FEAT_TERMINAL) + /* + * Use a terminal window to run a shell command in. + */ + static int + mch_call_shell_terminal( + char_u *cmd, + int options UNUSED) /* SHELL_*, see vim.h */ + { + jobopt_T opt; + char_u *newcmd = NULL; + typval_T argvar[2]; + long_u cmdlen; + int retval = -1; + buf_T *buf; + job_T *job; + aco_save_T aco; + oparg_T oa; /* operator arguments */ + + if (cmd == NULL) + cmdlen = STRLEN(p_sh) + 1; + else + cmdlen = STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10; + newcmd = lalloc(cmdlen, TRUE); + if (newcmd == NULL) + return 255; + if (cmd == NULL) + { + STRCPY(newcmd, p_sh); + ch_log(NULL, "starting terminal to run a shell"); + } + else + { + vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", p_sh, p_shcf, cmd); + ch_log(NULL, "starting terminal for system command '%s'", cmd); + } + + init_job_options(&opt); + + argvar[0].v_type = VAR_STRING; + argvar[0].vval.v_string = newcmd; + argvar[1].v_type = VAR_UNKNOWN; + buf = term_start(argvar, NULL, &opt, TERM_START_SYSTEM); + if (buf == NULL) + { + vim_free(newcmd); + return 255; + } + + job = term_getjob(buf->b_term); + ++job->jv_refcount; + + /* Find a window to make "buf" curbuf. */ + aucmd_prepbuf(&aco, buf); + + clear_oparg(&oa); + while (term_use_loop()) + { + if (oa.op_type == OP_NOP && oa.regname == NUL && !VIsual_active) + { + /* If terminal_loop() returns OK we got a key that is handled + * in Normal model. We don't do redrawing anyway. */ + if (terminal_loop(TRUE) == OK) + normal_cmd(&oa, TRUE); + } + else + normal_cmd(&oa, TRUE); + } + retval = job->jv_exitval; + ch_log(NULL, "system command finished"); + + job_unref(job); + + /* restore curwin/curbuf and a few other things */ + aucmd_restbuf(&aco); + + wait_return(TRUE); + do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE); + + vim_free(newcmd); + return retval; + } + #endif + + /* + * Either execute a command by calling the shell or start a new shell + */ + int + mch_call_shell( + char_u *cmd, + int options) /* SHELL_*, see vim.h */ + { + int x = 0; + int tmode = cur_tmode; + #ifdef FEAT_TITLE + WCHAR szShellTitle[512]; + + /* Change the title to reflect that we are in a subshell. */ + if (GetConsoleTitleW(szShellTitle, + sizeof(szShellTitle)/sizeof(WCHAR) - 4) > 0) + { + if (cmd == NULL) + wcscat(szShellTitle, L" :sh"); + else + { + WCHAR *wn = enc_to_utf16((char_u *)cmd, NULL); + + if (wn != NULL) + { + wcscat(szShellTitle, L" - !"); + if ((wcslen(szShellTitle) + wcslen(wn) < + sizeof(szShellTitle)/sizeof(WCHAR))) + wcscat(szShellTitle, wn); + SetConsoleTitleW(szShellTitle); + vim_free(wn); + } + } + } + #endif + + out_flush(); + + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fprintf(fdDump, "mch_call_shell(\"%s\", %d)\n", cmd, options); + fflush(fdDump); + } + #endif + #if defined(FEAT_GUI) && defined(FEAT_TERMINAL) + /* TODO: make the terminal window work with input or output redirected. */ +- if (vim_strchr(p_go, GO_TERMINAL) != NULL ++ if ( ++# ifdef VIMDLL ++ gui.in_use && ++# endif ++ vim_strchr(p_go, GO_TERMINAL) != NULL + && (options & (SHELL_FILTER|SHELL_DOOUT|SHELL_WRITE|SHELL_READ)) == 0) + { + /* Use a terminal window to run the command in. */ + x = mch_call_shell_terminal(cmd, options); + # ifdef FEAT_TITLE + resettitle(); + # endif + return x; + } + #endif + + /* + * Catch all deadly signals while running the external command, because a + * CTRL-C, Ctrl-Break or illegal instruction might otherwise kill us. + */ + signal(SIGINT, SIG_IGN); + #if defined(__GNUC__) && !defined(__MINGW32__) + signal(SIGKILL, SIG_IGN); + #else + signal(SIGBREAK, SIG_IGN); + #endif + signal(SIGILL, SIG_IGN); + signal(SIGFPE, SIG_IGN); + signal(SIGSEGV, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGABRT, SIG_IGN); + + if (options & SHELL_COOKED) + settmode(TMODE_COOK); /* set to normal mode */ + + if (cmd == NULL) + { + x = mch_system((char *)p_sh, options); + } + else + { + /* we use "command" or "cmd" to start the shell; slow but easy */ + char_u *newcmd = NULL; + char_u *cmdbase = cmd; + long_u cmdlen; + + /* Skip a leading ", ( and "(. */ + if (*cmdbase == '"' ) + ++cmdbase; + if (*cmdbase == '(') + ++cmdbase; + + if ((STRNICMP(cmdbase, "start", 5) == 0) && VIM_ISWHITE(cmdbase[5])) + { + STARTUPINFO si; + PROCESS_INFORMATION pi; + DWORD flags = CREATE_NEW_CONSOLE; + INT n_show_cmd = SW_SHOWNORMAL; + char_u *p; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.lpTitle = NULL; + si.dwFlags = 0; + si.cbReserved2 = 0; + si.lpReserved2 = NULL; + + cmdbase = skipwhite(cmdbase + 5); + if ((STRNICMP(cmdbase, "/min", 4) == 0) + && VIM_ISWHITE(cmdbase[4])) + { + cmdbase = skipwhite(cmdbase + 4); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWMINNOACTIVE; + n_show_cmd = SW_SHOWMINNOACTIVE; + } + else if ((STRNICMP(cmdbase, "/b", 2) == 0) + && VIM_ISWHITE(cmdbase[2])) + { + cmdbase = skipwhite(cmdbase + 2); + flags = CREATE_NO_WINDOW; + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = CreateFile("\\\\.\\NUL", // File name + GENERIC_READ, // Access flags + 0, // Share flags + NULL, // Security att. + OPEN_EXISTING, // Open flags + FILE_ATTRIBUTE_NORMAL, // File att. + NULL); // Temp file + si.hStdOutput = si.hStdInput; + si.hStdError = si.hStdInput; + } + + /* Remove a trailing ", ) and )" if they have a match + * at the start of the command. */ + if (cmdbase > cmd) + { + p = cmdbase + STRLEN(cmdbase); + if (p > cmdbase && p[-1] == '"' && *cmd == '"') + *--p = NUL; + if (p > cmdbase && p[-1] == ')' + && (*cmd =='(' || cmd[1] == '(')) + *--p = NUL; + } + + newcmd = cmdbase; + unescape_shellxquote(cmdbase, p_sxe); + + /* + * If creating new console, arguments are passed to the + * 'cmd.exe' as-is. If it's not, arguments are not treated + * correctly for current 'cmd.exe'. So unescape characters in + * shellxescape except '|' for avoiding to be treated as + * argument to them. Pass the arguments to sub-shell. + */ + if (flags != CREATE_NEW_CONSOLE) + { + char_u *subcmd; + char_u *cmd_shell = mch_getenv("COMSPEC"); + + if (cmd_shell == NULL || *cmd_shell == NUL) + cmd_shell = (char_u *)default_shell(); + + subcmd = vim_strsave_escaped_ext(cmdbase, + (char_u *)"|", '^', FALSE); + if (subcmd != NULL) + { + /* make "cmd.exe /c arguments" */ + cmdlen = STRLEN(cmd_shell) + STRLEN(subcmd) + 5; + newcmd = lalloc(cmdlen, TRUE); + if (newcmd != NULL) + vim_snprintf((char *)newcmd, cmdlen, "%s /c %s", + cmd_shell, subcmd); + else + newcmd = cmdbase; + vim_free(subcmd); + } + } + + /* + * Now, start the command as a process, so that it doesn't + * inherit our handles which causes unpleasant dangling swap + * files if we exit before the spawned process + */ + if (vim_create_process((char *)newcmd, FALSE, flags, + &si, &pi, NULL, NULL)) + x = 0; + else if (vim_shell_execute((char *)newcmd, n_show_cmd) + > (HINSTANCE)32) + x = 0; + else + { + x = -1; + #ifdef FEAT_GUI_MSWIN +- emsg(_("E371: Command not found")); ++# ifdef VIMDLL ++ if (gui.in_use) ++# endif ++ emsg(_("E371: Command not found")); + #endif + } + + if (newcmd != cmdbase) + vim_free(newcmd); + + if (si.dwFlags == STARTF_USESTDHANDLES && si.hStdInput != NULL) + { + /* Close the handle to \\.\NUL created above. */ + CloseHandle(si.hStdInput); + } + /* Close the handles to the subprocess, so that it goes away */ + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + else + { + cmdlen = ( + #ifdef FEAT_GUI_MSWIN +- (!p_stmp ? 0 : STRLEN(vimrun_path)) + ++ (gui.in_use ? (!p_stmp ? 0 : STRLEN(vimrun_path)) : 0) + + #endif + STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10); + + newcmd = lalloc(cmdlen, TRUE); + if (newcmd != NULL) + { + #if defined(FEAT_GUI_MSWIN) +- if (need_vimrun_warning) ++ if ( ++# ifdef VIMDLL ++ gui.in_use && ++# endif ++ need_vimrun_warning) + { + char *msg = _("VIMRUN.EXE not found in your $PATH.\n" + "External commands will not pause after completion.\n" + "See :help win32-vimrun for more information."); + char *title = _("Vim Warning"); + WCHAR *wmsg = enc_to_utf16((char_u *)msg, NULL); + WCHAR *wtitle = enc_to_utf16((char_u *)title, NULL); + + if (wmsg != NULL && wtitle != NULL) + MessageBoxW(NULL, wmsg, wtitle, MB_ICONWARNING); + vim_free(wmsg); + vim_free(wtitle); + need_vimrun_warning = FALSE; + } +- if (!s_dont_use_vimrun && p_stmp) ++ if ( ++# ifdef VIMDLL ++ gui.in_use && ++# endif ++ !s_dont_use_vimrun && p_stmp) + /* Use vimrun to execute the command. It opens a console + * window, which can be closed without killing Vim. */ + vim_snprintf((char *)newcmd, cmdlen, "%s%s%s %s %s", + vimrun_path, + (msg_silent != 0 || (options & SHELL_DOOUT)) + ? "-s " : "", + p_sh, p_shcf, cmd); + else + #endif + vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", + p_sh, p_shcf, cmd); + x = mch_system((char *)newcmd, options); + vim_free(newcmd); + } + } + } + + if (tmode == TMODE_RAW) + settmode(TMODE_RAW); /* set to raw mode */ + + /* Print the return value, unless "vimrun" was used. */ + if (x != 0 && !(options & SHELL_SILENT) && !emsg_silent + #if defined(FEAT_GUI_MSWIN) +- && ((options & SHELL_DOOUT) || s_dont_use_vimrun || !p_stmp) ++ && (gui.in_use ? ++ ((options & SHELL_DOOUT) || s_dont_use_vimrun || !p_stmp) : 1) + #endif + ) + { + smsg(_("shell returned %d"), x); + msg_putchar('\n'); + } + #ifdef FEAT_TITLE + resettitle(); + #endif + + signal(SIGINT, SIG_DFL); + #if defined(__GNUC__) && !defined(__MINGW32__) + signal(SIGKILL, SIG_DFL); + #else + signal(SIGBREAK, SIG_DFL); + #endif + signal(SIGILL, SIG_DFL); + signal(SIGFPE, SIG_DFL); + signal(SIGSEGV, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGABRT, SIG_DFL); + + return x; + } + + #if defined(FEAT_JOB_CHANNEL) || defined(PROTO) + static HANDLE + job_io_file_open( + char_u *fname, + DWORD dwDesiredAccess, + DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes) + { + HANDLE h; + WCHAR *wn; + + wn = enc_to_utf16(fname, NULL); + if (wn == NULL) + return INVALID_HANDLE_VALUE; + + h = CreateFileW(wn, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, NULL); + vim_free(wn); + return h; + } + + /* + * Turn the dictionary "env" into a NUL separated list that can be used as the + * environment argument of vim_create_process(). + */ + void + win32_build_env(dict_T *env, garray_T *gap, int is_terminal) + { + hashitem_T *hi; + long_u todo = env != NULL ? env->dv_hashtab.ht_used : 0; + LPVOID base = GetEnvironmentStringsW(); + + /* for last \0 */ + if (ga_grow(gap, 1) == FAIL) + return; + + if (base) + { + WCHAR *p = (WCHAR*) base; + + /* for last \0 */ + if (ga_grow(gap, 1) == FAIL) + return; + + while (*p != 0 || *(p + 1) != 0) + { + if (ga_grow(gap, 1) == OK) + *((WCHAR*)gap->ga_data + gap->ga_len++) = *p; + p++; + } + FreeEnvironmentStrings(base); + *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0'; + } + + if (env != NULL) + { + for (hi = env->dv_hashtab.ht_array; todo > 0; ++hi) + { + if (!HASHITEM_EMPTY(hi)) + { + typval_T *item = &dict_lookup(hi)->di_tv; + WCHAR *wkey = enc_to_utf16((char_u *)hi->hi_key, NULL); + WCHAR *wval = enc_to_utf16(tv_get_string(item), NULL); + --todo; + if (wkey != NULL && wval != NULL) + { + size_t n; + size_t lkey = wcslen(wkey); + size_t lval = wcslen(wval); + + if (ga_grow(gap, (int)(lkey + lval + 2)) != OK) + continue; + for (n = 0; n < lkey; n++) + *((WCHAR*)gap->ga_data + gap->ga_len++) = wkey[n]; + *((WCHAR*)gap->ga_data + gap->ga_len++) = L'='; + for (n = 0; n < lval; n++) + *((WCHAR*)gap->ga_data + gap->ga_len++) = wval[n]; + *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0'; + } + vim_free(wkey); + vim_free(wval); + } + } + } + + # if defined(FEAT_CLIENTSERVER) || defined(FEAT_TERMINAL) + { + # ifdef FEAT_CLIENTSERVER + char_u *servername = get_vim_var_str(VV_SEND_SERVER); + size_t servername_len = STRLEN(servername); + # endif + # ifdef FEAT_TERMINAL + char_u *version = get_vim_var_str(VV_VERSION); + size_t version_len = STRLEN(version); + # endif + // size of "VIM_SERVERNAME=" and value, + // plus "VIM_TERMINAL=" and value, + // plus two terminating NULs + size_t n = 0 + # ifdef FEAT_CLIENTSERVER + + 15 + servername_len + # endif + # ifdef FEAT_TERMINAL + + 13 + version_len + 2 + # endif + ; + + if (ga_grow(gap, (int)n) == OK) + { + # ifdef FEAT_CLIENTSERVER + for (n = 0; n < 15; n++) + *((WCHAR*)gap->ga_data + gap->ga_len++) = + (WCHAR)"VIM_SERVERNAME="[n]; + for (n = 0; n < servername_len; n++) + *((WCHAR*)gap->ga_data + gap->ga_len++) = + (WCHAR)servername[n]; + *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0'; + # endif + # ifdef FEAT_TERMINAL + if (is_terminal) + { + for (n = 0; n < 13; n++) + *((WCHAR*)gap->ga_data + gap->ga_len++) = + (WCHAR)"VIM_TERMINAL="[n]; + for (n = 0; n < version_len; n++) + *((WCHAR*)gap->ga_data + gap->ga_len++) = + (WCHAR)version[n]; + *((WCHAR*)gap->ga_data + gap->ga_len++) = L'\0'; + } + # endif + } + } + # endif + } + + /* + * Create a pair of pipes. + * Return TRUE for success, FALSE for failure. + */ + static BOOL + create_pipe_pair(HANDLE handles[2]) + { + static LONG s; + char name[64]; + SECURITY_ATTRIBUTES sa; + + sprintf(name, "\\\\?\\pipe\\vim-%08lx-%08lx", + GetCurrentProcessId(), + InterlockedIncrement(&s)); + + // Create named pipe. Max size of named pipe is 65535. + handles[1] = CreateNamedPipe( + name, + PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_NOWAIT, + 1, MAX_NAMED_PIPE_SIZE, 0, 0, NULL); + + if (handles[1] == INVALID_HANDLE_VALUE) + return FALSE; + + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + handles[0] = CreateFile(name, + FILE_GENERIC_READ, + FILE_SHARE_READ, &sa, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if (handles[0] == INVALID_HANDLE_VALUE) + { + CloseHandle(handles[1]); + return FALSE; + } + + return TRUE; + } + + void + mch_job_start(char *cmd, job_T *job, jobopt_T *options) + { + STARTUPINFO si; + PROCESS_INFORMATION pi; + HANDLE jo; + SECURITY_ATTRIBUTES saAttr; + channel_T *channel = NULL; + HANDLE ifd[2]; + HANDLE ofd[2]; + HANDLE efd[2]; + garray_T ga; + + int use_null_for_in = options->jo_io[PART_IN] == JIO_NULL; + int use_null_for_out = options->jo_io[PART_OUT] == JIO_NULL; + int use_null_for_err = options->jo_io[PART_ERR] == JIO_NULL; + int use_file_for_in = options->jo_io[PART_IN] == JIO_FILE; + int use_file_for_out = options->jo_io[PART_OUT] == JIO_FILE; + int use_file_for_err = options->jo_io[PART_ERR] == JIO_FILE; + int use_out_for_err = options->jo_io[PART_ERR] == JIO_OUT; + + if (use_out_for_err && use_null_for_out) + use_null_for_err = TRUE; + + ifd[0] = INVALID_HANDLE_VALUE; + ifd[1] = INVALID_HANDLE_VALUE; + ofd[0] = INVALID_HANDLE_VALUE; + ofd[1] = INVALID_HANDLE_VALUE; + efd[0] = INVALID_HANDLE_VALUE; + efd[1] = INVALID_HANDLE_VALUE; + ga_init2(&ga, (int)sizeof(wchar_t), 500); + + jo = CreateJobObject(NULL, NULL); + if (jo == NULL) + { + job->jv_status = JOB_FAILED; + goto failed; + } + + if (options->jo_env != NULL) + win32_build_env(options->jo_env, &ga, FALSE); + + ZeroMemory(&pi, sizeof(pi)); + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags |= STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (use_file_for_in) + { + char_u *fname = options->jo_io_name[PART_IN]; + + ifd[0] = job_io_file_open(fname, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL); + if (ifd[0] == INVALID_HANDLE_VALUE) + { + semsg(_(e_notopen), fname); + goto failed; + } + } + else if (!use_null_for_in + && (!create_pipe_pair(ifd) + || !SetHandleInformation(ifd[1], HANDLE_FLAG_INHERIT, 0))) + goto failed; + + if (use_file_for_out) + { + char_u *fname = options->jo_io_name[PART_OUT]; + + ofd[1] = job_io_file_open(fname, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &saAttr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL); + if (ofd[1] == INVALID_HANDLE_VALUE) + { + semsg(_(e_notopen), fname); + goto failed; + } + } + else if (!use_null_for_out && + (!CreatePipe(&ofd[0], &ofd[1], &saAttr, 0) + || !SetHandleInformation(ofd[0], HANDLE_FLAG_INHERIT, 0))) + goto failed; + + if (use_file_for_err) + { + char_u *fname = options->jo_io_name[PART_ERR]; + + efd[1] = job_io_file_open(fname, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &saAttr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL); + if (efd[1] == INVALID_HANDLE_VALUE) + { + semsg(_(e_notopen), fname); + goto failed; + } + } + else if (!use_out_for_err && !use_null_for_err && + (!CreatePipe(&efd[0], &efd[1], &saAttr, 0) + || !SetHandleInformation(efd[0], HANDLE_FLAG_INHERIT, 0))) + goto failed; + + si.dwFlags |= STARTF_USESTDHANDLES; + si.hStdInput = ifd[0]; + si.hStdOutput = ofd[1]; + si.hStdError = use_out_for_err ? ofd[1] : efd[1]; + + if (!use_null_for_in || !use_null_for_out || !use_null_for_err) + { + if (options->jo_set & JO_CHANNEL) + { + channel = options->jo_channel; + if (channel != NULL) + ++channel->ch_refcount; + } + else + channel = add_channel(); + if (channel == NULL) + goto failed; + } + + if (!vim_create_process(cmd, TRUE, + CREATE_SUSPENDED | + CREATE_DEFAULT_ERROR_MODE | + CREATE_NEW_PROCESS_GROUP | + CREATE_UNICODE_ENVIRONMENT | + CREATE_NEW_CONSOLE, + &si, &pi, + ga.ga_data, + (char *)options->jo_cwd)) + { + CloseHandle(jo); + job->jv_status = JOB_FAILED; + goto failed; + } + + ga_clear(&ga); + + if (!AssignProcessToJobObject(jo, pi.hProcess)) + { + /* if failing, switch the way to terminate + * process with TerminateProcess. */ + CloseHandle(jo); + jo = NULL; + } + ResumeThread(pi.hThread); + CloseHandle(pi.hThread); + job->jv_proc_info = pi; + job->jv_job_object = jo; + job->jv_status = JOB_STARTED; + + CloseHandle(ifd[0]); + CloseHandle(ofd[1]); + if (!use_out_for_err && !use_null_for_err) + CloseHandle(efd[1]); + + job->jv_channel = channel; + if (channel != NULL) + { + channel_set_pipes(channel, + use_file_for_in || use_null_for_in + ? INVALID_FD : (sock_T)ifd[1], + use_file_for_out || use_null_for_out + ? INVALID_FD : (sock_T)ofd[0], + use_out_for_err || use_file_for_err || use_null_for_err + ? INVALID_FD : (sock_T)efd[0]); + channel_set_job(channel, job, options); + } + return; + + failed: + CloseHandle(ifd[0]); + CloseHandle(ofd[0]); + CloseHandle(efd[0]); + CloseHandle(ifd[1]); + CloseHandle(ofd[1]); + CloseHandle(efd[1]); + channel_unref(channel); + ga_clear(&ga); + } + + char * + mch_job_status(job_T *job) + { + DWORD dwExitCode = 0; + + if (!GetExitCodeProcess(job->jv_proc_info.hProcess, &dwExitCode) + || dwExitCode != STILL_ACTIVE) + { + job->jv_exitval = (int)dwExitCode; + if (job->jv_status < JOB_ENDED) + { + ch_log(job->jv_channel, "Job ended"); + job->jv_status = JOB_ENDED; + } + return "dead"; + } + return "run"; + } + + job_T * + mch_detect_ended_job(job_T *job_list) + { + HANDLE jobHandles[MAXIMUM_WAIT_OBJECTS]; + job_T *jobArray[MAXIMUM_WAIT_OBJECTS]; + job_T *job = job_list; + + while (job != NULL) + { + DWORD n; + DWORD result; + + for (n = 0; n < MAXIMUM_WAIT_OBJECTS + && job != NULL; job = job->jv_next) + { + if (job->jv_status == JOB_STARTED) + { + jobHandles[n] = job->jv_proc_info.hProcess; + jobArray[n] = job; + ++n; + } + } + if (n == 0) + continue; + result = WaitForMultipleObjects(n, jobHandles, FALSE, 0); + if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + n) + { + job_T *wait_job = jobArray[result - WAIT_OBJECT_0]; + + if (STRCMP(mch_job_status(wait_job), "dead") == 0) + return wait_job; + } + } + return NULL; + } + + static BOOL + terminate_all(HANDLE process, int code) + { + PROCESSENTRY32 pe; + HANDLE h = INVALID_HANDLE_VALUE; + DWORD pid = GetProcessId(process); + + if (pid != 0) + { + h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h != INVALID_HANDLE_VALUE) + { + pe.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(h, &pe)) + goto theend; + + do + { + if (pe.th32ParentProcessID == pid) + { + HANDLE ph = OpenProcess( + PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID); + if (ph != NULL) + { + terminate_all(ph, code); + CloseHandle(ph); + } + } + } while (Process32Next(h, &pe)); + + CloseHandle(h); + } + } + + theend: + return TerminateProcess(process, code); + } + + /* + * Send a (deadly) signal to "job". + * Return FAIL if it didn't work. + */ + int + mch_signal_job(job_T *job, char_u *how) + { + int ret; + + if (STRCMP(how, "term") == 0 || STRCMP(how, "kill") == 0 || *how == NUL) + { + /* deadly signal */ + if (job->jv_job_object != NULL) + { + if (job->jv_channel != NULL && job->jv_channel->ch_anonymous_pipe) + job->jv_channel->ch_killing = TRUE; + return TerminateJobObject(job->jv_job_object, 0) ? OK : FAIL; + } + return terminate_all(job->jv_proc_info.hProcess, 0) ? OK : FAIL; + } + + if (!AttachConsole(job->jv_proc_info.dwProcessId)) + return FAIL; + ret = GenerateConsoleCtrlEvent( + STRCMP(how, "int") == 0 ? CTRL_C_EVENT : CTRL_BREAK_EVENT, + job->jv_proc_info.dwProcessId) + ? OK : FAIL; + FreeConsole(); + return ret; + } + + /* + * Clear the data related to "job". + */ + void + mch_clear_job(job_T *job) + { + if (job->jv_status != JOB_FAILED) + { + if (job->jv_job_object != NULL) + CloseHandle(job->jv_job_object); + CloseHandle(job->jv_proc_info.hProcess); + } + } + #endif + + +-#ifndef FEAT_GUI_MSWIN ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + + /* + * Start termcap mode + */ + static void + termcap_mode_start(void) + { + DWORD cmodein; + + if (g_fTermcapMode) + return; + + SaveConsoleBuffer(&g_cbNonTermcap); + + if (g_cbTermcap.IsValid) + { + /* + * We've been in termcap mode before. Restore certain screen + * characteristics, including the buffer size and the window + * size. Since we will be redrawing the screen, we don't need + * to restore the actual contents of the buffer. + */ + RestoreConsoleBuffer(&g_cbTermcap, FALSE); + reset_console_color_rgb(); + SetConsoleWindowInfo(g_hConOut, TRUE, &g_cbTermcap.Info.srWindow); + Rows = g_cbTermcap.Info.dwSize.Y; + Columns = g_cbTermcap.Info.dwSize.X; + } + else + { + /* + * This is our first time entering termcap mode. Clear the console + * screen buffer, and resize the buffer to match the current window + * size. We will use this as the size of our editing environment. + */ + ClearConsoleBuffer(g_attrCurrent); + set_console_color_rgb(); + ResizeConBufAndWindow(g_hConOut, Columns, Rows); + } + + #ifdef FEAT_TITLE + resettitle(); + #endif + + GetConsoleMode(g_hConIn, &cmodein); + #ifdef FEAT_MOUSE + if (g_fMouseActive) + cmodein |= ENABLE_MOUSE_INPUT; + else + cmodein &= ~ENABLE_MOUSE_INPUT; + #endif + cmodein |= ENABLE_WINDOW_INPUT; + SetConsoleMode(g_hConIn, cmodein); + + redraw_later_clear(); + g_fTermcapMode = TRUE; + } + + + /* + * End termcap mode + */ + static void + termcap_mode_end(void) + { + DWORD cmodein; + ConsoleBuffer *cb; + COORD coord; + DWORD dwDummy; + + if (!g_fTermcapMode) + return; + + SaveConsoleBuffer(&g_cbTermcap); + + GetConsoleMode(g_hConIn, &cmodein); + cmodein &= ~(ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT); + SetConsoleMode(g_hConIn, cmodein); + + #ifdef FEAT_RESTORE_ORIG_SCREEN + cb = exiting ? &g_cbOrig : &g_cbNonTermcap; + #else + cb = &g_cbNonTermcap; + #endif + RestoreConsoleBuffer(cb, p_rs); + reset_console_color_rgb(); + SetConsoleCursorInfo(g_hConOut, &g_cci); + + if (p_rs || exiting) + { + /* + * Clear anything that happens to be on the current line. + */ + coord.X = 0; + coord.Y = (SHORT) (p_rs ? cb->Info.dwCursorPosition.Y : (Rows - 1)); + FillConsoleOutputCharacter(g_hConOut, ' ', + cb->Info.dwSize.X, coord, &dwDummy); + /* + * The following is just for aesthetics. If we are exiting without + * restoring the screen, then we want to have a prompt string + * appear at the bottom line. However, the command interpreter + * seems to always advance the cursor one line before displaying + * the prompt string, which causes the screen to scroll. To + * counter this, move the cursor up one line before exiting. + */ + if (exiting && !p_rs) + coord.Y--; + /* + * Position the cursor at the leftmost column of the desired row. + */ + SetConsoleCursorPosition(g_hConOut, coord); + } + + g_fTermcapMode = FALSE; + } + #endif /* FEAT_GUI_MSWIN */ + + +-#ifdef FEAT_GUI_MSWIN ++#if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL) + void + mch_write( + char_u *s UNUSED, + int len UNUSED) + { + /* never used */ + } + + #else + + /* + * clear `n' chars, starting from `coord' + */ + static void + clear_chars( + COORD coord, + DWORD n) + { + DWORD dwDummy; + + FillConsoleOutputCharacter(g_hConOut, ' ', n, coord, &dwDummy); + + if (!USE_VTP) + FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, n, coord, &dwDummy); + else + { + set_console_color_rgb(); + gotoxy(coord.X + 1, coord.Y + 1); + vtp_printf("\033[%dX", n); + } + } + + + /* + * Clear the screen + */ + static void + clear_screen(void) + { + g_coord.X = g_coord.Y = 0; + + if (!USE_VTP) + clear_chars(g_coord, Rows * Columns); + else + { + set_console_color_rgb(); + gotoxy(1, 1); + vtp_printf("\033[2J"); + } + } + + + /* + * Clear to end of display + */ + static void + clear_to_end_of_display(void) + { + COORD save = g_coord; + + if (!USE_VTP) + clear_chars(g_coord, (Rows - g_coord.Y - 1) + * Columns + (Columns - g_coord.X)); + else + { + set_console_color_rgb(); + gotoxy(g_coord.X + 1, g_coord.Y + 1); + vtp_printf("\033[0J"); + + gotoxy(save.X + 1, save.Y + 1); + g_coord = save; + } + } + + + /* + * Clear to end of line + */ + static void + clear_to_end_of_line(void) + { + COORD save = g_coord; + + if (!USE_VTP) + clear_chars(g_coord, Columns - g_coord.X); + else + { + set_console_color_rgb(); + gotoxy(g_coord.X + 1, g_coord.Y + 1); + vtp_printf("\033[0K"); + + gotoxy(save.X + 1, save.Y + 1); + g_coord = save; + } + } + + + /* + * Scroll the scroll region up by `cLines' lines + */ + static void + scroll(unsigned cLines) + { + COORD oldcoord = g_coord; + + gotoxy(g_srScrollRegion.Left + 1, g_srScrollRegion.Top + 1); + delete_lines(cLines); + + g_coord = oldcoord; + } + + + /* + * Set the scroll region + */ + static void + set_scroll_region( + unsigned left, + unsigned top, + unsigned right, + unsigned bottom) + { + if (left >= right + || top >= bottom + || right > (unsigned) Columns - 1 + || bottom > (unsigned) Rows - 1) + return; + + g_srScrollRegion.Left = left; + g_srScrollRegion.Top = top; + g_srScrollRegion.Right = right; + g_srScrollRegion.Bottom = bottom; + } + + static void + set_scroll_region_tb( + unsigned top, + unsigned bottom) + { + if (top >= bottom || bottom > (unsigned)Rows - 1) + return; + + g_srScrollRegion.Top = top; + g_srScrollRegion.Bottom = bottom; + } + + static void + set_scroll_region_lr( + unsigned left, + unsigned right) + { + if (left >= right || right > (unsigned)Columns - 1) + return; + + g_srScrollRegion.Left = left; + g_srScrollRegion.Right = right; + } + + + /* + * Insert `cLines' lines at the current cursor position + */ + static void + insert_lines(unsigned cLines) + { + SMALL_RECT source, clip; + COORD dest; + CHAR_INFO fill; + + gotoxy(g_srScrollRegion.Left + 1, g_srScrollRegion.Top + 1); + + dest.X = g_srScrollRegion.Left; + dest.Y = g_coord.Y + cLines; + + source.Left = g_srScrollRegion.Left; + source.Top = g_coord.Y; + source.Right = g_srScrollRegion.Right; + source.Bottom = g_srScrollRegion.Bottom - cLines; + + clip.Left = g_srScrollRegion.Left; + clip.Top = g_coord.Y; + clip.Right = g_srScrollRegion.Right; + clip.Bottom = g_srScrollRegion.Bottom; + + fill.Char.AsciiChar = ' '; + if (!USE_VTP) + fill.Attributes = g_attrCurrent; + else + fill.Attributes = g_attrDefault; + + set_console_color_rgb(); + + ScrollConsoleScreenBuffer(g_hConOut, &source, &clip, dest, &fill); + + // Here we have to deal with a win32 console flake: If the scroll + // region looks like abc and we scroll c to a and fill with d we get + // cbd... if we scroll block c one line at a time to a, we get cdd... + // vim expects cdd consistently... So we have to deal with that + // here... (this also occurs scrolling the same way in the other + // direction). + + if (source.Bottom < dest.Y) + { + COORD coord; + int i; + + coord.X = source.Left; + for (i = clip.Top; i < dest.Y; ++i) + { + coord.Y = i; + clear_chars(coord, source.Right - source.Left + 1); + } + } + } + + + /* + * Delete `cLines' lines at the current cursor position + */ + static void + delete_lines(unsigned cLines) + { + SMALL_RECT source, clip; + COORD dest; + CHAR_INFO fill; + int nb; + + gotoxy(g_srScrollRegion.Left + 1, g_srScrollRegion.Top + 1); + + dest.X = g_srScrollRegion.Left; + dest.Y = g_coord.Y; + + source.Left = g_srScrollRegion.Left; + source.Top = g_coord.Y + cLines; + source.Right = g_srScrollRegion.Right; + source.Bottom = g_srScrollRegion.Bottom; + + clip.Left = g_srScrollRegion.Left; + clip.Top = g_coord.Y; + clip.Right = g_srScrollRegion.Right; + clip.Bottom = g_srScrollRegion.Bottom; + + fill.Char.AsciiChar = ' '; + if (!USE_VTP) + fill.Attributes = g_attrCurrent; + else + fill.Attributes = g_attrDefault; + + set_console_color_rgb(); + + ScrollConsoleScreenBuffer(g_hConOut, &source, &clip, dest, &fill); + + // Here we have to deal with a win32 console flake; See insert_lines() + // above. + + nb = dest.Y + (source.Bottom - source.Top) + 1; + + if (nb < source.Top) + { + COORD coord; + int i; + + coord.X = source.Left; + for (i = nb; i < clip.Bottom; ++i) + { + coord.Y = i; + clear_chars(coord, source.Right - source.Left + 1); + } + } + } + + + /* + * Set the cursor position + */ + static void + gotoxy( + unsigned x, + unsigned y) + { + if (x < 1 || x > (unsigned)Columns || y < 1 || y > (unsigned)Rows) + return; + + /* external cursor coords are 1-based; internal are 0-based */ + g_coord.X = x - 1; + g_coord.Y = y - 1; + + if (!USE_VTP) + SetConsoleCursorPosition(g_hConOut, g_coord); + else + vtp_printf("\033[%d;%dH", y, x); + } + + + /* + * Set the current text attribute = (foreground | background) +- * See ../doc/os_win32.txt for the numbers. ++ * See ../runtime/doc/os_win32.txt for the numbers. + */ + static void + textattr(WORD wAttr) + { + g_attrCurrent = wAttr & 0xff; + + SetConsoleTextAttribute(g_hConOut, wAttr); + } + + + static void + textcolor(WORD wAttr) + { + g_attrCurrent = (g_attrCurrent & 0xf0) + (wAttr & 0x0f); + + if (!USE_VTP) + SetConsoleTextAttribute(g_hConOut, g_attrCurrent); + else + vtp_sgr_bulk(wAttr); + } + + + static void + textbackground(WORD wAttr) + { + g_attrCurrent = (g_attrCurrent & 0x0f) + ((wAttr & 0x0f) << 4); + + if (!USE_VTP) + SetConsoleTextAttribute(g_hConOut, g_attrCurrent); + else + vtp_sgr_bulk(wAttr); + } + + + /* + * restore the default text attribute (whatever we started with) + */ + static void + normvideo(void) + { + if (!USE_VTP) + textattr(g_attrDefault); + else + vtp_sgr_bulk(0); + } + + + static WORD g_attrPreStandout = 0; + + /* + * Make the text standout, by brightening it + */ + static void + standout(void) + { + g_attrPreStandout = g_attrCurrent; + + textattr((WORD) (g_attrCurrent|FOREGROUND_INTENSITY|BACKGROUND_INTENSITY)); + } + + + /* + * Turn off standout mode + */ + static void + standend(void) + { + if (g_attrPreStandout) + textattr(g_attrPreStandout); + + g_attrPreStandout = 0; + } + + + /* + * Set normal fg/bg color, based on T_ME. Called when t_me has been set. + */ + void + mch_set_normal_colors(void) + { + char_u *p; + int n; + + cterm_normal_fg_color = (g_attrDefault & 0xf) + 1; + cterm_normal_bg_color = ((g_attrDefault >> 4) & 0xf) + 1; + if ( + #ifdef FEAT_TERMGUICOLORS + !p_tgc && + #endif + T_ME[0] == ESC && T_ME[1] == '|') + { + p = T_ME + 2; + n = getdigits(&p); + if (*p == 'm' && n > 0) + { + cterm_normal_fg_color = (n & 0xf) + 1; + cterm_normal_bg_color = ((n >> 4) & 0xf) + 1; + } + } + #ifdef FEAT_TERMGUICOLORS + cterm_normal_fg_gui_color = INVALCOLOR; + cterm_normal_bg_gui_color = INVALCOLOR; + #endif + } + + + /* + * visual bell: flash the screen + */ + static void + visual_bell(void) + { + COORD coordOrigin = {0, 0}; + WORD attrFlash = ~g_attrCurrent & 0xff; + + DWORD dwDummy; + LPWORD oldattrs = (LPWORD)alloc(Rows * Columns * sizeof(WORD)); + + if (oldattrs == NULL) + return; + ReadConsoleOutputAttribute(g_hConOut, oldattrs, Rows * Columns, + coordOrigin, &dwDummy); + FillConsoleOutputAttribute(g_hConOut, attrFlash, Rows * Columns, + coordOrigin, &dwDummy); + + Sleep(15); /* wait for 15 msec */ + if (!USE_VTP) + WriteConsoleOutputAttribute(g_hConOut, oldattrs, Rows * Columns, + coordOrigin, &dwDummy); + vim_free(oldattrs); + } + + + /* + * Make the cursor visible or invisible + */ + static void + cursor_visible(BOOL fVisible) + { + s_cursor_visible = fVisible; + #ifdef MCH_CURSOR_SHAPE + mch_update_cursor(); + #endif + } + + + /* + * Write "cbToWrite" bytes in `pchBuf' to the screen. + * Returns the number of bytes actually written (at least one). + */ + static DWORD + write_chars( + char_u *pchBuf, + DWORD cbToWrite) + { + COORD coord = g_coord; + DWORD written; + DWORD n, cchwritten, cells; + static WCHAR *unicodebuf = NULL; + static int unibuflen = 0; + int length; + int cp = enc_utf8 ? CP_UTF8 : enc_codepage; + + length = MultiByteToWideChar(cp, 0, (LPCSTR)pchBuf, cbToWrite, 0, 0); + if (unicodebuf == NULL || length > unibuflen) + { + vim_free(unicodebuf); + unicodebuf = (WCHAR *)lalloc(length * sizeof(WCHAR), FALSE); + unibuflen = length; + } + MultiByteToWideChar(cp, 0, (LPCSTR)pchBuf, cbToWrite, + unicodebuf, unibuflen); + + cells = mb_string2cells(pchBuf, cbToWrite); + + if (!USE_VTP) + { + FillConsoleOutputAttribute(g_hConOut, g_attrCurrent, cells, + coord, &written); + // When writing fails or didn't write a single character, pretend one + // character was written, otherwise we get stuck. + if (WriteConsoleOutputCharacterW(g_hConOut, unicodebuf, length, + coord, &cchwritten) == 0 + || cchwritten == 0 || cchwritten == (DWORD)-1) + cchwritten = 1; + } + else + { + if (WriteConsoleW(g_hConOut, unicodebuf, length, &cchwritten, + NULL) == 0 || cchwritten == 0) + cchwritten = 1; + } + + if (cchwritten == length) + { + written = cbToWrite; + g_coord.X += (SHORT)cells; + } + else + { + char_u *p = pchBuf; + for (n = 0; n < cchwritten; n++) + MB_CPTR_ADV(p); + written = p - pchBuf; + g_coord.X += (SHORT)mb_string2cells(pchBuf, written); + } + + while (g_coord.X > g_srScrollRegion.Right) + { + g_coord.X -= (SHORT) Columns; + if (g_coord.Y < g_srScrollRegion.Bottom) + g_coord.Y++; + } + + gotoxy(g_coord.X + 1, g_coord.Y + 1); + + return written; + } + + + /* + * mch_write(): write the output buffer to the screen, translating ESC + * sequences into calls to console output routines. + */ + void + mch_write( + char_u *s, + int len) + { ++# ifdef VIMDLL ++ if (gui.in_use) ++ return; ++# endif ++ + s[len] = NUL; + + if (!term_console) + { + write(1, s, (unsigned)len); + return; + } + + /* translate ESC | sequences into faked bios calls */ + while (len--) + { + /* optimization: use one single write_chars for runs of text, + * rather than once per character It ain't curses, but it helps. */ + DWORD prefix = (DWORD)strcspn((char *)s, "\n\r\b\a\033"); + + if (p_wd) + { + WaitForChar(p_wd, FALSE); + if (prefix != 0) + prefix = 1; + } + + if (prefix != 0) + { + DWORD nWritten; + + nWritten = write_chars(s, prefix); + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fputc('>', fdDump); + fwrite(s, sizeof(char_u), nWritten, fdDump); + fputs("<\n", fdDump); + } + #endif + len -= (nWritten - 1); + s += nWritten; + } + else if (s[0] == '\n') + { + /* \n, newline: go to the beginning of the next line or scroll */ + if (g_coord.Y == g_srScrollRegion.Bottom) + { + scroll(1); + gotoxy(g_srScrollRegion.Left + 1, g_srScrollRegion.Bottom + 1); + } + else + { + gotoxy(g_srScrollRegion.Left + 1, g_coord.Y + 2); + } + #ifdef MCH_WRITE_DUMP + if (fdDump) + fputs("\\n\n", fdDump); + #endif + s++; + } + else if (s[0] == '\r') + { + /* \r, carriage return: go to beginning of line */ + gotoxy(g_srScrollRegion.Left+1, g_coord.Y + 1); + #ifdef MCH_WRITE_DUMP + if (fdDump) + fputs("\\r\n", fdDump); + #endif + s++; + } + else if (s[0] == '\b') + { + /* \b, backspace: move cursor one position left */ + if (g_coord.X > g_srScrollRegion.Left) + g_coord.X--; + else if (g_coord.Y > g_srScrollRegion.Top) + { + g_coord.X = g_srScrollRegion.Right; + g_coord.Y--; + } + gotoxy(g_coord.X + 1, g_coord.Y + 1); + #ifdef MCH_WRITE_DUMP + if (fdDump) + fputs("\\b\n", fdDump); + #endif + s++; + } + else if (s[0] == '\a') + { + /* \a, bell */ + MessageBeep(0xFFFFFFFF); + #ifdef MCH_WRITE_DUMP + if (fdDump) + fputs("\\a\n", fdDump); + #endif + s++; + } + else if (s[0] == ESC && len >= 3-1 && s[1] == '|') + { + #ifdef MCH_WRITE_DUMP + char_u *old_s = s; + #endif + char_u *p; + int arg1 = 0, arg2 = 0, argc = 0, args[16]; + + switch (s[2]) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + p = s + 1; + do + { + ++p; + args[argc] = getdigits(&p); + argc += (argc < 15) ? 1 : 0; + if (p > s + len) + break; + } while (*p == ';'); + + if (p > s + len) + break; + + arg1 = args[0]; + arg2 = args[1]; + if (*p == 'm') + { + if (argc == 1 && args[0] == 0) + normvideo(); + else if (argc == 1) + { + if (USE_VTP) + textcolor((WORD) arg1); + else + textattr((WORD) arg1); + } + else if (USE_VTP) + vtp_sgr_bulks(argc, args); + } + else if (argc == 2 && *p == 'H') + { + gotoxy(arg2, arg1); + } + else if (argc == 2 && *p == 'r') + { + set_scroll_region(0, arg1 - 1, Columns - 1, arg2 - 1); + } + else if (argc == 2 && *p == 'R') + { + set_scroll_region_tb(arg1, arg2); + } + else if (argc == 2 && *p == 'V') + { + set_scroll_region_lr(arg1, arg2); + } + else if (argc == 1 && *p == 'A') + { + gotoxy(g_coord.X + 1, + max(g_srScrollRegion.Top, g_coord.Y - arg1) + 1); + } + else if (argc == 1 && *p == 'b') + { + textbackground((WORD) arg1); + } + else if (argc == 1 && *p == 'C') + { + gotoxy(min(g_srScrollRegion.Right, g_coord.X + arg1) + 1, + g_coord.Y + 1); + } + else if (argc == 1 && *p == 'f') + { + textcolor((WORD) arg1); + } + else if (argc == 1 && *p == 'H') + { + gotoxy(1, arg1); + } + else if (argc == 1 && *p == 'L') + { + insert_lines(arg1); + } + else if (argc == 1 && *p == 'M') + { + delete_lines(arg1); + } + + len -= (int)(p - s); + s = p + 1; + break; + + case 'A': + gotoxy(g_coord.X + 1, + max(g_srScrollRegion.Top, g_coord.Y - 1) + 1); + goto got3; + + case 'B': + visual_bell(); + goto got3; + + case 'C': + gotoxy(min(g_srScrollRegion.Right, g_coord.X + 1) + 1, + g_coord.Y + 1); + goto got3; + + case 'E': + termcap_mode_end(); + goto got3; + + case 'F': + standout(); + goto got3; + + case 'f': + standend(); + goto got3; + + case 'H': + gotoxy(1, 1); + goto got3; + + case 'j': + clear_to_end_of_display(); + goto got3; + + case 'J': + clear_screen(); + goto got3; + + case 'K': + clear_to_end_of_line(); + goto got3; + + case 'L': + insert_lines(1); + goto got3; + + case 'M': + delete_lines(1); + goto got3; + + case 'S': + termcap_mode_start(); + goto got3; + + case 'V': + cursor_visible(TRUE); + goto got3; + + case 'v': + cursor_visible(FALSE); + goto got3; + + got3: + s += 3; + len -= 2; + } + + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fputs("ESC | ", fdDump); + fwrite(old_s + 2, sizeof(char_u), s - old_s - 2, fdDump); + fputc('\n', fdDump); + } + #endif + } + else + { + /* Write a single character */ + DWORD nWritten; + + nWritten = write_chars(s, 1); + #ifdef MCH_WRITE_DUMP + if (fdDump) + { + fputc('>', fdDump); + fwrite(s, sizeof(char_u), nWritten, fdDump); + fputs("<\n", fdDump); + } + #endif + + len -= (nWritten - 1); + s += nWritten; + } + } + + #ifdef MCH_WRITE_DUMP + if (fdDump) + fflush(fdDump); + #endif + } + + #endif /* FEAT_GUI_MSWIN */ + + + /* + * Delay for "msec" milliseconds. + */ + void + mch_delay( + long msec, + int ignoreinput UNUSED) + { +-#ifdef FEAT_GUI_MSWIN ++#if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL) + Sleep((int)msec); /* never wait for input */ + #else /* Console */ ++# ifdef VIMDLL ++ if (gui.in_use) ++ { ++ Sleep((int)msec); /* never wait for input */ ++ return; ++ } ++# endif + if (ignoreinput) + # ifdef FEAT_MZSCHEME + if (mzthreads_allowed() && p_mzq > 0 && msec > p_mzq) + { + int towait = p_mzq; + + /* if msec is large enough, wait by portions in p_mzq */ + while (msec > 0) + { + mzvim_check_threads(); + if (msec < towait) + towait = msec; + Sleep(towait); + msec -= towait; + } + } + else + # endif + Sleep((int)msec); + else + WaitForChar(msec, FALSE); + #endif + } + + + /* + * This version of remove is not scared by a readonly (backup) file. + * This can also remove a symbolic link like Unix. + * Return 0 for success, -1 for failure. + */ + int + mch_remove(char_u *name) + { + WCHAR *wn; + int n; + + /* + * On Windows, deleting a directory's symbolic link is done by + * RemoveDirectory(): mch_rmdir. It seems unnatural, but it is fact. + */ + if (mch_isdir(name) && mch_is_symbolic_link(name)) + return mch_rmdir(name); + + win32_setattrs(name, FILE_ATTRIBUTE_NORMAL); + + wn = enc_to_utf16(name, NULL); + if (wn == NULL) + return -1; + + n = DeleteFileW(wn) ? 0 : -1; + vim_free(wn); + return n; + } + + + /* + * Check for an "interrupt signal": CTRL-break or CTRL-C. + */ + void + mch_breakcheck(int force) + { +-#ifndef FEAT_GUI_MSWIN /* never used */ +- if (g_fCtrlCPressed || g_fCBrkPressed) +- { +- ctrl_break_was_pressed = g_fCBrkPressed; +- g_fCtrlCPressed = g_fCBrkPressed = FALSE; +- got_int = TRUE; +- } ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) ++# ifdef VIMDLL ++ if (!gui.in_use) ++# endif ++ if (g_fCtrlCPressed || g_fCBrkPressed) ++ { ++ ctrl_break_was_pressed = g_fCBrkPressed; ++ g_fCtrlCPressed = g_fCBrkPressed = FALSE; ++ got_int = TRUE; ++ } + #endif + } + + /* physical RAM to leave for the OS */ + #define WINNT_RESERVE_BYTES (256*1024*1024) + + /* + * How much main memory in KiB that can be used by VIM. + */ + long_u + mch_total_mem(int special UNUSED) + { + MEMORYSTATUSEX ms; + + /* Need to use GlobalMemoryStatusEx() when there is more memory than +- * what fits in 32 bits. But it's not always available. */ ++ * what fits in 32 bits. */ + ms.dwLength = sizeof(MEMORYSTATUSEX); + GlobalMemoryStatusEx(&ms); + if (ms.ullAvailVirtual < ms.ullTotalPhys) + { + /* Process address space fits in physical RAM, use all of it. */ + return (long_u)(ms.ullAvailVirtual / 1024); + } + if (ms.ullTotalPhys <= WINNT_RESERVE_BYTES) + { + /* Catch old NT box or perverse hardware setup. */ + return (long_u)((ms.ullTotalPhys / 2) / 1024); + } + /* Use physical RAM less reserve for OS + data. */ + return (long_u)((ms.ullTotalPhys - WINNT_RESERVE_BYTES) / 1024); + } + + /* + * mch_wrename() works around a bug in rename (aka MoveFile) in + * Windows 95: rename("foo.bar", "foo.bar~") will generate a + * file whose short file name is "FOO.BAR" (its long file name will + * be correct: "foo.bar~"). Because a file can be accessed by + * either its SFN or its LFN, "foo.bar" has effectively been + * renamed to "foo.bar", which is not at all what was wanted. This + * seems to happen only when renaming files with three-character + * extensions by appending a suffix that does not include ".". + * Windows NT gets it right, however, with an SFN of "FOO~1.BAR". + * + * There is another problem, which isn't really a bug but isn't right either: + * When renaming "abcdef~1.txt" to "abcdef~1.txt~", the short name can be + * "abcdef~1.txt" again. This has been reported on Windows NT 4.0 with + * service pack 6. Doesn't seem to happen on Windows 98. + * + * Like rename(), returns 0 upon success, non-zero upon failure. + * Should probably set errno appropriately when errors occur. + */ + int + mch_wrename(WCHAR *wold, WCHAR *wnew) + { + WCHAR *p; + int i; + WCHAR szTempFile[_MAX_PATH + 1]; + WCHAR szNewPath[_MAX_PATH + 1]; + HANDLE hf; + + // No need to play tricks unless the file name contains a "~" as the + // seventh character. + p = wold; + for (i = 0; wold[i] != NUL; ++i) + if ((wold[i] == '/' || wold[i] == '\\' || wold[i] == ':') + && wold[i + 1] != 0) + p = wold + i + 1; + if ((int)(wold + i - p) < 8 || p[6] != '~') + return (MoveFileW(wold, wnew) == 0); + + // Get base path of new file name. Undocumented feature: If pszNewFile is + // a directory, no error is returned and pszFilePart will be NULL. + if (GetFullPathNameW(wnew, _MAX_PATH, szNewPath, &p) == 0 || p == NULL) + return -1; + *p = NUL; + + // Get (and create) a unique temporary file name in directory of new file + if (GetTempFileNameW(szNewPath, L"VIM", 0, szTempFile) == 0) + return -2; + + // blow the temp file away + if (!DeleteFileW(szTempFile)) + return -3; + + // rename old file to the temp file + if (!MoveFileW(wold, szTempFile)) + return -4; + + // now create an empty file called pszOldFile; this prevents the operating + // system using pszOldFile as an alias (SFN) if we're renaming within the + // same directory. For example, we're editing a file called + // filename.asc.txt by its SFN, filena~1.txt. If we rename filena~1.txt + // to filena~1.txt~ (i.e., we're making a backup while writing it), the + // SFN for filena~1.txt~ will be filena~1.txt, by default, which will + // cause all sorts of problems later in buf_write(). So, we create an + // empty file called filena~1.txt and the system will have to find some + // other SFN for filena~1.txt~, such as filena~2.txt + if ((hf = CreateFileW(wold, GENERIC_WRITE, 0, NULL, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) + return -5; + if (!CloseHandle(hf)) + return -6; + + // rename the temp file to the new file + if (!MoveFileW(szTempFile, wnew)) + { + // Renaming failed. Rename the file back to its old name, so that it + // looks like nothing happened. + (void)MoveFileW(szTempFile, wold); + return -7; + } + + // Seems to be left around on Novell filesystems + DeleteFileW(szTempFile); + + // finally, remove the empty old file + if (!DeleteFileW(wold)) + return -8; + + return 0; + } + + + /* + * Converts the filenames to UTF-16, then call mch_wrename(). + * Like rename(), returns 0 upon success, non-zero upon failure. + */ + int + mch_rename( + const char *pszOldFile, + const char *pszNewFile) + { + WCHAR *wold = NULL; + WCHAR *wnew = NULL; + int retval = -1; + + wold = enc_to_utf16((char_u *)pszOldFile, NULL); + wnew = enc_to_utf16((char_u *)pszNewFile, NULL); + if (wold != NULL && wnew != NULL) + retval = mch_wrename(wold, wnew); + vim_free(wold); + vim_free(wnew); + return retval; + } + + /* + * Get the default shell for the current hardware platform + */ + char * + default_shell(void) + { + return "cmd.exe"; + } + + /* + * mch_access() extends access() to do more detailed check on network drives. + * Returns 0 if file "n" has access rights according to "p", -1 otherwise. + */ + int + mch_access(char *n, int p) + { + HANDLE hFile; + int retval = -1; /* default: fail */ + WCHAR *wn; + + wn = enc_to_utf16((char_u *)n, NULL); + if (wn == NULL) + return -1; + + if (mch_isdir((char_u *)n)) + { + WCHAR TempNameW[_MAX_PATH + 16] = L""; + + if (p & R_OK) + { + /* Read check is performed by seeing if we can do a find file on + * the directory for any file. */ + int i; + WIN32_FIND_DATAW d; + + for (i = 0; i < _MAX_PATH && wn[i] != 0; ++i) + TempNameW[i] = wn[i]; + if (TempNameW[i - 1] != '\\' && TempNameW[i - 1] != '/') + TempNameW[i++] = '\\'; + TempNameW[i++] = '*'; + TempNameW[i++] = 0; + + hFile = FindFirstFileW(TempNameW, &d); + if (hFile == INVALID_HANDLE_VALUE) + goto getout; + else + (void)FindClose(hFile); + } + + if (p & W_OK) + { + /* Trying to create a temporary file in the directory should catch + * directories on read-only network shares. However, in + * directories whose ACL allows writes but denies deletes will end + * up keeping the temporary file :-(. */ + if (!GetTempFileNameW(wn, L"VIM", 0, TempNameW)) + goto getout; + else + DeleteFileW(TempNameW); + } + } + else + { + // Don't consider a file read-only if another process has opened it. + DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + /* Trying to open the file for the required access does ACL, read-only + * network share, and file attribute checks. */ + DWORD access_mode = ((p & W_OK) ? GENERIC_WRITE : 0) + | ((p & R_OK) ? GENERIC_READ : 0); + + hFile = CreateFileW(wn, access_mode, share_mode, + NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) + goto getout; + CloseHandle(hFile); + } + + retval = 0; /* success */ + getout: + vim_free(wn); + return retval; + } + + /* + * Version of open() that may use UTF-16 file name. + */ + int + mch_open(const char *name, int flags, int mode) + { + /* _wopen() does not work with Borland C 5.5: creates a read-only file. */ + #ifndef __BORLANDC__ + WCHAR *wn; + int f; + + wn = enc_to_utf16((char_u *)name, NULL); + if (wn == NULL) + return -1; + + f = _wopen(wn, flags, mode); + vim_free(wn); + return f; + #else + /* open() can open a file which name is longer than _MAX_PATH bytes + * and shorter than _MAX_PATH characters successfully, but sometimes it + * causes unexpected error in another part. We make it an error explicitly + * here. */ + if (strlen(name) >= _MAX_PATH) + return -1; + + return open(name, flags, mode); + #endif + } + + /* + * Version of fopen() that uses UTF-16 file name. + */ + FILE * + mch_fopen(const char *name, const char *mode) + { + WCHAR *wn, *wm; + FILE *f = NULL; + + #if defined(DEBUG) && _MSC_VER >= 1400 + /* Work around an annoying assertion in the Microsoft debug CRT + * when mode's text/binary setting doesn't match _get_fmode(). */ + char newMode = mode[strlen(mode) - 1]; + int oldMode = 0; + + _get_fmode(&oldMode); + if (newMode == 't') + _set_fmode(_O_TEXT); + else if (newMode == 'b') + _set_fmode(_O_BINARY); + #endif + wn = enc_to_utf16((char_u *)name, NULL); + wm = enc_to_utf16((char_u *)mode, NULL); + if (wn != NULL && wm != NULL) + f = _wfopen(wn, wm); + vim_free(wn); + vim_free(wm); + + #if defined(DEBUG) && _MSC_VER >= 1400 + _set_fmode(oldMode); + #endif + return f; + } + + /* + * SUB STREAM (aka info stream) handling: + * + * NTFS can have sub streams for each file. Normal contents of file is + * stored in the main stream, and extra contents (author information and + * title and so on) can be stored in sub stream. After Windows 2000, user + * can access and store those informations in sub streams via explorer's + * property menuitem in right click menu. Those informations in sub streams + * were lost when copying only the main stream. So we have to copy sub + * streams. + * + * Incomplete explanation: + * http://msdn.microsoft.com/library/en-us/dnw2k/html/ntfs5.asp + * More useful info and an example: + * http://www.sysinternals.com/ntw2k/source/misc.shtml#streams + */ + + /* + * Copy info stream data "substream". Read from the file with BackupRead(sh) + * and write to stream "substream" of file "to". + * Errors are ignored. + */ + static void + copy_substream(HANDLE sh, void *context, WCHAR *to, WCHAR *substream, long len) + { + HANDLE hTo; + WCHAR *to_name; + + to_name = malloc((wcslen(to) + wcslen(substream) + 1) * sizeof(WCHAR)); + wcscpy(to_name, to); + wcscat(to_name, substream); + + hTo = CreateFileW(to_name, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hTo != INVALID_HANDLE_VALUE) + { + long done; + DWORD todo; + DWORD readcnt, written; + char buf[4096]; + + /* Copy block of bytes at a time. Abort when something goes wrong. */ + for (done = 0; done < len; done += written) + { + /* (size_t) cast for Borland C 5.5 */ + todo = (DWORD)((size_t)(len - done) > sizeof(buf) ? sizeof(buf) + : (size_t)(len - done)); + if (!BackupRead(sh, (LPBYTE)buf, todo, &readcnt, + FALSE, FALSE, context) + || readcnt != todo + || !WriteFile(hTo, buf, todo, &written, NULL) + || written != todo) + break; + } + CloseHandle(hTo); + } + + free(to_name); + } + + /* + * Copy info streams from file "from" to file "to". + */ + static void + copy_infostreams(char_u *from, char_u *to) + { + WCHAR *fromw; + WCHAR *tow; + HANDLE sh; + WIN32_STREAM_ID sid; + int headersize; + WCHAR streamname[_MAX_PATH]; + DWORD readcount; + void *context = NULL; + DWORD lo, hi; + int len; + + /* Convert the file names to wide characters. */ + fromw = enc_to_utf16(from, NULL); + tow = enc_to_utf16(to, NULL); + if (fromw != NULL && tow != NULL) + { + /* Open the file for reading. */ + sh = CreateFileW(fromw, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (sh != INVALID_HANDLE_VALUE) + { + /* Use BackupRead() to find the info streams. Repeat until we + * have done them all.*/ + for (;;) + { + /* Get the header to find the length of the stream name. If + * the "readcount" is zero we have done all info streams. */ + ZeroMemory(&sid, sizeof(WIN32_STREAM_ID)); + headersize = (int)((char *)&sid.cStreamName - (char *)&sid.dwStreamId); + if (!BackupRead(sh, (LPBYTE)&sid, headersize, + &readcount, FALSE, FALSE, &context) + || readcount == 0) + break; + + /* We only deal with streams that have a name. The normal + * file data appears to be without a name, even though docs + * suggest it is called "::$DATA". */ + if (sid.dwStreamNameSize > 0) + { + /* Read the stream name. */ + if (!BackupRead(sh, (LPBYTE)streamname, + sid.dwStreamNameSize, + &readcount, FALSE, FALSE, &context)) + break; + + /* Copy an info stream with a name ":anything:$DATA". + * Skip "::$DATA", it has no stream name (examples suggest + * it might be used for the normal file contents). + * Note that BackupRead() counts bytes, but the name is in + * wide characters. */ + len = readcount / sizeof(WCHAR); + streamname[len] = 0; + if (len > 7 && wcsicmp(streamname + len - 6, + L":$DATA") == 0) + { + streamname[len - 6] = 0; + copy_substream(sh, &context, tow, streamname, + (long)sid.Size.u.LowPart); + } + } + + /* Advance to the next stream. We might try seeking too far, + * but BackupSeek() doesn't skip over stream borders, thus + * that's OK. */ + (void)BackupSeek(sh, sid.Size.u.LowPart, sid.Size.u.HighPart, + &lo, &hi, &context); + } + + /* Clear the context. */ + (void)BackupRead(sh, NULL, 0, &readcount, TRUE, FALSE, &context); + + CloseHandle(sh); + } + } + vim_free(fromw); + vim_free(tow); + } + + /* + * Copy file attributes from file "from" to file "to". + * For Windows NT and later we copy info streams. + * Always returns zero, errors are ignored. + */ + int + mch_copy_file_attribute(char_u *from, char_u *to) + { + /* File streams only work on Windows NT and later. */ + copy_infostreams(from, to); + return 0; + } + + #if defined(MYRESETSTKOFLW) || defined(PROTO) + /* + * Recreate a destroyed stack guard page in win32. + * Written by Benjamin Peterson. + */ + + /* These magic numbers are from the MS header files */ + # define MIN_STACK_WINNT 2 + + /* + * This function does the same thing as _resetstkoflw(), which is only + * available in DevStudio .net and later. + * Returns 0 for failure, 1 for success. + */ + int + myresetstkoflw(void) + { + BYTE *pStackPtr; + BYTE *pGuardPage; + BYTE *pStackBase; + BYTE *pLowestPossiblePage; + MEMORY_BASIC_INFORMATION mbi; + SYSTEM_INFO si; + DWORD nPageSize; + DWORD dummy; + + /* We need to know the system page size. */ + GetSystemInfo(&si); + nPageSize = si.dwPageSize; + + /* ...and the current stack pointer */ + pStackPtr = (BYTE*)_alloca(1); + + /* ...and the base of the stack. */ + if (VirtualQuery(pStackPtr, &mbi, sizeof mbi) == 0) + return 0; + pStackBase = (BYTE*)mbi.AllocationBase; + + /* ...and the page thats min_stack_req pages away from stack base; this is + * the lowest page we could use. */ + pLowestPossiblePage = pStackBase + MIN_STACK_WINNT * nPageSize; + + { + /* We want the first committed page in the stack Start at the stack + * base and move forward through memory until we find a committed block. + */ + BYTE *pBlock = pStackBase; + + for (;;) + { + if (VirtualQuery(pBlock, &mbi, sizeof mbi) == 0) + return 0; + + pBlock += mbi.RegionSize; + + if (mbi.State & MEM_COMMIT) + break; + } + + /* mbi now describes the first committed block in the stack. */ + if (mbi.Protect & PAGE_GUARD) + return 1; + + /* decide where the guard page should start */ + if ((long_u)(mbi.BaseAddress) < (long_u)pLowestPossiblePage) + pGuardPage = pLowestPossiblePage; + else + pGuardPage = (BYTE*)mbi.BaseAddress; + + /* allocate the guard page */ + if (!VirtualAlloc(pGuardPage, nPageSize, MEM_COMMIT, PAGE_READWRITE)) + return 0; + + /* apply the guard attribute to the page */ + if (!VirtualProtect(pGuardPage, nPageSize, PAGE_READWRITE | PAGE_GUARD, + &dummy)) + return 0; + } + + return 1; + } + #endif + + + /* + * The command line arguments in UCS2 + */ + static int nArgsW = 0; + static LPWSTR *ArglistW = NULL; + static int global_argc = 0; + static char **global_argv; + + static int used_file_argc = 0; /* last argument in global_argv[] used + for the argument list. */ + static int *used_file_indexes = NULL; /* indexes in global_argv[] for + command line arguments added to + the argument list */ + static int used_file_count = 0; /* nr of entries in used_file_indexes */ + static int used_file_literal = FALSE; /* take file names literally */ + static int used_file_full_path = FALSE; /* file name was full path */ + static int used_file_diff_mode = FALSE; /* file name was with diff mode */ + static int used_alist_count = 0; + + + /* + * Get the command line arguments. Unicode version. + * Returns argc. Zero when something fails. + */ + int + get_cmd_argsW(char ***argvp) + { + char **argv = NULL; + int argc = 0; + int i; + + free_cmd_argsW(); + ArglistW = CommandLineToArgvW(GetCommandLineW(), &nArgsW); + if (ArglistW != NULL) + { + argv = malloc((nArgsW + 1) * sizeof(char *)); + if (argv != NULL) + { + argc = nArgsW; + argv[argc] = NULL; + for (i = 0; i < argc; ++i) + { + int len; + + /* Convert each Unicode argument to the current codepage. */ + WideCharToMultiByte_alloc(GetACP(), 0, + ArglistW[i], (int)wcslen(ArglistW[i]) + 1, + (LPSTR *)&argv[i], &len, 0, 0); + if (argv[i] == NULL) + { + /* Out of memory, clear everything. */ + while (i > 0) + free(argv[--i]); + free(argv); + argv = NULL; + argc = 0; + } + } + } + } + + global_argc = argc; + global_argv = argv; + if (argc > 0) + { + if (used_file_indexes != NULL) + free(used_file_indexes); + used_file_indexes = malloc(argc * sizeof(int)); + } + + if (argvp != NULL) + *argvp = argv; + return argc; + } + + void + free_cmd_argsW(void) + { + if (ArglistW != NULL) + { + GlobalFree(ArglistW); + ArglistW = NULL; + } + } + + /* + * Remember "name" is an argument that was added to the argument list. + * This avoids that we have to re-parse the argument list when fix_arg_enc() + * is called. + */ + void + used_file_arg(char *name, int literal, int full_path, int diff_mode) + { + int i; + + if (used_file_indexes == NULL) + return; + for (i = used_file_argc + 1; i < global_argc; ++i) + if (STRCMP(global_argv[i], name) == 0) + { + used_file_argc = i; + used_file_indexes[used_file_count++] = i; + break; + } + used_file_literal = literal; + used_file_full_path = full_path; + used_file_diff_mode = diff_mode; + } + + /* + * Remember the length of the argument list as it was. If it changes then we + * leave it alone when 'encoding' is set. + */ + void + set_alist_count(void) + { + used_alist_count = GARGCOUNT; + } + + /* + * Fix the encoding of the command line arguments. Invoked when 'encoding' + * has been changed while starting up. Use the UCS-2 command line arguments + * and convert them to 'encoding'. + */ + void + fix_arg_enc(void) + { + int i; + int idx; + char_u *str; + int *fnum_list; + + /* Safety checks: + * - if argument count differs between the wide and non-wide argument + * list, something must be wrong. + * - the file name arguments must have been located. + * - the length of the argument list wasn't changed by the user. + */ + if (global_argc != nArgsW + || ArglistW == NULL + || used_file_indexes == NULL + || used_file_count == 0 + || used_alist_count != GARGCOUNT) + return; + + /* Remember the buffer numbers for the arguments. */ + fnum_list = (int *)alloc((int)sizeof(int) * GARGCOUNT); + if (fnum_list == NULL) + return; /* out of memory */ + for (i = 0; i < GARGCOUNT; ++i) + fnum_list[i] = GARGLIST[i].ae_fnum; + + /* Clear the argument list. Make room for the new arguments. */ + alist_clear(&global_alist); + if (ga_grow(&global_alist.al_ga, used_file_count) == FAIL) + return; /* out of memory */ + + for (i = 0; i < used_file_count; ++i) + { + idx = used_file_indexes[i]; + str = utf16_to_enc(ArglistW[idx], NULL); + if (str != NULL) + { + int literal = used_file_literal; + + # ifdef FEAT_DIFF + /* When using diff mode may need to concatenate file name to + * directory name. Just like it's done in main(). */ + if (used_file_diff_mode && mch_isdir(str) && GARGCOUNT > 0 + && !mch_isdir(alist_name(&GARGLIST[0]))) + { + char_u *r; + + r = concat_fnames(str, gettail(alist_name(&GARGLIST[0])), TRUE); + if (r != NULL) + { + vim_free(str); + str = r; + } + } + # endif + /* Re-use the old buffer by renaming it. When not using literal + * names it's done by alist_expand() below. */ + if (used_file_literal) + buf_set_name(fnum_list[i], str); + + /* Check backtick literal. backtick literal is already expanded in + * main.c, so this part add str as literal. */ + if (literal == FALSE) + { + size_t len = STRLEN(str); + + if (len > 2 && *str == '`' && *(str + len - 1) == '`') + literal = TRUE; + } + alist_add(&global_alist, str, literal ? 2 : 0); + } + } + + if (!used_file_literal) + { + /* Now expand wildcards in the arguments. */ + /* Temporarily add '(' and ')' to 'isfname'. These are valid + * filename characters but are excluded from 'isfname' to make + * "gf" work on a file name in parenthesis (e.g.: see vim.h). + * Also, unset wildignore to not be influenced by this option. + * The arguments specified in command-line should be kept even if + * encoding options were changed. */ + do_cmdline_cmd((char_u *)":let SaVe_ISF = &isf|set isf+=(,)"); + do_cmdline_cmd((char_u *)":let SaVe_WIG = &wig|set wig="); + alist_expand(fnum_list, used_alist_count); + do_cmdline_cmd((char_u *)":let &isf = SaVe_ISF|unlet SaVe_ISF"); + do_cmdline_cmd((char_u *)":let &wig = SaVe_WIG|unlet SaVe_WIG"); + } + + /* If wildcard expansion failed, we are editing the first file of the + * arglist and there is no file name: Edit the first argument now. */ + if (curwin->w_arg_idx == 0 && curbuf->b_fname == NULL) + { + do_cmdline_cmd((char_u *)":rewind"); + if (GARGCOUNT == 1 && used_file_full_path) + (void)vim_chdirfile(alist_name(&GARGLIST[0]), "drop"); + } + + set_alist_count(); + } + + int + mch_setenv(char *var, char *value, int x) + { + char_u *envbuf; + WCHAR *p; + + envbuf = alloc((unsigned)(STRLEN(var) + STRLEN(value) + 2)); + if (envbuf == NULL) + return -1; + + sprintf((char *)envbuf, "%s=%s", var, value); + + p = enc_to_utf16(envbuf, NULL); + + vim_free(envbuf); + if (p == NULL) + return -1; + _wputenv(p); + #ifdef libintl_wputenv + libintl_wputenv(p); + #endif + // Unlike Un*x systems, we can free the string for _wputenv(). + vim_free(p); + + return 0; + } + + /* + * Support for 256 colors and 24-bit colors was added in Windows 10 + * version 1703 (Creators update). + */ + #define VTP_FIRST_SUPPORT_BUILD MAKE_VER(10, 0, 15063) + + /* + * Support for pseudo-console (ConPTY) was added in windows 10 + * version 1809 (October 2018 update). However, that version is unstable. + */ + #define CONPTY_FIRST_SUPPORT_BUILD MAKE_VER(10, 0, 17763) + #define CONPTY_STABLE_BUILD MAKE_VER(10, 0, 32767) // T.B.D. + + static void + vtp_flag_init(void) + { + DWORD ver = get_build_number(); +-#ifndef FEAT_GUI_MSWIN ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) + DWORD mode; + HANDLE out; + +- out = GetStdHandle(STD_OUTPUT_HANDLE); ++# ifdef VIMDLL ++ if (!gui.in_use) ++# endif ++ { ++ out = GetStdHandle(STD_OUTPUT_HANDLE); + +- vtp_working = (ver >= VTP_FIRST_SUPPORT_BUILD) ? 1 : 0; +- GetConsoleMode(out, &mode); +- mode |= (ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); +- if (SetConsoleMode(out, mode) == 0) +- vtp_working = 0; ++ vtp_working = (ver >= VTP_FIRST_SUPPORT_BUILD) ? 1 : 0; ++ GetConsoleMode(out, &mode); ++ mode |= (ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); ++ if (SetConsoleMode(out, mode) == 0) ++ vtp_working = 0; ++ } + #endif + + if (ver >= CONPTY_FIRST_SUPPORT_BUILD) + conpty_working = 1; + if (ver >= CONPTY_STABLE_BUILD) + conpty_stable = 1; + + } + +-#if !defined(FEAT_GUI_MSWIN) || defined(PROTO) ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) || defined(PROTO) + + static void + vtp_init(void) + { + HMODULE hKerneldll; + DYN_CONSOLE_SCREEN_BUFFER_INFOEX csbi; + # ifdef FEAT_TERMGUICOLORS + COLORREF fg, bg; + # endif + + /* Use functions supported from Vista */ + hKerneldll = GetModuleHandle("kernel32.dll"); + if (hKerneldll != NULL) + { + pGetConsoleScreenBufferInfoEx = + (PfnGetConsoleScreenBufferInfoEx)GetProcAddress( + hKerneldll, "GetConsoleScreenBufferInfoEx"); + pSetConsoleScreenBufferInfoEx = + (PfnSetConsoleScreenBufferInfoEx)GetProcAddress( + hKerneldll, "SetConsoleScreenBufferInfoEx"); + if (pGetConsoleScreenBufferInfoEx != NULL + && pSetConsoleScreenBufferInfoEx != NULL) + has_csbiex = TRUE; + } + + csbi.cbSize = sizeof(csbi); + if (has_csbiex) + pGetConsoleScreenBufferInfoEx(g_hConOut, &csbi); + save_console_bg_rgb = (guicolor_T)csbi.ColorTable[g_color_index_bg]; + save_console_fg_rgb = (guicolor_T)csbi.ColorTable[g_color_index_fg]; + + # ifdef FEAT_TERMGUICOLORS + bg = (COLORREF)csbi.ColorTable[g_color_index_bg]; + fg = (COLORREF)csbi.ColorTable[g_color_index_fg]; + bg = (GetRValue(bg) << 16) | (GetGValue(bg) << 8) | GetBValue(bg); + fg = (GetRValue(fg) << 16) | (GetGValue(fg) << 8) | GetBValue(fg); + default_console_color_bg = bg; + default_console_color_fg = fg; + # endif + + set_console_color_rgb(); + } + + static void + vtp_exit(void) + { + reset_console_color_rgb(); + } + + static int + vtp_printf( + char *format, + ...) + { + char_u buf[100]; + va_list list; + DWORD result; + + va_start(list, format); + vim_vsnprintf((char *)buf, 100, (char *)format, list); + va_end(list); + WriteConsoleA(g_hConOut, buf, (DWORD)STRLEN(buf), &result, NULL); + return (int)result; + } + + static void + vtp_sgr_bulk( + int arg) + { + int args[1]; + + args[0] = arg; + vtp_sgr_bulks(1, args); + } + + static void + vtp_sgr_bulks( + int argc, + int *args + ) + { + /* 2('\033[') + 4('255.') * 16 + NUL */ + char_u buf[2 + (4 * 16) + 1]; + char_u *p; + int i; + + p = buf; + *p++ = '\033'; + *p++ = '['; + + for (i = 0; i < argc; ++i) + { + p += vim_snprintf((char *)p, 4, "%d", args[i] & 0xff); + *p++ = ';'; + } + p--; + *p++ = 'm'; + *p = NUL; + vtp_printf((char *)buf); + } + + # ifdef FEAT_TERMGUICOLORS + static int + ctermtoxterm( + int cterm) + { + char_u r, g, b, idx; + + cterm_color2rgb(cterm, &r, &g, &b, &idx); + return (((int)r << 16) | ((int)g << 8) | (int)b); + } + # endif + + static void + set_console_color_rgb(void) + { + # ifdef FEAT_TERMGUICOLORS + DYN_CONSOLE_SCREEN_BUFFER_INFOEX csbi; + int id; + guicolor_T fg = INVALCOLOR; + guicolor_T bg = INVALCOLOR; + int ctermfg; + int ctermbg; + + if (!USE_VTP) + return; + + id = syn_name2id((char_u *)"Normal"); + if (id > 0 && p_tgc) + syn_id2colors(id, &fg, &bg); + if (fg == INVALCOLOR) + { + ctermfg = -1; + if (id > 0) + syn_id2cterm_bg(id, &ctermfg, &ctermbg); + fg = ctermfg != -1 ? ctermtoxterm(ctermfg) : default_console_color_fg; + cterm_normal_fg_gui_color = fg; + } + if (bg == INVALCOLOR) + { + ctermbg = -1; + if (id > 0) + syn_id2cterm_bg(id, &ctermfg, &ctermbg); + bg = ctermbg != -1 ? ctermtoxterm(ctermbg) : default_console_color_bg; + cterm_normal_bg_gui_color = bg; + } + fg = (GetRValue(fg) << 16) | (GetGValue(fg) << 8) | GetBValue(fg); + bg = (GetRValue(bg) << 16) | (GetGValue(bg) << 8) | GetBValue(bg); + + csbi.cbSize = sizeof(csbi); + if (has_csbiex) + pGetConsoleScreenBufferInfoEx(g_hConOut, &csbi); + + csbi.cbSize = sizeof(csbi); + csbi.srWindow.Right += 1; + csbi.srWindow.Bottom += 1; + csbi.ColorTable[g_color_index_bg] = (COLORREF)bg; + csbi.ColorTable[g_color_index_fg] = (COLORREF)fg; + if (has_csbiex) + pSetConsoleScreenBufferInfoEx(g_hConOut, &csbi); + # endif + } + + static void + reset_console_color_rgb(void) + { + # ifdef FEAT_TERMGUICOLORS + DYN_CONSOLE_SCREEN_BUFFER_INFOEX csbi; + + csbi.cbSize = sizeof(csbi); + if (has_csbiex) + pGetConsoleScreenBufferInfoEx(g_hConOut, &csbi); + + csbi.cbSize = sizeof(csbi); + csbi.srWindow.Right += 1; + csbi.srWindow.Bottom += 1; + csbi.ColorTable[g_color_index_bg] = (COLORREF)save_console_bg_rgb; + csbi.ColorTable[g_color_index_fg] = (COLORREF)save_console_fg_rgb; + if (has_csbiex) + pSetConsoleScreenBufferInfoEx(g_hConOut, &csbi); + # endif + } + + void + control_console_color_rgb(void) + { + if (USE_VTP) + set_console_color_rgb(); + else + reset_console_color_rgb(); + } + + int + use_vtp(void) + { + return USE_VTP; + } + + int + is_term_win32(void) + { + return T_NAME != NULL && STRCMP(T_NAME, "win32") == 0; + } + + int + has_vtp_working(void) + { + return vtp_working; + } + + #endif + + int + has_conpty_working(void) + { + return conpty_working; + } + + int + is_conpty_stable(void) + { + return conpty_stable; + } + +-#if !defined(FEAT_GUI_MSWIN) || defined(PROTO) ++#if !defined(FEAT_GUI_MSWIN) || defined(VIMDLL) || defined(PROTO) + void + resize_console_buf(void) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; + COORD coord; + SMALL_RECT newsize; + + if (GetConsoleScreenBufferInfo(g_hConOut, &csbi)) + { + coord.X = SRWIDTH(csbi.srWindow); + coord.Y = SRHEIGHT(csbi.srWindow); + SetConsoleScreenBufferSize(g_hConOut, coord); + + newsize.Left = 0; + newsize.Top = 0; + newsize.Right = coord.X - 1; + newsize.Bottom = coord.Y - 1; + SetConsoleWindowInfo(g_hConOut, TRUE, &newsize); + + SetConsoleScreenBufferSize(g_hConOut, coord); + } + } + #endif diff --git a/src/test/resources/unparser/diff/error06.txt b/src/test/resources/unparser/diff/error06.txt new file mode 100644 index 00000000..1968bd8e --- /dev/null +++ b/src/test/resources/unparser/diff/error06.txt @@ -0,0 +1,4293 @@ +textDiff: + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + #include "vim.h" + + #ifdef AMIGA + # include // for time() + #endif + + /* + * Vim originated from Stevie version 3.6 (Fish disk 217) by GRWalter (Fred) + * It has been changed beyond recognition since then. + * + * Differences between version 7.4 and 8.x can be found with ":help version8". + * Differences between version 6.4 and 7.x can be found with ":help version7". + * Differences between version 5.8 and 6.x can be found with ":help version6". + * Differences between version 4.x and 5.x can be found with ":help version5". + * Differences between version 3.0 and 4.x can be found with ":help version4". + * All the remarks about older versions have been removed, they are not very + * interesting. + */ + + #include "version.h" + + char *Version = VIM_VERSION_SHORT; + static char *mediumVersion = VIM_VERSION_MEDIUM; + + #if defined(HAVE_DATE_TIME) || defined(PROTO) + # if (defined(VMS) && defined(VAXC)) || defined(PROTO) + char longVersion[sizeof(VIM_VERSION_LONG_DATE) + sizeof(__DATE__) + + sizeof(__TIME__) + 3]; + + void + init_longVersion(void) + { + /* + * Construct the long version string. Necessary because + * VAX C can't concatenate strings in the preprocessor. + */ + strcpy(longVersion, VIM_VERSION_LONG_DATE); + #ifdef BUILD_DATE + strcat(longVersion, BUILD_DATE); + #else + strcat(longVersion, __DATE__); + strcat(longVersion, " "); + strcat(longVersion, __TIME__); + #endif + strcat(longVersion, ")"); + } + + # else + void + init_longVersion(void) + { + if (longVersion == NULL) + { + #ifdef BUILD_DATE + char *date_time = BUILD_DATE; + #else + char *date_time = __DATE__ " " __TIME__; + #endif + char *msg = _("%s (%s, compiled %s)"); + size_t len = strlen(msg) + + strlen(VIM_VERSION_LONG_ONLY) + + strlen(VIM_VERSION_DATE_ONLY) + + strlen(date_time); + + longVersion = alloc(len); + if (longVersion == NULL) + longVersion = VIM_VERSION_LONG; + else + vim_snprintf(longVersion, len, msg, + VIM_VERSION_LONG_ONLY, VIM_VERSION_DATE_ONLY, date_time); + } + } + # endif + #else + char *longVersion = VIM_VERSION_LONG; + + void + init_longVersion(void) + { + // nothing to do + } + #endif + + static char *(features[]) = + { + #ifdef HAVE_ACL + "+acl", + #else + "-acl", + #endif + #ifdef AMIGA // only for Amiga systems + # ifdef FEAT_ARP + "+ARP", + # else + "-ARP", + # endif + #endif + #ifdef FEAT_ARABIC + "+arabic", + #else + "-arabic", + #endif + "+autocmd", + #ifdef FEAT_AUTOCHDIR + "+autochdir", + #else + "-autochdir", + #endif + #ifdef FEAT_AUTOSERVERNAME + "+autoservername", + #else + "-autoservername", + #endif + #ifdef FEAT_BEVAL_GUI + "+balloon_eval", + #else + "-balloon_eval", + #endif + #ifdef FEAT_BEVAL_TERM + "+balloon_eval_term", + #else + "-balloon_eval_term", + #endif + #ifdef FEAT_BROWSE + "+browse", + #else + "-browse", + #endif + #ifdef NO_BUILTIN_TCAPS + "-builtin_terms", + #endif + #ifdef SOME_BUILTIN_TCAPS + "+builtin_terms", + #endif + #ifdef ALL_BUILTIN_TCAPS + "++builtin_terms", + #endif + #ifdef FEAT_BYTEOFF + "+byte_offset", + #else + "-byte_offset", + #endif + #ifdef FEAT_JOB_CHANNEL + "+channel", + #else + "-channel", + #endif + #ifdef FEAT_CINDENT + "+cindent", + #else + "-cindent", + #endif + #ifdef FEAT_CLIENTSERVER + "+clientserver", + #else + "-clientserver", + #endif + #ifdef FEAT_CLIPBOARD + "+clipboard", + #else + "-clipboard", + #endif + "+cmdline_compl", + "+cmdline_hist", + #ifdef FEAT_CMDL_INFO + "+cmdline_info", + #else + "-cmdline_info", + #endif + "+comments", + #ifdef FEAT_CONCEAL + "+conceal", + #else + "-conceal", + #endif + #ifdef FEAT_CRYPT + "+cryptv", + #else + "-cryptv", + #endif + #ifdef FEAT_CSCOPE + "+cscope", + #else + "-cscope", + #endif + "+cursorbind", + #ifdef CURSOR_SHAPE + "+cursorshape", + #else + "-cursorshape", + #endif + #if defined(FEAT_CON_DIALOG) && defined(FEAT_GUI_DIALOG) + "+dialog_con_gui", + #else + # if defined(FEAT_CON_DIALOG) + "+dialog_con", + # else + # if defined(FEAT_GUI_DIALOG) + "+dialog_gui", + # else + "-dialog", + # endif + # endif + #endif + #ifdef FEAT_DIFF + "+diff", + #else + "-diff", + #endif + #ifdef FEAT_DIGRAPHS + "+digraphs", + #else + "-digraphs", + #endif + #ifdef FEAT_GUI_MSWIN + # ifdef FEAT_DIRECTX + "+directx", + # else + "-directx", + # endif + #endif + #ifdef FEAT_DND + "+dnd", + #else + "-dnd", + #endif + #ifdef EBCDIC + "+ebcdic", + #else + "-ebcdic", + #endif + #ifdef FEAT_EMACS_TAGS + "+emacs_tags", + #else + "-emacs_tags", + #endif + #ifdef FEAT_EVAL + "+eval", + #else + "-eval", + #endif + "+ex_extra", + #ifdef FEAT_SEARCH_EXTRA + "+extra_search", + #else + "-extra_search", + #endif + "-farsi", + #ifdef FEAT_SEARCHPATH + "+file_in_path", + #else + "-file_in_path", + #endif + #ifdef FEAT_FIND_ID + "+find_in_path", + #else + "-find_in_path", + #endif + #ifdef FEAT_FLOAT + "+float", + #else + "-float", + #endif + #ifdef FEAT_FOLDING + "+folding", + #else + "-folding", + #endif + #ifdef FEAT_FOOTER + "+footer", + #else + "-footer", + #endif + // only interesting on Unix systems + #if !defined(USE_SYSTEM) && defined(UNIX) + "+fork()", + #endif + #ifdef FEAT_GETTEXT + # ifdef DYNAMIC_GETTEXT + "+gettext/dyn", + # else + "+gettext", + # endif + #else + "-gettext", + #endif + "-hangul_input", + #if (defined(HAVE_ICONV_H) && defined(USE_ICONV)) || defined(DYNAMIC_ICONV) + # ifdef DYNAMIC_ICONV + "+iconv/dyn", + # else + "+iconv", + # endif + #else + "-iconv", + #endif + "+insert_expand", + #ifdef FEAT_IPV6 + "+ipv6", + #else + "-ipv6", + #endif + #ifdef FEAT_JOB_CHANNEL + "+job", + #else + "-job", + #endif + #ifdef FEAT_JUMPLIST + "+jumplist", + #else + "-jumplist", + #endif + #ifdef FEAT_KEYMAP + "+keymap", + #else + "-keymap", + #endif + #ifdef FEAT_EVAL + "+lambda", + #else + "-lambda", + #endif + #ifdef FEAT_LANGMAP + "+langmap", + #else + "-langmap", + #endif + #ifdef FEAT_LIBCALL + "+libcall", + #else + "-libcall", + #endif + #ifdef FEAT_LINEBREAK + "+linebreak", + #else + "-linebreak", + #endif + #ifdef FEAT_LISP + "+lispindent", + #else + "-lispindent", + #endif + "+listcmds", + "+localmap", + #ifdef FEAT_LUA + # ifdef DYNAMIC_LUA + "+lua/dyn", + # else + "+lua", + # endif + #else + "-lua", + #endif + #ifdef FEAT_MENU + "+menu", + #else + "-menu", + #endif + #ifdef FEAT_SESSION + "+mksession", + #else + "-mksession", + #endif + "+modify_fname", + "+mouse", + #ifdef FEAT_MOUSESHAPE + "+mouseshape", + #else + "-mouseshape", + #endif + + #if defined(UNIX) || defined(VMS) + # ifdef FEAT_MOUSE_DEC + "+mouse_dec", + # else + "-mouse_dec", + # endif + # ifdef FEAT_MOUSE_GPM + "+mouse_gpm", + # else + "-mouse_gpm", + # endif + # ifdef FEAT_MOUSE_JSB + "+mouse_jsbterm", + # else + "-mouse_jsbterm", + # endif + # ifdef FEAT_MOUSE_NET + "+mouse_netterm", + # else + "-mouse_netterm", + # endif + #endif + + #ifdef __QNX__ + # ifdef FEAT_MOUSE_PTERM + "+mouse_pterm", + # else + "-mouse_pterm", + # endif + #endif + + #if defined(UNIX) || defined(VMS) + "+mouse_sgr", + # ifdef FEAT_SYSMOUSE + "+mouse_sysmouse", + # else + "-mouse_sysmouse", + # endif + # ifdef FEAT_MOUSE_URXVT + "+mouse_urxvt", + # else + "-mouse_urxvt", + # endif + "+mouse_xterm", + #endif + + #ifdef FEAT_MBYTE_IME + # ifdef DYNAMIC_IME + "+multi_byte_ime/dyn", + # else + "+multi_byte_ime", + # endif + #else + "+multi_byte", + #endif + #ifdef FEAT_MULTI_LANG + "+multi_lang", + #else + "-multi_lang", + #endif + #ifdef FEAT_MZSCHEME + # ifdef DYNAMIC_MZSCHEME + "+mzscheme/dyn", + # else + "+mzscheme", + # endif + #else + "-mzscheme", + #endif + #ifdef FEAT_NETBEANS_INTG + "+netbeans_intg", + #else + "-netbeans_intg", + #endif + "+num64", + #ifdef FEAT_GUI_MSWIN + # ifdef FEAT_OLE + "+ole", + # else + "-ole", + # endif + #endif + #ifdef FEAT_EVAL + "+packages", + #else + "-packages", + #endif + #ifdef FEAT_PATH_EXTRA + "+path_extra", + #else + "-path_extra", + #endif + #ifdef FEAT_PERL + # ifdef DYNAMIC_PERL + "+perl/dyn", + # else + "+perl", + # endif + #else + "-perl", + #endif + #ifdef FEAT_PERSISTENT_UNDO + "+persistent_undo", + #else + "-persistent_undo", + #endif + #ifdef FEAT_PROP_POPUP + "+popupwin", + #else + "-popupwin", + #endif + #ifdef FEAT_PRINTER + # ifdef FEAT_POSTSCRIPT + "+postscript", + # else + "-postscript", + # endif + "+printer", + #else + "-printer", + #endif + #ifdef FEAT_PROFILE + "+profile", + #else + "-profile", + #endif + #ifdef FEAT_PYTHON + # ifdef DYNAMIC_PYTHON + "+python/dyn", + # else + "+python", + # endif + #else + "-python", + #endif + #ifdef FEAT_PYTHON3 + # ifdef DYNAMIC_PYTHON3 + "+python3/dyn", + # else + "+python3", + # endif + #else + "-python3", + #endif + #ifdef FEAT_QUICKFIX + "+quickfix", + #else + "-quickfix", + #endif + #ifdef FEAT_RELTIME + "+reltime", + #else + "-reltime", + #endif + #ifdef FEAT_RIGHTLEFT + "+rightleft", + #else + "-rightleft", + #endif + #ifdef FEAT_RUBY + # ifdef DYNAMIC_RUBY + "+ruby/dyn", + # else + "+ruby", + # endif + #else + "-ruby", + #endif + "+scrollbind", + #ifdef FEAT_SIGNS + "+signs", + #else + "-signs", + #endif + #ifdef FEAT_SMARTINDENT + "+smartindent", + #else + "-smartindent", + #endif + #ifdef FEAT_SOUND + "+sound", + #else + "-sound", + #endif + #ifdef FEAT_SPELL + "+spell", + #else + "-spell", + #endif + #ifdef STARTUPTIME + "+startuptime", + #else + "-startuptime", + #endif + #ifdef FEAT_STL_OPT + "+statusline", + #else + "-statusline", + #endif + "-sun_workshop", + #ifdef FEAT_SYN_HL + "+syntax", + #else + "-syntax", + #endif + // only interesting on Unix systems + #if defined(USE_SYSTEM) && defined(UNIX) + "+system()", + #endif + #ifdef FEAT_TAG_BINS + "+tag_binary", + #else + "-tag_binary", + #endif + "-tag_old_static", + "-tag_any_white", + #ifdef FEAT_TCL + # ifdef DYNAMIC_TCL + "+tcl/dyn", + # else + "+tcl", + # endif + #else + "-tcl", + #endif + #ifdef FEAT_TERMGUICOLORS + "+termguicolors", + #else + "-termguicolors", + #endif + #ifdef FEAT_TERMINAL + "+terminal", + #else + "-terminal", + #endif + #if defined(UNIX) + // only Unix can have terminfo instead of termcap + # ifdef TERMINFO + "+terminfo", + # else + "-terminfo", + # endif + #endif + #ifdef FEAT_TERMRESPONSE + "+termresponse", + #else + "-termresponse", + #endif + #ifdef FEAT_TEXTOBJ + "+textobjects", + #else + "-textobjects", + #endif + #ifdef FEAT_PROP_POPUP + "+textprop", + #else + "-textprop", + #endif + #if !defined(UNIX) + // unix always includes termcap support + # ifdef HAVE_TGETENT + "+tgetent", + # else + "-tgetent", + # endif + #endif + #ifdef FEAT_TIMERS + "+timers", + #else + "-timers", + #endif + #ifdef FEAT_TITLE + "+title", + #else + "-title", + #endif + #ifdef FEAT_TOOLBAR + "+toolbar", + #else + "-toolbar", + #endif + "+user_commands", + #ifdef FEAT_VARTABS + "+vartabs", + #else + "-vartabs", + #endif + "+vertsplit", + "+virtualedit", + "+visual", + "+visualextra", + #ifdef FEAT_VIMINFO + "+viminfo", + #else + "-viminfo", + #endif + "+vreplace", + #ifdef MSWIN + # ifdef FEAT_VTP + "+vtp", + # else + "-vtp", + # endif + #endif + #ifdef FEAT_WILDIGN + "+wildignore", + #else + "-wildignore", + #endif + #ifdef FEAT_WILDMENU + "+wildmenu", + #else + "-wildmenu", + #endif + "+windows", + #ifdef FEAT_WRITEBACKUP + "+writebackup", + #else + "-writebackup", + #endif + #if defined(UNIX) || defined(VMS) + # ifdef FEAT_X11 + "+X11", + # else + "-X11", + # endif + #endif + #ifdef FEAT_XFONTSET + "+xfontset", + #else + "-xfontset", + #endif + #ifdef FEAT_XIM + "+xim", + #else + "-xim", + #endif + #ifdef MSWIN + # ifdef FEAT_XPM_W32 + "+xpm_w32", + # else + "-xpm_w32", + # endif + #else + # ifdef HAVE_XPM + "+xpm", + # else + "-xpm", + # endif + #endif + #if defined(UNIX) || defined(VMS) + # ifdef USE_XSMP_INTERACT + "+xsmp_interact", + # else + # ifdef USE_XSMP + "+xsmp", + # else + "-xsmp", + # endif + # endif + # ifdef FEAT_XCLIPBOARD + "+xterm_clipboard", + # else + "-xterm_clipboard", + # endif + #endif + #ifdef FEAT_XTERM_SAVE + "+xterm_save", + #else + "-xterm_save", + #endif + NULL + }; + + static int included_patches[] = + { /* Add new patch number below this line */ + /**/ ++ 1424, ++/**/ + 1423, + /**/ + 1422, + /**/ + 1421, + /**/ + 1420, + /**/ + 1419, + /**/ + 1418, + /**/ + 1417, + /**/ + 1416, + /**/ + 1415, + /**/ + 1414, + /**/ + 1413, + /**/ + 1412, + /**/ + 1411, + /**/ + 1410, + /**/ + 1409, + /**/ + 1408, + /**/ + 1407, + /**/ + 1406, + /**/ + 1405, + /**/ + 1404, + /**/ + 1403, + /**/ + 1402, + /**/ + 1401, + /**/ + 1400, + /**/ + 1399, + /**/ + 1398, + /**/ + 1397, + /**/ + 1396, + /**/ + 1395, + /**/ + 1394, + /**/ + 1393, + /**/ + 1392, + /**/ + 1391, + /**/ + 1390, + /**/ + 1389, + /**/ + 1388, + /**/ + 1387, + /**/ + 1386, + /**/ + 1385, + /**/ + 1384, + /**/ + 1383, + /**/ + 1382, + /**/ + 1381, + /**/ + 1380, + /**/ + 1379, + /**/ + 1378, + /**/ + 1377, + /**/ + 1376, + /**/ + 1375, + /**/ + 1374, + /**/ + 1373, + /**/ + 1372, + /**/ + 1371, + /**/ + 1370, + /**/ + 1369, + /**/ + 1368, + /**/ + 1367, + /**/ + 1366, + /**/ + 1365, + /**/ + 1364, + /**/ + 1363, + /**/ + 1362, + /**/ + 1361, + /**/ + 1360, + /**/ + 1359, + /**/ + 1358, + /**/ + 1357, + /**/ + 1356, + /**/ + 1355, + /**/ + 1354, + /**/ + 1353, + /**/ + 1352, + /**/ + 1351, + /**/ + 1350, + /**/ + 1349, + /**/ + 1348, + /**/ + 1347, + /**/ + 1346, + /**/ + 1345, + /**/ + 1344, + /**/ + 1343, + /**/ + 1342, + /**/ + 1341, + /**/ + 1340, + /**/ + 1339, + /**/ + 1338, + /**/ + 1337, + /**/ + 1336, + /**/ + 1335, + /**/ + 1334, + /**/ + 1333, + /**/ + 1332, + /**/ + 1331, + /**/ + 1330, + /**/ + 1329, + /**/ + 1328, + /**/ + 1327, + /**/ + 1326, + /**/ + 1325, + /**/ + 1324, + /**/ + 1323, + /**/ + 1322, + /**/ + 1321, + /**/ + 1320, + /**/ + 1319, + /**/ + 1318, + /**/ + 1317, + /**/ + 1316, + /**/ + 1315, + /**/ + 1314, + /**/ + 1313, + /**/ + 1312, + /**/ + 1311, + /**/ + 1310, + /**/ + 1309, + /**/ + 1308, + /**/ + 1307, + /**/ + 1306, + /**/ + 1305, + /**/ + 1304, + /**/ + 1303, + /**/ + 1302, + /**/ + 1301, + /**/ + 1300, + /**/ + 1299, + /**/ + 1298, + /**/ + 1297, + /**/ + 1296, + /**/ + 1295, + /**/ + 1294, + /**/ + 1293, + /**/ + 1292, + /**/ + 1291, + /**/ + 1290, + /**/ + 1289, + /**/ + 1288, + /**/ + 1287, + /**/ + 1286, + /**/ + 1285, + /**/ + 1284, + /**/ + 1283, + /**/ + 1282, + /**/ + 1281, + /**/ + 1280, + /**/ + 1279, + /**/ + 1278, + /**/ + 1277, + /**/ + 1276, + /**/ + 1275, + /**/ + 1274, + /**/ + 1273, + /**/ + 1272, + /**/ + 1271, + /**/ + 1270, + /**/ + 1269, + /**/ + 1268, + /**/ + 1267, + /**/ + 1266, + /**/ + 1265, + /**/ + 1264, + /**/ + 1263, + /**/ + 1262, + /**/ + 1261, + /**/ + 1260, + /**/ + 1259, + /**/ + 1258, + /**/ + 1257, + /**/ + 1256, + /**/ + 1255, + /**/ + 1254, + /**/ + 1253, + /**/ + 1252, + /**/ + 1251, + /**/ + 1250, + /**/ + 1249, + /**/ + 1248, + /**/ + 1247, + /**/ + 1246, + /**/ + 1245, + /**/ + 1244, + /**/ + 1243, + /**/ + 1242, + /**/ + 1241, + /**/ + 1240, + /**/ + 1239, + /**/ + 1238, + /**/ + 1237, + /**/ + 1236, + /**/ + 1235, + /**/ + 1234, + /**/ + 1233, + /**/ + 1232, + /**/ + 1231, + /**/ + 1230, + /**/ + 1229, + /**/ + 1228, + /**/ + 1227, + /**/ + 1226, + /**/ + 1225, + /**/ + 1224, + /**/ + 1223, + /**/ + 1222, + /**/ + 1221, + /**/ + 1220, + /**/ + 1219, + /**/ + 1218, + /**/ + 1217, + /**/ + 1216, + /**/ + 1215, + /**/ + 1214, + /**/ + 1213, + /**/ + 1212, + /**/ + 1211, + /**/ + 1210, + /**/ + 1209, + /**/ + 1208, + /**/ + 1207, + /**/ + 1206, + /**/ + 1205, + /**/ + 1204, + /**/ + 1203, + /**/ + 1202, + /**/ + 1201, + /**/ + 1200, + /**/ + 1199, + /**/ + 1198, + /**/ + 1197, + /**/ + 1196, + /**/ + 1195, + /**/ + 1194, + /**/ + 1193, + /**/ + 1192, + /**/ + 1191, + /**/ + 1190, + /**/ + 1189, + /**/ + 1188, + /**/ + 1187, + /**/ + 1186, + /**/ + 1185, + /**/ + 1184, + /**/ + 1183, + /**/ + 1182, + /**/ + 1181, + /**/ + 1180, + /**/ + 1179, + /**/ + 1178, + /**/ + 1177, + /**/ + 1176, + /**/ + 1175, + /**/ + 1174, + /**/ + 1173, + /**/ + 1172, + /**/ + 1171, + /**/ + 1170, + /**/ + 1169, + /**/ + 1168, + /**/ + 1167, + /**/ + 1166, + /**/ + 1165, + /**/ + 1164, + /**/ + 1163, + /**/ + 1162, + /**/ + 1161, + /**/ + 1160, + /**/ + 1159, + /**/ + 1158, + /**/ + 1157, + /**/ + 1156, + /**/ + 1155, + /**/ + 1154, + /**/ + 1153, + /**/ + 1152, + /**/ + 1151, + /**/ + 1150, + /**/ + 1149, + /**/ + 1148, + /**/ + 1147, + /**/ + 1146, + /**/ + 1145, + /**/ + 1144, + /**/ + 1143, + /**/ + 1142, + /**/ + 1141, + /**/ + 1140, + /**/ + 1139, + /**/ + 1138, + /**/ + 1137, + /**/ + 1136, + /**/ + 1135, + /**/ + 1134, + /**/ + 1133, + /**/ + 1132, + /**/ + 1131, + /**/ + 1130, + /**/ + 1129, + /**/ + 1128, + /**/ + 1127, + /**/ + 1126, + /**/ + 1125, + /**/ + 1124, + /**/ + 1123, + /**/ + 1122, + /**/ + 1121, + /**/ + 1120, + /**/ + 1119, + /**/ + 1118, + /**/ + 1117, + /**/ + 1116, + /**/ + 1115, + /**/ + 1114, + /**/ + 1113, + /**/ + 1112, + /**/ + 1111, + /**/ + 1110, + /**/ + 1109, + /**/ + 1108, + /**/ + 1107, + /**/ + 1106, + /**/ + 1105, + /**/ + 1104, + /**/ + 1103, + /**/ + 1102, + /**/ + 1101, + /**/ + 1100, + /**/ + 1099, + /**/ + 1098, + /**/ + 1097, + /**/ + 1096, + /**/ + 1095, + /**/ + 1094, + /**/ + 1093, + /**/ + 1092, + /**/ + 1091, + /**/ + 1090, + /**/ + 1089, + /**/ + 1088, + /**/ + 1087, + /**/ + 1086, + /**/ + 1085, + /**/ + 1084, + /**/ + 1083, + /**/ + 1082, + /**/ + 1081, + /**/ + 1080, + /**/ + 1079, + /**/ + 1078, + /**/ + 1077, + /**/ + 1076, + /**/ + 1075, + /**/ + 1074, + /**/ + 1073, + /**/ + 1072, + /**/ + 1071, + /**/ + 1070, + /**/ + 1069, + /**/ + 1068, + /**/ + 1067, + /**/ + 1066, + /**/ + 1065, + /**/ + 1064, + /**/ + 1063, + /**/ + 1062, + /**/ + 1061, + /**/ + 1060, + /**/ + 1059, + /**/ + 1058, + /**/ + 1057, + /**/ + 1056, + /**/ + 1055, + /**/ + 1054, + /**/ + 1053, + /**/ + 1052, + /**/ + 1051, + /**/ + 1050, + /**/ + 1049, + /**/ + 1048, + /**/ + 1047, + /**/ + 1046, + /**/ + 1045, + /**/ + 1044, + /**/ + 1043, + /**/ + 1042, + /**/ + 1041, + /**/ + 1040, + /**/ + 1039, + /**/ + 1038, + /**/ + 1037, + /**/ + 1036, + /**/ + 1035, + /**/ + 1034, + /**/ + 1033, + /**/ + 1032, + /**/ + 1031, + /**/ + 1030, + /**/ + 1029, + /**/ + 1028, + /**/ + 1027, + /**/ + 1026, + /**/ + 1025, + /**/ + 1024, + /**/ + 1023, + /**/ + 1022, + /**/ + 1021, + /**/ + 1020, + /**/ + 1019, + /**/ + 1018, + /**/ + 1017, + /**/ + 1016, + /**/ + 1015, + /**/ + 1014, + /**/ + 1013, + /**/ + 1012, + /**/ + 1011, + /**/ + 1010, + /**/ + 1009, + /**/ + 1008, + /**/ + 1007, + /**/ + 1006, + /**/ + 1005, + /**/ + 1004, + /**/ + 1003, + /**/ + 1002, + /**/ + 1001, + /**/ + 1000, + /**/ + 999, + /**/ + 998, + /**/ + 997, + /**/ + 996, + /**/ + 995, + /**/ + 994, + /**/ + 993, + /**/ + 992, + /**/ + 991, + /**/ + 990, + /**/ + 989, + /**/ + 988, + /**/ + 987, + /**/ + 986, + /**/ + 985, + /**/ + 984, + /**/ + 983, + /**/ + 982, + /**/ + 981, + /**/ + 980, + /**/ + 979, + /**/ + 978, + /**/ + 977, + /**/ + 976, + /**/ + 975, + /**/ + 974, + /**/ + 973, + /**/ + 972, + /**/ + 971, + /**/ + 970, + /**/ + 969, + /**/ + 968, + /**/ + 967, + /**/ + 966, + /**/ + 965, + /**/ + 964, + /**/ + 963, + /**/ + 962, + /**/ + 961, + /**/ + 960, + /**/ + 959, + /**/ + 958, + /**/ + 957, + /**/ + 956, + /**/ + 955, + /**/ + 954, + /**/ + 953, + /**/ + 952, + /**/ + 951, + /**/ + 950, + /**/ + 949, + /**/ + 948, + /**/ + 947, + /**/ + 946, + /**/ + 945, + /**/ + 944, + /**/ + 943, + /**/ + 942, + /**/ + 941, + /**/ + 940, + /**/ + 939, + /**/ + 938, + /**/ + 937, + /**/ + 936, + /**/ + 935, + /**/ + 934, + /**/ + 933, + /**/ + 932, + /**/ + 931, + /**/ + 930, + /**/ + 929, + /**/ + 928, + /**/ + 927, + /**/ + 926, + /**/ + 925, + /**/ + 924, + /**/ + 923, + /**/ + 922, + /**/ + 921, + /**/ + 920, + /**/ + 919, + /**/ + 918, + /**/ + 917, + /**/ + 916, + /**/ + 915, + /**/ + 914, + /**/ + 913, + /**/ + 912, + /**/ + 911, + /**/ + 910, + /**/ + 909, + /**/ + 908, + /**/ + 907, + /**/ + 906, + /**/ + 905, + /**/ + 904, + /**/ + 903, + /**/ + 902, + /**/ + 901, + /**/ + 900, + /**/ + 899, + /**/ + 898, + /**/ + 897, + /**/ + 896, + /**/ + 895, + /**/ + 894, + /**/ + 893, + /**/ + 892, + /**/ + 891, + /**/ + 890, + /**/ + 889, + /**/ + 888, + /**/ + 887, + /**/ + 886, + /**/ + 885, + /**/ + 884, + /**/ + 883, + /**/ + 882, + /**/ + 881, + /**/ + 880, + /**/ + 879, + /**/ + 878, + /**/ + 877, + /**/ + 876, + /**/ + 875, + /**/ + 874, + /**/ + 873, + /**/ + 872, + /**/ + 871, + /**/ + 870, + /**/ + 869, + /**/ + 868, + /**/ + 867, + /**/ + 866, + /**/ + 865, + /**/ + 864, + /**/ + 863, + /**/ + 862, + /**/ + 861, + /**/ + 860, + /**/ + 859, + /**/ + 858, + /**/ + 857, + /**/ + 856, + /**/ + 855, + /**/ + 854, + /**/ + 853, + /**/ + 852, + /**/ + 851, + /**/ + 850, + /**/ + 849, + /**/ + 848, + /**/ + 847, + /**/ + 846, + /**/ + 845, + /**/ + 844, + /**/ + 843, + /**/ + 842, + /**/ + 841, + /**/ + 840, + /**/ + 839, + /**/ + 838, + /**/ + 837, + /**/ + 836, + /**/ + 835, + /**/ + 834, + /**/ + 833, + /**/ + 832, + /**/ + 831, + /**/ + 830, + /**/ + 829, + /**/ + 828, + /**/ + 827, + /**/ + 826, + /**/ + 825, + /**/ + 824, + /**/ + 823, + /**/ + 822, + /**/ + 821, + /**/ + 820, + /**/ + 819, + /**/ + 818, + /**/ + 817, + /**/ + 816, + /**/ + 815, + /**/ + 814, + /**/ + 813, + /**/ + 812, + /**/ + 811, + /**/ + 810, + /**/ + 809, + /**/ + 808, + /**/ + 807, + /**/ + 806, + /**/ + 805, + /**/ + 804, + /**/ + 803, + /**/ + 802, + /**/ + 801, + /**/ + 800, + /**/ + 799, + /**/ + 798, + /**/ + 797, + /**/ + 796, + /**/ + 795, + /**/ + 794, + /**/ + 793, + /**/ + 792, + /**/ + 791, + /**/ + 790, + /**/ + 789, + /**/ + 788, + /**/ + 787, + /**/ + 786, + /**/ + 785, + /**/ + 784, + /**/ + 783, + /**/ + 782, + /**/ + 781, + /**/ + 780, + /**/ + 779, + /**/ + 778, + /**/ + 777, + /**/ + 776, + /**/ + 775, + /**/ + 774, + /**/ + 773, + /**/ + 772, + /**/ + 771, + /**/ + 770, + /**/ + 769, + /**/ + 768, + /**/ + 767, + /**/ + 766, + /**/ + 765, + /**/ + 764, + /**/ + 763, + /**/ + 762, + /**/ + 761, + /**/ + 760, + /**/ + 759, + /**/ + 758, + /**/ + 757, + /**/ + 756, + /**/ + 755, + /**/ + 754, + /**/ + 753, + /**/ + 752, + /**/ + 751, + /**/ + 750, + /**/ + 749, + /**/ + 748, + /**/ + 747, + /**/ + 746, + /**/ + 745, + /**/ + 744, + /**/ + 743, + /**/ + 742, + /**/ + 741, + /**/ + 740, + /**/ + 739, + /**/ + 738, + /**/ + 737, + /**/ + 736, + /**/ + 735, + /**/ + 734, + /**/ + 733, + /**/ + 732, + /**/ + 731, + /**/ + 730, + /**/ + 729, + /**/ + 728, + /**/ + 727, + /**/ + 726, + /**/ + 725, + /**/ + 724, + /**/ + 723, + /**/ + 722, + /**/ + 721, + /**/ + 720, + /**/ + 719, + /**/ + 718, + /**/ + 717, + /**/ + 716, + /**/ + 715, + /**/ + 714, + /**/ + 713, + /**/ + 712, + /**/ + 711, + /**/ + 710, + /**/ + 709, + /**/ + 708, + /**/ + 707, + /**/ + 706, + /**/ + 705, + /**/ + 704, + /**/ + 703, + /**/ + 702, + /**/ + 701, + /**/ + 700, + /**/ + 699, + /**/ + 698, + /**/ + 697, + /**/ + 696, + /**/ + 695, + /**/ + 694, + /**/ + 693, + /**/ + 692, + /**/ + 691, + /**/ + 690, + /**/ + 689, + /**/ + 688, + /**/ + 687, + /**/ + 686, + /**/ + 685, + /**/ + 684, + /**/ + 683, + /**/ + 682, + /**/ + 681, + /**/ + 680, + /**/ + 679, + /**/ + 678, + /**/ + 677, + /**/ + 676, + /**/ + 675, + /**/ + 674, + /**/ + 673, + /**/ + 672, + /**/ + 671, + /**/ + 670, + /**/ + 669, + /**/ + 668, + /**/ + 667, + /**/ + 666, + /**/ + 665, + /**/ + 664, + /**/ + 663, + /**/ + 662, + /**/ + 661, + /**/ + 660, + /**/ + 659, + /**/ + 658, + /**/ + 657, + /**/ + 656, + /**/ + 655, + /**/ + 654, + /**/ + 653, + /**/ + 652, + /**/ + 651, + /**/ + 650, + /**/ + 649, + /**/ + 648, + /**/ + 647, + /**/ + 646, + /**/ + 645, + /**/ + 644, + /**/ + 643, + /**/ + 642, + /**/ + 641, + /**/ + 640, + /**/ + 639, + /**/ + 638, + /**/ + 637, + /**/ + 636, + /**/ + 635, + /**/ + 634, + /**/ + 633, + /**/ + 632, + /**/ + 631, + /**/ + 630, + /**/ + 629, + /**/ + 628, + /**/ + 627, + /**/ + 626, + /**/ + 625, + /**/ + 624, + /**/ + 623, + /**/ + 622, + /**/ + 621, + /**/ + 620, + /**/ + 619, + /**/ + 618, + /**/ + 617, + /**/ + 616, + /**/ + 615, + /**/ + 614, + /**/ + 613, + /**/ + 612, + /**/ + 611, + /**/ + 610, + /**/ + 609, + /**/ + 608, + /**/ + 607, + /**/ + 606, + /**/ + 605, + /**/ + 604, + /**/ + 603, + /**/ + 602, + /**/ + 601, + /**/ + 600, + /**/ + 599, + /**/ + 598, + /**/ + 597, + /**/ + 596, + /**/ + 595, + /**/ + 594, + /**/ + 593, + /**/ + 592, + /**/ + 591, + /**/ + 590, + /**/ + 589, + /**/ + 588, + /**/ + 587, + /**/ + 586, + /**/ + 585, + /**/ + 584, + /**/ + 583, + /**/ + 582, + /**/ + 581, + /**/ + 580, + /**/ + 579, + /**/ + 578, + /**/ + 577, + /**/ + 576, + /**/ + 575, + /**/ + 574, + /**/ + 573, + /**/ + 572, + /**/ + 571, + /**/ + 570, + /**/ + 569, + /**/ + 568, + /**/ + 567, + /**/ + 566, + /**/ + 565, + /**/ + 564, + /**/ + 563, + /**/ + 562, + /**/ + 561, + /**/ + 560, + /**/ + 559, + /**/ + 558, + /**/ + 557, + /**/ + 556, + /**/ + 555, + /**/ + 554, + /**/ + 553, + /**/ + 552, + /**/ + 551, + /**/ + 550, + /**/ + 549, + /**/ + 548, + /**/ + 547, + /**/ + 546, + /**/ + 545, + /**/ + 544, + /**/ + 543, + /**/ + 542, + /**/ + 541, + /**/ + 540, + /**/ + 539, + /**/ + 538, + /**/ + 537, + /**/ + 536, + /**/ + 535, + /**/ + 534, + /**/ + 533, + /**/ + 532, + /**/ + 531, + /**/ + 530, + /**/ + 529, + /**/ + 528, + /**/ + 527, + /**/ + 526, + /**/ + 525, + /**/ + 524, + /**/ + 523, + /**/ + 522, + /**/ + 521, + /**/ + 520, + /**/ + 519, + /**/ + 518, + /**/ + 517, + /**/ + 516, + /**/ + 515, + /**/ + 514, + /**/ + 513, + /**/ + 512, + /**/ + 511, + /**/ + 510, + /**/ + 509, + /**/ + 508, + /**/ + 507, + /**/ + 506, + /**/ + 505, + /**/ + 504, + /**/ + 503, + /**/ + 502, + /**/ + 501, + /**/ + 500, + /**/ + 499, + /**/ + 498, + /**/ + 497, + /**/ + 496, + /**/ + 495, + /**/ + 494, + /**/ + 493, + /**/ + 492, + /**/ + 491, + /**/ + 490, + /**/ + 489, + /**/ + 488, + /**/ + 487, + /**/ + 486, + /**/ + 485, + /**/ + 484, + /**/ + 483, + /**/ + 482, + /**/ + 481, + /**/ + 480, + /**/ + 479, + /**/ + 478, + /**/ + 477, + /**/ + 476, + /**/ + 475, + /**/ + 474, + /**/ + 473, + /**/ + 472, + /**/ + 471, + /**/ + 470, + /**/ + 469, + /**/ + 468, + /**/ + 467, + /**/ + 466, + /**/ + 465, + /**/ + 464, + /**/ + 463, + /**/ + 462, + /**/ + 461, + /**/ + 460, + /**/ + 459, + /**/ + 458, + /**/ + 457, + /**/ + 456, + /**/ + 455, + /**/ + 454, + /**/ + 453, + /**/ + 452, + /**/ + 451, + /**/ + 450, + /**/ + 449, + /**/ + 448, + /**/ + 447, + /**/ + 446, + /**/ + 445, + /**/ + 444, + /**/ + 443, + /**/ + 442, + /**/ + 441, + /**/ + 440, + /**/ + 439, + /**/ + 438, + /**/ + 437, + /**/ + 436, + /**/ + 435, + /**/ + 434, + /**/ + 433, + /**/ + 432, + /**/ + 431, + /**/ + 430, + /**/ + 429, + /**/ + 428, + /**/ + 427, + /**/ + 426, + /**/ + 425, + /**/ + 424, + /**/ + 423, + /**/ + 422, + /**/ + 421, + /**/ + 420, + /**/ + 419, + /**/ + 418, + /**/ + 417, + /**/ + 416, + /**/ + 415, + /**/ + 414, + /**/ + 413, + /**/ + 412, + /**/ + 411, + /**/ + 410, + /**/ + 409, + /**/ + 408, + /**/ + 407, + /**/ + 406, + /**/ + 405, + /**/ + 404, + /**/ + 403, + /**/ + 402, + /**/ + 401, + /**/ + 400, + /**/ + 399, + /**/ + 398, + /**/ + 397, + /**/ + 396, + /**/ + 395, + /**/ + 394, + /**/ + 393, + /**/ + 392, + /**/ + 391, + /**/ + 390, + /**/ + 389, + /**/ + 388, + /**/ + 387, + /**/ + 386, + /**/ + 385, + /**/ + 384, + /**/ + 383, + /**/ + 382, + /**/ + 381, + /**/ + 380, + /**/ + 379, + /**/ + 378, + /**/ + 377, + /**/ + 376, + /**/ + 375, + /**/ + 374, + /**/ + 373, + /**/ + 372, + /**/ + 371, + /**/ + 370, + /**/ + 369, + /**/ + 368, + /**/ + 367, + /**/ + 366, + /**/ + 365, + /**/ + 364, + /**/ + 363, + /**/ + 362, + /**/ + 361, + /**/ + 360, + /**/ + 359, + /**/ + 358, + /**/ + 357, + /**/ + 356, + /**/ + 355, + /**/ + 354, + /**/ + 353, + /**/ + 352, + /**/ + 351, + /**/ + 350, + /**/ + 349, + /**/ + 348, + /**/ + 347, + /**/ + 346, + /**/ + 345, + /**/ + 344, + /**/ + 343, + /**/ + 342, + /**/ + 341, + /**/ + 340, + /**/ + 339, + /**/ + 338, + /**/ + 337, + /**/ + 336, + /**/ + 335, + /**/ + 334, + /**/ + 333, + /**/ + 332, + /**/ + 331, + /**/ + 330, + /**/ + 329, + /**/ + 328, + /**/ + 327, + /**/ + 326, + /**/ + 325, + /**/ + 324, + /**/ + 323, + /**/ + 322, + /**/ + 321, + /**/ + 320, + /**/ + 319, + /**/ + 318, + /**/ + 317, + /**/ + 316, + /**/ + 315, + /**/ + 314, + /**/ + 313, + /**/ + 312, + /**/ + 311, + /**/ + 310, + /**/ + 309, + /**/ + 308, + /**/ + 307, + /**/ + 306, + /**/ + 305, + /**/ + 304, + /**/ + 303, + /**/ + 302, + /**/ + 301, + /**/ + 300, + /**/ + 299, + /**/ + 298, + /**/ + 297, + /**/ + 296, + /**/ + 295, + /**/ + 294, + /**/ + 293, + /**/ + 292, + /**/ + 291, + /**/ + 290, + /**/ + 289, + /**/ + 288, + /**/ + 287, + /**/ + 286, + /**/ + 285, + /**/ + 284, + /**/ + 283, + /**/ + 282, + /**/ + 281, + /**/ + 280, + /**/ + 279, + /**/ + 278, + /**/ + 277, + /**/ + 276, + /**/ + 275, + /**/ + 274, + /**/ + 273, + /**/ + 272, + /**/ + 271, + /**/ + 270, + /**/ + 269, + /**/ + 268, + /**/ + 267, + /**/ + 266, + /**/ + 265, + /**/ + 264, + /**/ + 263, + /**/ + 262, + /**/ + 261, + /**/ + 260, + /**/ + 259, + /**/ + 258, + /**/ + 257, + /**/ + 256, + /**/ + 255, + /**/ + 254, + /**/ + 253, + /**/ + 252, + /**/ + 251, + /**/ + 250, + /**/ + 249, + /**/ + 248, + /**/ + 247, + /**/ + 246, + /**/ + 245, + /**/ + 244, + /**/ + 243, + /**/ + 242, + /**/ + 241, + /**/ + 240, + /**/ + 239, + /**/ + 238, + /**/ + 237, + /**/ + 236, + /**/ + 235, + /**/ + 234, + /**/ + 233, + /**/ + 232, + /**/ + 231, + /**/ + 230, + /**/ + 229, + /**/ + 228, + /**/ + 227, + /**/ + 226, + /**/ + 225, + /**/ + 224, + /**/ + 223, + /**/ + 222, + /**/ + 221, + /**/ + 220, + /**/ + 219, + /**/ + 218, + /**/ + 217, + /**/ + 216, + /**/ + 215, + /**/ + 214, + /**/ + 213, + /**/ + 212, + /**/ + 211, + /**/ + 210, + /**/ + 209, + /**/ + 208, + /**/ + 207, + /**/ + 206, + /**/ + 205, + /**/ + 204, + /**/ + 203, + /**/ + 202, + /**/ + 201, + /**/ + 200, + /**/ + 199, + /**/ + 198, + /**/ + 197, + /**/ + 196, + /**/ + 195, + /**/ + 194, + /**/ + 193, + /**/ + 192, + /**/ + 191, + /**/ + 190, + /**/ + 189, + /**/ + 188, + /**/ + 187, + /**/ + 186, + /**/ + 185, + /**/ + 184, + /**/ + 183, + /**/ + 182, + /**/ + 181, + /**/ + 180, + /**/ + 179, + /**/ + 178, + /**/ + 177, + /**/ + 176, + /**/ + 175, + /**/ + 174, + /**/ + 173, + /**/ + 172, + /**/ + 171, + /**/ + 170, + /**/ + 169, + /**/ + 168, + /**/ + 167, + /**/ + 166, + /**/ + 165, + /**/ + 164, + /**/ + 163, + /**/ + 162, + /**/ + 161, + /**/ + 160, + /**/ + 159, + /**/ + 158, + /**/ + 157, + /**/ + 156, + /**/ + 155, + /**/ + 154, + /**/ + 153, + /**/ + 152, + /**/ + 151, + /**/ + 150, + /**/ + 149, + /**/ + 148, + /**/ + 147, + /**/ + 146, + /**/ + 145, + /**/ + 144, + /**/ + 143, + /**/ + 142, + /**/ + 141, + /**/ + 140, + /**/ + 139, + /**/ + 138, + /**/ + 137, + /**/ + 136, + /**/ + 135, + /**/ + 134, + /**/ + 133, + /**/ + 132, + /**/ + 131, + /**/ + 130, + /**/ + 129, + /**/ + 128, + /**/ + 127, + /**/ + 126, + /**/ + 125, + /**/ + 124, + /**/ + 123, + /**/ + 122, + /**/ + 121, + /**/ + 120, + /**/ + 119, + /**/ + 118, + /**/ + 117, + /**/ + 116, + /**/ + 115, + /**/ + 114, + /**/ + 113, + /**/ + 112, + /**/ + 111, + /**/ + 110, + /**/ + 109, + /**/ + 108, + /**/ + 107, + /**/ + 106, + /**/ + 105, + /**/ + 104, + /**/ + 103, + /**/ + 102, + /**/ + 101, + /**/ + 100, + /**/ + 99, + /**/ + 98, + /**/ + 97, + /**/ + 96, + /**/ + 95, + /**/ + 94, + /**/ + 93, + /**/ + 92, + /**/ + 91, + /**/ + 90, + /**/ + 89, + /**/ + 88, + /**/ + 87, + /**/ + 86, + /**/ + 85, + /**/ + 84, + /**/ + 83, + /**/ + 82, + /**/ + 81, + /**/ + 80, + /**/ + 79, + /**/ + 78, + /**/ + 77, + /**/ + 76, + /**/ + 75, + /**/ + 74, + /**/ + 73, + /**/ + 72, + /**/ + 71, + /**/ + 70, + /**/ + 69, + /**/ + 68, + /**/ + 67, + /**/ + 66, + /**/ + 65, + /**/ + 64, + /**/ + 63, + /**/ + 62, + /**/ + 61, + /**/ + 60, + /**/ + 59, + /**/ + 58, + /**/ + 57, + /**/ + 56, + /**/ + 55, + /**/ + 54, + /**/ + 53, + /**/ + 52, + /**/ + 51, + /**/ + 50, + /**/ + 49, + /**/ + 48, + /**/ + 47, + /**/ + 46, + /**/ + 45, + /**/ + 44, + /**/ + 43, + /**/ + 42, + /**/ + 41, + /**/ + 40, + /**/ + 39, + /**/ + 38, + /**/ + 37, + /**/ + 36, + /**/ + 35, + /**/ + 34, + /**/ + 33, + /**/ + 32, + /**/ + 31, + /**/ + 30, + /**/ + 29, + /**/ + 28, + /**/ + 27, + /**/ + 26, + /**/ + 25, + /**/ + 24, + /**/ + 23, + /**/ + 22, + /**/ + 21, + /**/ + 20, + /**/ + 19, + /**/ + 18, + /**/ + 17, + /**/ + 16, + /**/ + 15, + /**/ + 14, + /**/ + 13, + /**/ + 12, + /**/ + 11, + /**/ + 10, + /**/ + 9, + /**/ + 8, + /**/ + 7, + /**/ + 6, + /**/ + 5, + /**/ + 4, + /**/ + 3, + /**/ + 2, + /**/ + 1, + /**/ + 0 + }; + + /* + * Place to put a short description when adding a feature with a patch. + * Keep it short, e.g.,: "relative numbers", "persistent undo". + * Also add a comment marker to separate the lines. + * See the official Vim patches for the diff format: It must use a context of + * one line only. Create it by hand or use "diff -C2" and edit the patch. + */ + static char *(extra_patches[]) = + { /* Add your patch description below this line */ + /**/ + NULL + }; + + int + highest_patch(void) + { + // this relies on the highest patch number to be the first entry + return included_patches[0]; + } + + #if defined(FEAT_EVAL) || defined(PROTO) + /* + * Return TRUE if patch "n" has been included. + */ + int + has_patch(int n) + { + int i; + + for (i = 0; included_patches[i] != 0; ++i) + if (included_patches[i] == n) + return TRUE; + return FALSE; + } + #endif + + void + ex_version(exarg_T *eap) + { + /* + * Ignore a ":version 9.99" command. + */ + if (*eap->arg == NUL) + { + msg_putchar('\n'); + list_version(); + } + } + + /* + * Output a string for the version message. If it's going to wrap, output a + * newline, unless the message is too long to fit on the screen anyway. + * When "wrap" is TRUE wrap the string in []. + */ + static void + version_msg_wrap(char_u *s, int wrap) + { + int len = (int)vim_strsize(s) + (wrap ? 2 : 0); + + if (!got_int && len < (int)Columns && msg_col + len >= (int)Columns + && *s != '\n') + msg_putchar('\n'); + if (!got_int) + { + if (wrap) + msg_puts("["); + msg_puts((char *)s); + if (wrap) + msg_puts("]"); + } + } + + static void + version_msg(char *s) + { + version_msg_wrap((char_u *)s, FALSE); + } + + /* + * List all features aligned in columns, dictionary style. + */ + static void + list_features(void) + { + list_in_columns((char_u **)features, -1, -1); + } + + /* + * List string items nicely aligned in columns. + * When "size" is < 0 then the last entry is marked with NULL. + * The entry with index "current" is inclosed in []. + */ + void + list_in_columns(char_u **items, int size, int current) + { + int i; + int ncol; + int nrow; + int cur_row = 1; + int item_count = 0; + int width = 0; + #ifdef FEAT_SYN_HL + int use_highlight = (items == (char_u **)features); + #endif + + // Find the length of the longest item, use that + 1 as the column + // width. + for (i = 0; size < 0 ? items[i] != NULL : i < size; ++i) + { + int l = (int)vim_strsize(items[i]) + (i == current ? 2 : 0); + + if (l > width) + width = l; + ++item_count; + } + width += 1; + + if (Columns < width) + { + // Not enough screen columns - show one per line + for (i = 0; i < item_count; ++i) + { + version_msg_wrap(items[i], i == current); + if (msg_col > 0 && i < item_count - 1) + msg_putchar('\n'); + } + return; + } + + // The rightmost column doesn't need a separator. + // Sacrifice it to fit in one more column if possible. + ncol = (int) (Columns + 1) / width; + nrow = item_count / ncol + (item_count % ncol ? 1 : 0); + + // "i" counts columns then rows. "idx" counts rows then columns. + for (i = 0; !got_int && i < nrow * ncol; ++i) + { + int idx = (i / ncol) + (i % ncol) * nrow; + + if (idx < item_count) + { + int last_col = (i + 1) % ncol == 0; + + if (idx == current) + msg_putchar('['); + #ifdef FEAT_SYN_HL + if (use_highlight && items[idx][0] == '-') + msg_puts_attr((char *)items[idx], HL_ATTR(HLF_W)); + else + #endif + msg_puts((char *)items[idx]); + if (idx == current) + msg_putchar(']'); + if (last_col) + { + if (msg_col > 0 && cur_row < nrow) + msg_putchar('\n'); + ++cur_row; + } + else + { + while (msg_col % width) + msg_putchar(' '); + } + } + else + { + // this row is out of items, thus at the end of the row + if (msg_col > 0) + { + if (cur_row < nrow) + msg_putchar('\n'); + ++cur_row; + } + } + } + } + + void + list_version(void) + { + int i; + int first; + char *s = ""; + + /* + * When adding features here, don't forget to update the list of + * internal variables in eval.c! + */ + init_longVersion(); + msg(longVersion); + #ifdef MSWIN + # ifdef FEAT_GUI_MSWIN + # ifdef VIMDLL + # ifdef _WIN64 + msg_puts(_("\nMS-Windows 64-bit GUI/console version")); + # else + msg_puts(_("\nMS-Windows 32-bit GUI/console version")); + # endif + # else + # ifdef _WIN64 + msg_puts(_("\nMS-Windows 64-bit GUI version")); + # else + msg_puts(_("\nMS-Windows 32-bit GUI version")); + # endif + # endif + # ifdef FEAT_OLE + msg_puts(_(" with OLE support")); + # endif + # else + # ifdef _WIN64 + msg_puts(_("\nMS-Windows 64-bit console version")); + # else + msg_puts(_("\nMS-Windows 32-bit console version")); + # endif + # endif + #endif + #if defined(MACOS_X) + # if defined(MACOS_X_DARWIN) + msg_puts(_("\nmacOS version")); + # else + msg_puts(_("\nmacOS version w/o darwin feat.")); + # endif + #endif + + #ifdef VMS + msg_puts(_("\nOpenVMS version")); + # ifdef HAVE_PATHDEF + if (*compiled_arch != NUL) + { + msg_puts(" - "); + msg_puts((char *)compiled_arch); + } + # endif + + #endif + + // Print the list of patch numbers if there is at least one. + // Print a range when patches are consecutive: "1-10, 12, 15-40, 42-45" + if (included_patches[0] != 0) + { + msg_puts(_("\nIncluded patches: ")); + first = -1; + // find last one + for (i = 0; included_patches[i] != 0; ++i) + ; + while (--i >= 0) + { + if (first < 0) + first = included_patches[i]; + if (i == 0 || included_patches[i - 1] != included_patches[i] + 1) + { + msg_puts(s); + s = ", "; + msg_outnum((long)first); + if (first != included_patches[i]) + { + msg_puts("-"); + msg_outnum((long)included_patches[i]); + } + first = -1; + } + } + } + + // Print the list of extra patch descriptions if there is at least one. + if (extra_patches[0] != NULL) + { + msg_puts(_("\nExtra patches: ")); + s = ""; + for (i = 0; extra_patches[i] != NULL; ++i) + { + msg_puts(s); + s = ", "; + msg_puts(extra_patches[i]); + } + } + + #ifdef MODIFIED_BY + msg_puts("\n"); + msg_puts(_("Modified by ")); + msg_puts(MODIFIED_BY); + #endif + + #ifdef HAVE_PATHDEF + if (*compiled_user != NUL || *compiled_sys != NUL) + { + msg_puts(_("\nCompiled ")); + if (*compiled_user != NUL) + { + msg_puts(_("by ")); + msg_puts((char *)compiled_user); + } + if (*compiled_sys != NUL) + { + msg_puts("@"); + msg_puts((char *)compiled_sys); + } + } + #endif + + #ifdef FEAT_HUGE + msg_puts(_("\nHuge version ")); + #else + # ifdef FEAT_BIG + msg_puts(_("\nBig version ")); + # else + # ifdef FEAT_NORMAL + msg_puts(_("\nNormal version ")); + # else + # ifdef FEAT_SMALL + msg_puts(_("\nSmall version ")); + # else + msg_puts(_("\nTiny version ")); + # endif + # endif + # endif + #endif + #ifndef FEAT_GUI + msg_puts(_("without GUI.")); + #else + # ifdef FEAT_GUI_GTK + # ifdef USE_GTK3 + msg_puts(_("with GTK3 GUI.")); + # else + # ifdef FEAT_GUI_GNOME + msg_puts(_("with GTK2-GNOME GUI.")); + # else + msg_puts(_("with GTK2 GUI.")); + # endif + # endif + # else + # ifdef FEAT_GUI_MOTIF + msg_puts(_("with X11-Motif GUI.")); + # else + # ifdef FEAT_GUI_ATHENA + # ifdef FEAT_GUI_NEXTAW + msg_puts(_("with X11-neXtaw GUI.")); + # else + msg_puts(_("with X11-Athena GUI.")); + # endif + # else + # ifdef FEAT_GUI_HAIKU + msg_puts(_("with Haiku GUI.")); + # else + # ifdef FEAT_GUI_PHOTON + msg_puts(_("with Photon GUI.")); + # else + # if defined(MSWIN) + msg_puts(_("with GUI.")); +-# else +-# if defined(TARGET_API_MAC_CARBON) && TARGET_API_MAC_CARBON +- msg_puts(_("with Carbon GUI.")); +-# else +-# if defined(TARGET_API_MAC_OSX) && TARGET_API_MAC_OSX +- msg_puts(_("with Cocoa GUI.")); +-# else +-# endif +-# endif +-# endif + # endif ++# endif + # endif + # endif + # endif + # endif + #endif + version_msg(_(" Features included (+) or not (-):\n")); + + list_features(); + if (msg_col > 0) + msg_putchar('\n'); + + #ifdef SYS_VIMRC_FILE + version_msg(_(" system vimrc file: \"")); + version_msg(SYS_VIMRC_FILE); + version_msg("\"\n"); + #endif + #ifdef USR_VIMRC_FILE + version_msg(_(" user vimrc file: \"")); + version_msg(USR_VIMRC_FILE); + version_msg("\"\n"); + #endif + #ifdef USR_VIMRC_FILE2 + version_msg(_(" 2nd user vimrc file: \"")); + version_msg(USR_VIMRC_FILE2); + version_msg("\"\n"); + #endif + #ifdef USR_VIMRC_FILE3 + version_msg(_(" 3rd user vimrc file: \"")); + version_msg(USR_VIMRC_FILE3); + version_msg("\"\n"); + #endif + #ifdef USR_EXRC_FILE + version_msg(_(" user exrc file: \"")); + version_msg(USR_EXRC_FILE); + version_msg("\"\n"); + #endif + #ifdef USR_EXRC_FILE2 + version_msg(_(" 2nd user exrc file: \"")); + version_msg(USR_EXRC_FILE2); + version_msg("\"\n"); + #endif + #ifdef FEAT_GUI + # ifdef SYS_GVIMRC_FILE + version_msg(_(" system gvimrc file: \"")); + version_msg(SYS_GVIMRC_FILE); + version_msg("\"\n"); + # endif + version_msg(_(" user gvimrc file: \"")); + version_msg(USR_GVIMRC_FILE); + version_msg("\"\n"); + # ifdef USR_GVIMRC_FILE2 + version_msg(_("2nd user gvimrc file: \"")); + version_msg(USR_GVIMRC_FILE2); + version_msg("\"\n"); + # endif + # ifdef USR_GVIMRC_FILE3 + version_msg(_("3rd user gvimrc file: \"")); + version_msg(USR_GVIMRC_FILE3); + version_msg("\"\n"); + # endif + #endif + version_msg(_(" defaults file: \"")); + version_msg(VIM_DEFAULTS_FILE); + version_msg("\"\n"); + #ifdef FEAT_GUI + # ifdef SYS_MENU_FILE + version_msg(_(" system menu file: \"")); + version_msg(SYS_MENU_FILE); + version_msg("\"\n"); + # endif + #endif + #ifdef HAVE_PATHDEF + if (*default_vim_dir != NUL) + { + version_msg(_(" fall-back for $VIM: \"")); + version_msg((char *)default_vim_dir); + version_msg("\"\n"); + } + if (*default_vimruntime_dir != NUL) + { + version_msg(_(" f-b for $VIMRUNTIME: \"")); + version_msg((char *)default_vimruntime_dir); + version_msg("\"\n"); + } + version_msg(_("Compilation: ")); + version_msg((char *)all_cflags); + version_msg("\n"); + #ifdef VMS + if (*compiler_version != NUL) + { + version_msg(_("Compiler: ")); + version_msg((char *)compiler_version); + version_msg("\n"); + } + #endif + version_msg(_("Linking: ")); + version_msg((char *)all_lflags); + #endif + #ifdef DEBUG + version_msg("\n"); + version_msg(_(" DEBUG BUILD")); + #endif + } + + static void do_intro_line(int row, char_u *mesg, int add_version, int attr); + static void intro_message(int colon); + + /* + * Show the intro message when not editing a file. + */ + void + maybe_intro_message(void) + { + if (BUFEMPTY() + && curbuf->b_fname == NULL + && firstwin->w_next == NULL + && vim_strchr(p_shm, SHM_INTRO) == NULL) + intro_message(FALSE); + } + + /* + * Give an introductory message about Vim. + * Only used when starting Vim on an empty file, without a file name. + * Or with the ":intro" command (for Sven :-). + */ + static void + intro_message( + int colon) // TRUE for ":intro" + { + int i; + int row; + int blanklines; + int sponsor; + char *p; + static char *(lines[]) = + { + N_("VIM - Vi IMproved"), + "", + N_("version "), + N_("by Bram Moolenaar et al."), + #ifdef MODIFIED_BY + " ", + #endif + N_("Vim is open source and freely distributable"), + "", + N_("Help poor children in Uganda!"), + N_("type :help iccf for information "), + "", + N_("type :q to exit "), + N_("type :help or for on-line help"), + N_("type :help version8 for version info"), + NULL, + "", + N_("Running in Vi compatible mode"), + N_("type :set nocp for Vim defaults"), + N_("type :help cp-default for info on this"), + }; + #ifdef FEAT_GUI + static char *(gui_lines[]) = + { + NULL, + NULL, + NULL, + NULL, + #ifdef MODIFIED_BY + NULL, + #endif + NULL, + NULL, + NULL, + N_("menu Help->Orphans for information "), + NULL, + N_("Running modeless, typed text is inserted"), + N_("menu Edit->Global Settings->Toggle Insert Mode "), + N_(" for two modes "), + NULL, + NULL, + NULL, + N_("menu Edit->Global Settings->Toggle Vi Compatible"), + N_(" for Vim defaults "), + }; + #endif + + // blanklines = screen height - # message lines + blanklines = (int)Rows - ((sizeof(lines) / sizeof(char *)) - 1); + if (!p_cp) + blanklines += 4; // add 4 for not showing "Vi compatible" message + + // Don't overwrite a statusline. Depends on 'cmdheight'. + if (p_ls > 1) + blanklines -= Rows - topframe->fr_height; + if (blanklines < 0) + blanklines = 0; + + // Show the sponsor and register message one out of four times, the Uganda + // message two out of four times. + sponsor = (int)time(NULL); + sponsor = ((sponsor & 2) == 0) - ((sponsor & 4) == 0); + + // start displaying the message lines after half of the blank lines + row = blanklines / 2; + if ((row >= 2 && Columns >= 50) || colon) + { + for (i = 0; i < (int)(sizeof(lines) / sizeof(char *)); ++i) + { + p = lines[i]; + #ifdef FEAT_GUI + if (p_im && gui.in_use && gui_lines[i] != NULL) + p = gui_lines[i]; + #endif + if (p == NULL) + { + if (!p_cp) + break; + continue; + } + if (sponsor != 0) + { + if (strstr(p, "children") != NULL) + p = sponsor < 0 + ? N_("Sponsor Vim development!") + : N_("Become a registered Vim user!"); + else if (strstr(p, "iccf") != NULL) + p = sponsor < 0 + ? N_("type :help sponsor for information ") + : N_("type :help register for information "); + else if (strstr(p, "Orphans") != NULL) + p = N_("menu Help->Sponsor/Register for information "); + } + if (*p != NUL) + do_intro_line(row, (char_u *)_(p), i == 2, 0); + ++row; + } + } + + // Make the wait-return message appear just below the text. + if (colon) + msg_row = row; + } + + static void + do_intro_line( + int row, + char_u *mesg, + int add_version, + int attr) + { + char_u vers[20]; + int col; + char_u *p; + int l; + int clen; + #ifdef MODIFIED_BY + # define MODBY_LEN 150 + char_u modby[MODBY_LEN]; + + if (*mesg == ' ') + { + vim_strncpy(modby, (char_u *)_("Modified by "), MODBY_LEN - 1); + l = (int)STRLEN(modby); + vim_strncpy(modby + l, (char_u *)MODIFIED_BY, MODBY_LEN - l - 1); + mesg = modby; + } + #endif + + // Center the message horizontally. + col = vim_strsize(mesg); + if (add_version) + { + STRCPY(vers, mediumVersion); + if (highest_patch()) + { + // Check for 9.9x or 9.9xx, alpha/beta version + if (isalpha((int)vers[3])) + { + int len = (isalpha((int)vers[4])) ? 5 : 4; + sprintf((char *)vers + len, ".%d%s", highest_patch(), + mediumVersion + len); + } + else + sprintf((char *)vers + 3, ".%d", highest_patch()); + } + col += (int)STRLEN(vers); + } + col = (Columns - col) / 2; + if (col < 0) + col = 0; + + // Split up in parts to highlight <> items differently. + for (p = mesg; *p != NUL; p += l) + { + clen = 0; + for (l = 0; p[l] != NUL + && (l == 0 || (p[l] != '<' && p[l - 1] != '>')); ++l) + { + if (has_mbyte) + { + clen += ptr2cells(p + l); + l += (*mb_ptr2len)(p + l) - 1; + } + else + clen += byte2cells(p[l]); + } + screen_puts_len(p, l, row, col, *p == '<' ? HL_ATTR(HLF_8) : attr); + col += clen; + } + + // Add the version number to the version line. + if (add_version) + screen_puts(vers, row, col, 0); + } + + /* + * ":intro": clear screen, display intro screen and wait for return. + */ + void + ex_intro(exarg_T *eap UNUSED) + { + screenclear(); + intro_message(TRUE); + wait_return(TRUE); + } diff --git a/src/test/resources/unparser/diff/error07.txt b/src/test/resources/unparser/diff/error07.txt new file mode 100644 index 00000000..0032ccd8 --- /dev/null +++ b/src/test/resources/unparser/diff/error07.txt @@ -0,0 +1,9768 @@ +textDiff: + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + /* + * ex_docmd.c: functions for executing an Ex command line. + */ + + #include "vim.h" + + static int quitmore = 0; + static int ex_pressedreturn = FALSE; + #ifndef FEAT_PRINTER + # define ex_hardcopy ex_ni + #endif + + #ifdef FEAT_EVAL + static char_u *do_one_cmd(char_u **, int, cstack_T *, char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie); + #else + static char_u *do_one_cmd(char_u **, int, char_u *(*fgetline)(int, void *, int, getline_opt_T), void *cookie); + static int if_level = 0; // depth in :if + #endif + static void append_command(char_u *cmd); + + #ifndef FEAT_MENU + # define ex_emenu ex_ni + # define ex_menu ex_ni + # define ex_menutranslate ex_ni + #endif + static void ex_autocmd(exarg_T *eap); + static void ex_doautocmd(exarg_T *eap); + static void ex_bunload(exarg_T *eap); + static void ex_buffer(exarg_T *eap); + static void ex_bmodified(exarg_T *eap); + static void ex_bnext(exarg_T *eap); + static void ex_bprevious(exarg_T *eap); + static void ex_brewind(exarg_T *eap); + static void ex_blast(exarg_T *eap); + static char_u *getargcmd(char_u **); + static int getargopt(exarg_T *eap); + #ifndef FEAT_QUICKFIX + # define ex_make ex_ni + # define ex_cbuffer ex_ni + # define ex_cc ex_ni + # define ex_cnext ex_ni + # define ex_cbelow ex_ni + # define ex_cfile ex_ni + # define qf_list ex_ni + # define qf_age ex_ni + # define qf_history ex_ni + # define ex_helpgrep ex_ni + # define ex_vimgrep ex_ni + # define ex_cclose ex_ni + # define ex_copen ex_ni + # define ex_cwindow ex_ni + # define ex_cbottom ex_ni + #endif + #if !defined(FEAT_QUICKFIX) || !defined(FEAT_EVAL) + # define ex_cexpr ex_ni + #endif + + static linenr_T default_address(exarg_T *eap); + static linenr_T get_address(exarg_T *, char_u **, cmd_addr_T addr_type, int skip, int silent, int to_other_file, int address_count); + static void address_default_all(exarg_T *eap); + static void get_flags(exarg_T *eap); + #if !defined(FEAT_PERL) \ + || !defined(FEAT_PYTHON) || !defined(FEAT_PYTHON3) \ + || !defined(FEAT_TCL) \ + || !defined(FEAT_RUBY) \ + || !defined(FEAT_LUA) \ + || !defined(FEAT_MZSCHEME) + # define HAVE_EX_SCRIPT_NI + static void ex_script_ni(exarg_T *eap); + #endif + static char *invalid_range(exarg_T *eap); + static void correct_range(exarg_T *eap); + #ifdef FEAT_QUICKFIX + static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep); + #endif + static char_u *repl_cmdline(exarg_T *eap, char_u *src, int srclen, char_u *repl, char_u **cmdlinep); + static void ex_highlight(exarg_T *eap); + static void ex_colorscheme(exarg_T *eap); + static void ex_cquit(exarg_T *eap); + static void ex_quit_all(exarg_T *eap); + static void ex_close(exarg_T *eap); + static void ex_win_close(int forceit, win_T *win, tabpage_T *tp); + static void ex_only(exarg_T *eap); + static void ex_resize(exarg_T *eap); + static void ex_stag(exarg_T *eap); + static void ex_tabclose(exarg_T *eap); + static void ex_tabonly(exarg_T *eap); + static void ex_tabnext(exarg_T *eap); + static void ex_tabmove(exarg_T *eap); + static void ex_tabs(exarg_T *eap); + #if defined(FEAT_QUICKFIX) + static void ex_pclose(exarg_T *eap); + static void ex_ptag(exarg_T *eap); + static void ex_pedit(exarg_T *eap); + #else + # define ex_pclose ex_ni + # define ex_ptag ex_ni + # define ex_pedit ex_ni + #endif + static void ex_hide(exarg_T *eap); + static void ex_exit(exarg_T *eap); + static void ex_print(exarg_T *eap); + #ifdef FEAT_BYTEOFF + static void ex_goto(exarg_T *eap); + #else + # define ex_goto ex_ni + #endif + static void ex_shell(exarg_T *eap); + static void ex_preserve(exarg_T *eap); + static void ex_recover(exarg_T *eap); + static void ex_mode(exarg_T *eap); + static void ex_wrongmodifier(exarg_T *eap); + static void ex_find(exarg_T *eap); + static void ex_open(exarg_T *eap); + static void ex_edit(exarg_T *eap); + #ifndef FEAT_GUI + # define ex_gui ex_nogui + static void ex_nogui(exarg_T *eap); + #endif + #if defined(FEAT_GUI_MSWIN) && defined(FEAT_MENU) && defined(FEAT_TEAROFF) + static void ex_tearoff(exarg_T *eap); + #else + # define ex_tearoff ex_ni + #endif + #if (defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \ + || defined(FEAT_TERM_POPUP_MENU)) && defined(FEAT_MENU) + static void ex_popup(exarg_T *eap); + #else + # define ex_popup ex_ni + #endif + #ifndef FEAT_GUI_MSWIN + # define ex_simalt ex_ni + #endif + #if !defined(FEAT_GUI_MSWIN) && !defined(FEAT_GUI_GTK) && !defined(FEAT_GUI_MOTIF) + # define gui_mch_find_dialog ex_ni + # define gui_mch_replace_dialog ex_ni + #endif + #if !defined(FEAT_GUI_GTK) + # define ex_helpfind ex_ni + #endif + #ifndef FEAT_CSCOPE + # define ex_cscope ex_ni + # define ex_scscope ex_ni + # define ex_cstag ex_ni + #endif + #ifndef FEAT_SYN_HL + # define ex_syntax ex_ni + # define ex_ownsyntax ex_ni + #endif + #if !defined(FEAT_SYN_HL) || !defined(FEAT_PROFILE) + # define ex_syntime ex_ni + #endif + #ifndef FEAT_SPELL + # define ex_spell ex_ni + # define ex_mkspell ex_ni + # define ex_spelldump ex_ni + # define ex_spellinfo ex_ni + # define ex_spellrepall ex_ni + #endif + #ifndef FEAT_PERSISTENT_UNDO + # define ex_rundo ex_ni + # define ex_wundo ex_ni + #endif + #ifndef FEAT_LUA + # define ex_lua ex_script_ni + # define ex_luado ex_ni + # define ex_luafile ex_ni + #endif + #ifndef FEAT_MZSCHEME + # define ex_mzscheme ex_script_ni + # define ex_mzfile ex_ni + #endif + #ifndef FEAT_PERL + # define ex_perl ex_script_ni + # define ex_perldo ex_ni + #endif + #ifndef FEAT_PYTHON + # define ex_python ex_script_ni + # define ex_pydo ex_ni + # define ex_pyfile ex_ni + #endif + #ifndef FEAT_PYTHON3 + # define ex_py3 ex_script_ni + # define ex_py3do ex_ni + # define ex_py3file ex_ni + #endif + #if !defined(FEAT_PYTHON) && !defined(FEAT_PYTHON3) + # define ex_pyx ex_script_ni + # define ex_pyxdo ex_ni + # define ex_pyxfile ex_ni + #endif + #ifndef FEAT_TCL + # define ex_tcl ex_script_ni + # define ex_tcldo ex_ni + # define ex_tclfile ex_ni + #endif + #ifndef FEAT_RUBY + # define ex_ruby ex_script_ni + # define ex_rubydo ex_ni + # define ex_rubyfile ex_ni + #endif + #ifndef FEAT_KEYMAP + # define ex_loadkeymap ex_ni + #endif + static void ex_swapname(exarg_T *eap); + static void ex_syncbind(exarg_T *eap); + static void ex_read(exarg_T *eap); + static void ex_pwd(exarg_T *eap); + static void ex_equal(exarg_T *eap); + static void ex_sleep(exarg_T *eap); + static void ex_winsize(exarg_T *eap); + static void ex_wincmd(exarg_T *eap); + #if defined(FEAT_GUI) || defined(UNIX) || defined(VMS) || defined(MSWIN) + static void ex_winpos(exarg_T *eap); + #else + # define ex_winpos ex_ni + #endif + static void ex_operators(exarg_T *eap); + static void ex_put(exarg_T *eap); + static void ex_copymove(exarg_T *eap); + static void ex_submagic(exarg_T *eap); + static void ex_join(exarg_T *eap); + static void ex_at(exarg_T *eap); + static void ex_bang(exarg_T *eap); + static void ex_undo(exarg_T *eap); + #ifdef FEAT_PERSISTENT_UNDO + static void ex_wundo(exarg_T *eap); + static void ex_rundo(exarg_T *eap); + #endif + static void ex_redo(exarg_T *eap); + static void ex_later(exarg_T *eap); + static void ex_redir(exarg_T *eap); + static void ex_redrawstatus(exarg_T *eap); + static void ex_redrawtabline(exarg_T *eap); + static void close_redir(void); + static void ex_mark(exarg_T *eap); + static void ex_startinsert(exarg_T *eap); + static void ex_stopinsert(exarg_T *eap); + #ifdef FEAT_FIND_ID + static void ex_checkpath(exarg_T *eap); + static void ex_findpat(exarg_T *eap); + #else + # define ex_findpat ex_ni + # define ex_checkpath ex_ni + #endif + #if defined(FEAT_FIND_ID) && defined(FEAT_QUICKFIX) + static void ex_psearch(exarg_T *eap); + #else + # define ex_psearch ex_ni + #endif + static void ex_tag(exarg_T *eap); + static void ex_tag_cmd(exarg_T *eap, char_u *name); + #ifndef FEAT_EVAL + # define ex_block ex_ni + # define ex_break ex_ni + # define ex_breakadd ex_ni + # define ex_breakdel ex_ni + # define ex_breaklist ex_ni + # define ex_call ex_ni + # define ex_catch ex_ni + # define ex_compiler ex_ni + # define ex_continue ex_ni + # define ex_debug ex_ni + # define ex_debuggreedy ex_ni + # define ex_defcompile ex_ni + # define ex_delfunction ex_ni + # define ex_disassemble ex_ni + # define ex_echo ex_ni + # define ex_echohl ex_ni + # define ex_else ex_ni + # define ex_endblock ex_ni + # define ex_endfunction ex_ni + # define ex_endif ex_ni + # define ex_endtry ex_ni + # define ex_endwhile ex_ni + # define ex_eval ex_ni + # define ex_execute ex_ni + # define ex_incdec ex_ni + # define ex_finally ex_ni + # define ex_finish ex_ni + # define ex_function ex_ni + # define ex_if ex_ni + # define ex_let ex_ni + # define ex_var ex_ni + # define ex_lockvar ex_ni + # define ex_oldfiles ex_ni + # define ex_options ex_ni + # define ex_packadd ex_ni + # define ex_packloadall ex_ni + # define ex_return ex_ni + # define ex_scriptnames ex_ni + # define ex_throw ex_ni + # define ex_try ex_ni + # define ex_unlet ex_ni + # define ex_while ex_ni + # define ex_import ex_ni + # define ex_export ex_ni + #endif + #ifndef FEAT_SESSION + # define ex_loadview ex_ni + #endif + #ifndef FEAT_VIMINFO + # define ex_viminfo ex_ni + #endif + static void ex_behave(exarg_T *eap); + static void ex_filetype(exarg_T *eap); + static void ex_setfiletype(exarg_T *eap); + #ifndef FEAT_DIFF + # define ex_diffoff ex_ni + # define ex_diffpatch ex_ni + # define ex_diffgetput ex_ni + # define ex_diffsplit ex_ni + # define ex_diffthis ex_ni + # define ex_diffupdate ex_ni + #endif + static void ex_digraphs(exarg_T *eap); + #ifdef FEAT_SEARCH_EXTRA + static void ex_nohlsearch(exarg_T *eap); + #else + # define ex_nohlsearch ex_ni + # define ex_match ex_ni + #endif + #ifdef FEAT_CRYPT + static void ex_X(exarg_T *eap); + #else + # define ex_X ex_ni + #endif + #ifdef FEAT_FOLDING + static void ex_fold(exarg_T *eap); + static void ex_foldopen(exarg_T *eap); + static void ex_folddo(exarg_T *eap); + #else + # define ex_fold ex_ni + # define ex_foldopen ex_ni + # define ex_folddo ex_ni + #endif + #if !(defined(HAVE_LOCALE_H) || defined(X_LOCALE)) + # define ex_language ex_ni + #endif + #ifndef FEAT_SIGNS + # define ex_sign ex_ni + #endif + #ifndef FEAT_NETBEANS_INTG + # define ex_nbclose ex_ni + # define ex_nbkey ex_ni + # define ex_nbstart ex_ni + #endif + + #ifndef FEAT_PROFILE + # define ex_profile ex_ni + #endif + #ifndef FEAT_TERMINAL + # define ex_terminal ex_ni + #endif + #if !defined(FEAT_X11) || !defined(FEAT_XCLIPBOARD) + # define ex_xrestore ex_ni + #endif + #if !defined(FEAT_PROP_POPUP) + # define ex_popupclear ex_ni + #endif + + /* + * Declare cmdnames[]. + */ + #define DO_DECLARE_EXCMD + #include "ex_cmds.h" + #include "ex_cmdidxs.h" + + static char_u dollar_command[2] = {'$', 0}; + + + #ifdef FEAT_EVAL + // Struct for storing a line inside a while/for loop + typedef struct + { + char_u *line; // command line + linenr_T lnum; // sourcing_lnum of the line + } wcmd_T; + + /* + * Structure used to store info for line position in a while or for loop. + * This is required, because do_one_cmd() may invoke ex_function(), which + * reads more lines that may come from the while/for loop. + */ + struct loop_cookie + { + garray_T *lines_gap; // growarray with line info + int current_line; // last read line from growarray + int repeating; // TRUE when looping a second time + // When "repeating" is FALSE use "getline" and "cookie" to get lines + char_u *(*getline)(int, void *, int, getline_opt_T); + void *cookie; + }; + + static char_u *get_loop_line(int c, void *cookie, int indent, getline_opt_T options); + static int store_loop_line(garray_T *gap, char_u *line); + static void free_cmdlines(garray_T *gap); + + // Struct to save a few things while debugging. Used in do_cmdline() only. + struct dbg_stuff + { + int trylevel; + int force_abort; + except_T *caught_stack; + char_u *vv_exception; + char_u *vv_throwpoint; + int did_emsg; + int got_int; + int did_throw; + int need_rethrow; + int check_cstack; + except_T *current_exception; + }; + + static void + save_dbg_stuff(struct dbg_stuff *dsp) + { + dsp->trylevel = trylevel; trylevel = 0; + dsp->force_abort = force_abort; force_abort = FALSE; + dsp->caught_stack = caught_stack; caught_stack = NULL; + dsp->vv_exception = v_exception(NULL); + dsp->vv_throwpoint = v_throwpoint(NULL); + + // Necessary for debugging an inactive ":catch", ":finally", ":endtry" + dsp->did_emsg = did_emsg; did_emsg = FALSE; + dsp->got_int = got_int; got_int = FALSE; + dsp->did_throw = did_throw; did_throw = FALSE; + dsp->need_rethrow = need_rethrow; need_rethrow = FALSE; + dsp->check_cstack = check_cstack; check_cstack = FALSE; + dsp->current_exception = current_exception; current_exception = NULL; + } + + static void + restore_dbg_stuff(struct dbg_stuff *dsp) + { + suppress_errthrow = FALSE; + trylevel = dsp->trylevel; + force_abort = dsp->force_abort; + caught_stack = dsp->caught_stack; + (void)v_exception(dsp->vv_exception); + (void)v_throwpoint(dsp->vv_throwpoint); + did_emsg = dsp->did_emsg; + got_int = dsp->got_int; + did_throw = dsp->did_throw; + need_rethrow = dsp->need_rethrow; + check_cstack = dsp->check_cstack; + current_exception = dsp->current_exception; + } + #endif + + /* + * do_exmode(): Repeatedly get commands for the "Ex" mode, until the ":vi" + * command is given. + */ + void + do_exmode( + int improved) // TRUE for "improved Ex" mode + { + int save_msg_scroll; + int prev_msg_row; + linenr_T prev_line; + varnumber_T changedtick; + + if (improved) + exmode_active = EXMODE_VIM; + else + exmode_active = EXMODE_NORMAL; + State = MODE_NORMAL; + may_trigger_modechanged(); + + // When using ":global /pat/ visual" and then "Q" we return to continue + // the :global command. + if (global_busy) + return; + + save_msg_scroll = msg_scroll; + ++RedrawingDisabled; // don't redisplay the window + ++no_wait_return; // don't wait for return + #ifdef FEAT_GUI + // Ignore scrollbar and mouse events in Ex mode + ++hold_gui_events; + #endif + + msg(_("Entering Ex mode. Type \"visual\" to go to Normal mode.")); + while (exmode_active) + { + // Check for a ":normal" command and no more characters left. + if (ex_normal_busy > 0 && typebuf.tb_len == 0) + { + exmode_active = FALSE; + break; + } + msg_scroll = TRUE; + need_wait_return = FALSE; + ex_pressedreturn = FALSE; + ex_no_reprint = FALSE; + changedtick = CHANGEDTICK(curbuf); + prev_msg_row = msg_row; + prev_line = curwin->w_cursor.lnum; + if (improved) + { + cmdline_row = msg_row; + do_cmdline(NULL, getexline, NULL, 0); + } + else + do_cmdline(NULL, getexmodeline, NULL, DOCMD_NOWAIT); + lines_left = Rows - 1; + + if ((prev_line != curwin->w_cursor.lnum + || changedtick != CHANGEDTICK(curbuf)) && !ex_no_reprint) + { + if (curbuf->b_ml.ml_flags & ML_EMPTY) + emsg(_(e_empty_buffer)); + else + { + if (ex_pressedreturn) + { + // go up one line, to overwrite the ":" line, so the + // output doesn't contain empty lines. + msg_row = prev_msg_row; + if (prev_msg_row == Rows - 1) + msg_row--; + } + msg_col = 0; + print_line_no_prefix(curwin->w_cursor.lnum, FALSE, FALSE); + msg_clr_eos(); + } + } + else if (ex_pressedreturn && !ex_no_reprint) // must be at EOF + { + if (curbuf->b_ml.ml_flags & ML_EMPTY) + emsg(_(e_empty_buffer)); + else + emsg(_(e_at_end_of_file)); + } + } + + #ifdef FEAT_GUI + --hold_gui_events; + #endif + --RedrawingDisabled; + --no_wait_return; + update_screen(UPD_CLEAR); + need_wait_return = FALSE; + msg_scroll = save_msg_scroll; + } + + /* + * Print the executed command for when 'verbose' is set. + * When "lnum" is 0 only print the command. + */ + static void + msg_verbose_cmd(linenr_T lnum, char_u *cmd) + { + ++no_wait_return; + verbose_enter_scroll(); + + if (lnum == 0) + smsg(_("Executing: %s"), cmd); + else + smsg(_("line %ld: %s"), (long)lnum, cmd); + if (msg_silent == 0) + msg_puts("\n"); // don't overwrite this + + verbose_leave_scroll(); + --no_wait_return; + } + + /* + * Execute a simple command line. Used for translated commands like "*". + */ + int + do_cmdline_cmd(char_u *cmd) + { + return do_cmdline(cmd, NULL, NULL, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); + } + + /* + * Execute the "+cmd" argument of "edit +cmd fname" and the like. + * This allows for using a range without ":" in Vim9 script. + */ + static int + do_cmd_argument(char_u *cmd) + { + return do_cmdline(cmd, NULL, NULL, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED|DOCMD_RANGEOK); + } + + /* + * do_cmdline(): execute one Ex command line + * + * 1. Execute "cmdline" when it is not NULL. + * If "cmdline" is NULL, or more lines are needed, fgetline() is used. + * 2. Split up in parts separated with '|'. + * + * This function can be called recursively! + * + * flags: + * DOCMD_VERBOSE - The command will be included in the error message. + * DOCMD_NOWAIT - Don't call wait_return() and friends. + * DOCMD_REPEAT - Repeat execution until fgetline() returns NULL. + * DOCMD_KEYTYPED - Don't reset KeyTyped. + * DOCMD_EXCRESET - Reset the exception environment (used for debugging). + * DOCMD_KEEPLINE - Store first typed line (for repeating with "."). + * + * return FAIL if cmdline could not be executed, OK otherwise + */ + int + do_cmdline( + char_u *cmdline, + char_u *(*fgetline)(int, void *, int, getline_opt_T), + void *cookie, // argument for fgetline() + int flags) + { + char_u *next_cmdline; // next cmd to execute + char_u *cmdline_copy = NULL; // copy of cmd line + int used_getline = FALSE; // used "fgetline" to obtain command + static int recursive = 0; // recursive depth + int msg_didout_before_start = 0; + int count = 0; // line number count + int did_inc = FALSE; // incremented RedrawingDisabled + int retval = OK; + #ifdef FEAT_EVAL + cstack_T cstack; // conditional stack + garray_T lines_ga; // keep lines for ":while"/":for" + int current_line = 0; // active line in lines_ga + int current_line_before = 0; + char_u *fname = NULL; // function or script name + linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie + int *dbg_tick = NULL; // ptr to dbg_tick field in cookie + struct dbg_stuff debug_saved; // saved things for debug mode + int initial_trylevel; + msglist_T **saved_msg_list = NULL; + msglist_T *private_msg_list = NULL; + + // "fgetline" and "cookie" passed to do_one_cmd() + char_u *(*cmd_getline)(int, void *, int, getline_opt_T); + void *cmd_cookie; + struct loop_cookie cmd_loop_cookie; + void *real_cookie; + int getline_is_func; + #else + # define cmd_getline fgetline + # define cmd_cookie cookie + #endif + static int call_depth = 0; // recursiveness + #ifdef FEAT_EVAL + // For every pair of do_cmdline()/do_one_cmd() calls, use an extra memory + // location for storing error messages to be converted to an exception. + // This ensures that the do_errthrow() call in do_one_cmd() does not + // combine the messages stored by an earlier invocation of do_one_cmd() + // with the command name of the later one. This would happen when + // BufWritePost autocommands are executed after a write error. + saved_msg_list = msg_list; + msg_list = &private_msg_list; + #endif + + // It's possible to create an endless loop with ":execute", catch that + // here. The value of 200 allows nested function calls, ":source", etc. + // Allow 200 or 'maxfuncdepth', whatever is larger. + if (call_depth >= 200 + #ifdef FEAT_EVAL + && call_depth >= p_mfd + #endif + ) + { + emsg(_(e_command_too_recursive)); + #ifdef FEAT_EVAL + // When converting to an exception, we do not include the command name + // since this is not an error of the specific command. + do_errthrow((cstack_T *)NULL, (char_u *)NULL); + msg_list = saved_msg_list; + #endif + return FAIL; + } + ++call_depth; + + #ifdef FEAT_EVAL + CLEAR_FIELD(cstack); + cstack.cs_idx = -1; + ga_init2(&lines_ga, sizeof(wcmd_T), 10); + + real_cookie = getline_cookie(fgetline, cookie); + + // Inside a function use a higher nesting level. + getline_is_func = getline_equal(fgetline, cookie, get_func_line); + if (getline_is_func && ex_nesting_level == func_level(real_cookie)) + ++ex_nesting_level; + + // Get the function or script name and the address where the next breakpoint + // line and the debug tick for a function or script are stored. + if (getline_is_func) + { + fname = func_name(real_cookie); + breakpoint = func_breakpoint(real_cookie); + dbg_tick = func_dbg_tick(real_cookie); + } + else if (getline_equal(fgetline, cookie, getsourceline)) + { + fname = SOURCING_NAME; + breakpoint = source_breakpoint(real_cookie); + dbg_tick = source_dbg_tick(real_cookie); + } + + /* + * Initialize "force_abort" and "suppress_errthrow" at the top level. + */ + if (!recursive) + { + force_abort = FALSE; + suppress_errthrow = FALSE; + } + + /* + * If requested, store and reset the global values controlling the + * exception handling (used when debugging). Otherwise clear it to avoid + * a bogus compiler warning when the optimizer uses inline functions... + */ + if (flags & DOCMD_EXCRESET) + save_dbg_stuff(&debug_saved); + else + CLEAR_FIELD(debug_saved); + + initial_trylevel = trylevel; + + /* + * "did_throw" will be set to TRUE when an exception is being thrown. + */ + did_throw = FALSE; + #endif + /* + * "did_emsg" will be set to TRUE when emsg() is used, in which case we + * cancel the whole command line, and any if/endif or loop. + * If force_abort is set, we cancel everything. + */ + #ifdef FEAT_EVAL + did_emsg_cumul += did_emsg; + #endif + did_emsg = FALSE; + + /* + * KeyTyped is only set when calling vgetc(). Reset it here when not + * calling vgetc() (sourced command lines). + */ + if (!(flags & DOCMD_KEYTYPED) + && !getline_equal(fgetline, cookie, getexline)) + KeyTyped = FALSE; + + /* + * Continue executing command lines: + * - when inside an ":if", ":while" or ":for" + * - for multiple commands on one line, separated with '|' + * - when repeating until there are no more lines (for ":source") + */ + next_cmdline = cmdline; + do + { + #ifdef FEAT_EVAL + getline_is_func = getline_equal(fgetline, cookie, get_func_line); + #endif + + // stop skipping cmds for an error msg after all endif/while/for + if (next_cmdline == NULL + #ifdef FEAT_EVAL + && !force_abort + && cstack.cs_idx < 0 + && !(getline_is_func && func_has_abort(real_cookie)) + #endif + ) + { + #ifdef FEAT_EVAL + did_emsg_cumul += did_emsg; + #endif + did_emsg = FALSE; + } + + /* + * 1. If repeating a line in a loop, get a line from lines_ga. + * 2. If no line given: Get an allocated line with fgetline(). + * 3. If a line is given: Make a copy, so we can mess with it. + */ + + #ifdef FEAT_EVAL + // 1. If repeating, get a previous line from lines_ga. + if (cstack.cs_looplevel > 0 && current_line < lines_ga.ga_len) + { + // Each '|' separated command is stored separately in lines_ga, to + // be able to jump to it. Don't use next_cmdline now. + VIM_CLEAR(cmdline_copy); + + // Check if a function has returned or, unless it has an unclosed + // try conditional, aborted. + if (getline_is_func) + { + # ifdef FEAT_PROFILE + if (do_profiling == PROF_YES) + func_line_end(real_cookie); + # endif + if (func_has_ended(real_cookie)) + { + retval = FAIL; + break; + } + } + #ifdef FEAT_PROFILE + else if (do_profiling == PROF_YES + && getline_equal(fgetline, cookie, getsourceline)) + script_line_end(); + #endif + + // Check if a sourced file hit a ":finish" command. + if (source_finished(fgetline, cookie)) + { + retval = FAIL; + break; + } + + // If breakpoints have been added/deleted need to check for it. + if (breakpoint != NULL && dbg_tick != NULL + && *dbg_tick != debug_tick) + { + *breakpoint = dbg_find_breakpoint( + getline_equal(fgetline, cookie, getsourceline), + fname, SOURCING_LNUM); + *dbg_tick = debug_tick; + } + + next_cmdline = ((wcmd_T *)(lines_ga.ga_data))[current_line].line; + SOURCING_LNUM = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum; + + // Did we encounter a breakpoint? + if (breakpoint != NULL && *breakpoint != 0 + && *breakpoint <= SOURCING_LNUM) + { + dbg_breakpoint(fname, SOURCING_LNUM); + // Find next breakpoint. + *breakpoint = dbg_find_breakpoint( + getline_equal(fgetline, cookie, getsourceline), + fname, SOURCING_LNUM); + *dbg_tick = debug_tick; + } + # ifdef FEAT_PROFILE + if (do_profiling == PROF_YES) + { + if (getline_is_func) + func_line_start(real_cookie, SOURCING_LNUM); + else if (getline_equal(fgetline, cookie, getsourceline)) + script_line_start(); + } + # endif + } + #endif + + // 2. If no line given, get an allocated line with fgetline(). + if (next_cmdline == NULL) + { + /* + * Need to set msg_didout for the first line after an ":if", + * otherwise the ":if" will be overwritten. + */ + if (count == 1 && getline_equal(fgetline, cookie, getexline)) + msg_didout = TRUE; + if (fgetline == NULL || (next_cmdline = fgetline(':', cookie, + #ifdef FEAT_EVAL + cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2 + #else + 0 + #endif + , in_vim9script() ? GETLINE_CONCAT_CONTBAR + : GETLINE_CONCAT_CONT)) == NULL) + { + // Don't call wait_return for aborted command line. The NULL + // returned for the end of a sourced file or executed function + // doesn't do this. + if (KeyTyped && !(flags & DOCMD_REPEAT)) + need_wait_return = FALSE; + retval = FAIL; + break; + } + used_getline = TRUE; + + /* + * Keep the first typed line. Clear it when more lines are typed. + */ + if (flags & DOCMD_KEEPLINE) + { + vim_free(repeat_cmdline); + if (count == 0) + repeat_cmdline = vim_strsave(next_cmdline); + else + repeat_cmdline = NULL; + } + } + + // 3. Make a copy of the command so we can mess with it. + else if (cmdline_copy == NULL) + { + next_cmdline = vim_strsave(next_cmdline); + if (next_cmdline == NULL) + { + emsg(_(e_out_of_memory)); + retval = FAIL; + break; + } + } + cmdline_copy = next_cmdline; + + #ifdef FEAT_EVAL + /* + * Inside a while/for loop, and when the command looks like a ":while" + * or ":for", the line is stored, because we may need it later when + * looping. + * + * When there is a '|' and another command, it is stored separately, + * because we need to be able to jump back to it from an + * :endwhile/:endfor. + * + * Pass a different "fgetline" function to do_one_cmd() below, + * that it stores lines in or reads them from "lines_ga". Makes it + * possible to define a function inside a while/for loop and handles + * line continuation. + */ + if ((cstack.cs_looplevel > 0 || has_loop_cmd(next_cmdline))) + { + cmd_getline = get_loop_line; + cmd_cookie = (void *)&cmd_loop_cookie; + cmd_loop_cookie.lines_gap = &lines_ga; + cmd_loop_cookie.current_line = current_line; + cmd_loop_cookie.getline = fgetline; + cmd_loop_cookie.cookie = cookie; + cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len); + + // Save the current line when encountering it the first time. + if (current_line == lines_ga.ga_len + && store_loop_line(&lines_ga, next_cmdline) == FAIL) + { + retval = FAIL; + break; + } + current_line_before = current_line; + } + else + { + cmd_getline = fgetline; + cmd_cookie = cookie; + } + + did_endif = FALSE; + #endif + + if (count++ == 0) + { + /* + * All output from the commands is put below each other, without + * waiting for a return. Don't do this when executing commands + * from a script or when being called recursive (e.g. for ":e + * +command file"). + */ + if (!(flags & DOCMD_NOWAIT) && !recursive) + { + msg_didout_before_start = msg_didout; + msg_didany = FALSE; // no output yet + msg_start(); + msg_scroll = TRUE; // put messages below each other + ++no_wait_return; // don't wait for return until finished + ++RedrawingDisabled; + did_inc = TRUE; + } + } + + if ((p_verbose >= 15 && SOURCING_NAME != NULL) || p_verbose >= 16) + msg_verbose_cmd(SOURCING_LNUM, cmdline_copy); + + /* + * 2. Execute one '|' separated command. + * do_one_cmd() will return NULL if there is no trailing '|'. + * "cmdline_copy" can change, e.g. for '%' and '#' expansion. + */ + ++recursive; + next_cmdline = do_one_cmd(&cmdline_copy, flags, + #ifdef FEAT_EVAL + &cstack, + #endif + cmd_getline, cmd_cookie); + --recursive; + + #ifdef FEAT_EVAL + if (cmd_cookie == (void *)&cmd_loop_cookie) + // Use "current_line" from "cmd_loop_cookie", it may have been + // incremented when defining a function. + current_line = cmd_loop_cookie.current_line; + #endif + + if (next_cmdline == NULL) + { + VIM_CLEAR(cmdline_copy); + + /* + * If the command was typed, remember it for the ':' register. + * Do this AFTER executing the command to make :@: work. + */ + if (getline_equal(fgetline, cookie, getexline) + && new_last_cmdline != NULL) + { + vim_free(last_cmdline); + last_cmdline = new_last_cmdline; + new_last_cmdline = NULL; + } + } + else + { + // need to copy the command after the '|' to cmdline_copy, for the + // next do_one_cmd() + STRMOVE(cmdline_copy, next_cmdline); + next_cmdline = cmdline_copy; + } + + + #ifdef FEAT_EVAL + // reset did_emsg for a function that is not aborted by an error + if (did_emsg && !force_abort + && getline_equal(fgetline, cookie, get_func_line) + && !func_has_abort(real_cookie)) + { + // did_emsg_cumul is not set here + did_emsg = FALSE; + } + + if (cstack.cs_looplevel > 0) + { + ++current_line; + + /* + * An ":endwhile", ":endfor" and ":continue" is handled here. + * If we were executing commands, jump back to the ":while" or + * ":for". + * If we were not executing commands, decrement cs_looplevel. + */ + if (cstack.cs_lflags & (CSL_HAD_CONT | CSL_HAD_ENDLOOP)) + { + cstack.cs_lflags &= ~(CSL_HAD_CONT | CSL_HAD_ENDLOOP); + + // Jump back to the matching ":while" or ":for". Be careful + // not to use a cs_line[] from an entry that isn't a ":while" + // or ":for": It would make "current_line" invalid and can + // cause a crash. + if (!did_emsg && !got_int && !did_throw + && cstack.cs_idx >= 0 + && (cstack.cs_flags[cstack.cs_idx] + & (CSF_WHILE | CSF_FOR)) + && cstack.cs_line[cstack.cs_idx] >= 0 + && (cstack.cs_flags[cstack.cs_idx] & CSF_ACTIVE)) + { + current_line = cstack.cs_line[cstack.cs_idx]; + // remember we jumped there + cstack.cs_lflags |= CSL_HAD_LOOP; + line_breakcheck(); // check if CTRL-C typed + + // Check for the next breakpoint at or after the ":while" + // or ":for". + if (breakpoint != NULL) + { + *breakpoint = dbg_find_breakpoint( + getline_equal(fgetline, cookie, getsourceline), + fname, + ((wcmd_T *)lines_ga.ga_data)[current_line].lnum-1); + *dbg_tick = debug_tick; + } + } + else + { + // can only get here with ":endwhile" or ":endfor" + if (cstack.cs_idx >= 0) + rewind_conditionals(&cstack, cstack.cs_idx - 1, + CSF_WHILE | CSF_FOR, &cstack.cs_looplevel); + } + } + + /* + * For a ":while" or ":for" we need to remember the line number. + */ + else if (cstack.cs_lflags & CSL_HAD_LOOP) + { + cstack.cs_lflags &= ~CSL_HAD_LOOP; + cstack.cs_line[cstack.cs_idx] = current_line_before; + } + } + + // Check for the next breakpoint after a watchexpression + if (breakpoint != NULL && has_watchexpr()) + { + *breakpoint = dbg_find_breakpoint(FALSE, fname, SOURCING_LNUM); + *dbg_tick = debug_tick; + } + + /* + * When not inside any ":while" loop, clear remembered lines. + */ + if (cstack.cs_looplevel == 0) + { + if (lines_ga.ga_len > 0) + { + SOURCING_LNUM = + ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum; + free_cmdlines(&lines_ga); + } + current_line = 0; + } + + /* + * A ":finally" makes did_emsg, got_int, and did_throw pending for + * being restored at the ":endtry". Reset them here and set the + * ACTIVE and FINALLY flags, so that the finally clause gets executed. + * This includes the case where a missing ":endif", ":endwhile" or + * ":endfor" was detected by the ":finally" itself. + */ + if (cstack.cs_lflags & CSL_HAD_FINA) + { + cstack.cs_lflags &= ~CSL_HAD_FINA; + report_make_pending(cstack.cs_pending[cstack.cs_idx] + & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW), + did_throw ? (void *)current_exception : NULL); + did_emsg = got_int = did_throw = FALSE; + cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; + } + + // Update global "trylevel" for recursive calls to do_cmdline() from + // within this loop. + trylevel = initial_trylevel + cstack.cs_trylevel; + + /* + * If the outermost try conditional (across function calls and sourced + * files) is aborted because of an error, an interrupt, or an uncaught + * exception, cancel everything. If it is left normally, reset + * force_abort to get the non-EH compatible abortion behavior for + * the rest of the script. + */ + if (trylevel == 0 && !did_emsg && !got_int && !did_throw) + force_abort = FALSE; + + // Convert an interrupt to an exception if appropriate. + (void)do_intthrow(&cstack); + #endif // FEAT_EVAL + + } + /* + * Continue executing command lines when: + * - no CTRL-C typed, no aborting error, no exception thrown or try + * conditionals need to be checked for executing finally clauses or + * catching an interrupt exception + * - didn't get an error message or lines are not typed + * - there is a command after '|', inside a :if, :while, :for or :try, or + * looping for ":source" command or function call. + */ + while (!((got_int + #ifdef FEAT_EVAL + || (did_emsg && (force_abort || in_vim9script())) + || did_throw + #endif + ) + #ifdef FEAT_EVAL + && cstack.cs_trylevel == 0 + #endif + ) + && !(did_emsg + #ifdef FEAT_EVAL + // Keep going when inside try/catch, so that the error can be + // deal with, except when it is a syntax error, it may cause + // the :endtry to be missed. + && (cstack.cs_trylevel == 0 || did_emsg_syntax) + #endif + && used_getline + && (getline_equal(fgetline, cookie, getexmodeline) + || getline_equal(fgetline, cookie, getexline))) + && (next_cmdline != NULL + #ifdef FEAT_EVAL + || cstack.cs_idx >= 0 + #endif + || (flags & DOCMD_REPEAT))); + + vim_free(cmdline_copy); + did_emsg_syntax = FALSE; + #ifdef FEAT_EVAL + free_cmdlines(&lines_ga); + ga_clear(&lines_ga); + + if (cstack.cs_idx >= 0) + { + /* + * If a sourced file or executed function ran to its end, report the + * unclosed conditional. + * In Vim9 script do not give a second error, executing aborts after + * the first one. + */ + if (!got_int && !did_throw && !aborting() + && !(did_emsg && in_vim9script()) + && ((getline_equal(fgetline, cookie, getsourceline) + && !source_finished(fgetline, cookie)) + || (getline_equal(fgetline, cookie, get_func_line) + && !func_has_ended(real_cookie)))) + { + if (cstack.cs_flags[cstack.cs_idx] & CSF_TRY) + emsg(_(e_missing_endtry)); + else if (cstack.cs_flags[cstack.cs_idx] & CSF_WHILE) + emsg(_(e_missing_endwhile)); + else if (cstack.cs_flags[cstack.cs_idx] & CSF_FOR) + emsg(_(e_missing_endfor)); + else + emsg(_(e_missing_endif)); + } + + /* + * Reset "trylevel" in case of a ":finish" or ":return" or a missing + * ":endtry" in a sourced file or executed function. If the try + * conditional is in its finally clause, ignore anything pending. + * If it is in a catch clause, finish the caught exception. + * Also cleanup any "cs_forinfo" structures. + */ + do + { + int idx = cleanup_conditionals(&cstack, 0, TRUE); + + if (idx >= 0) + --idx; // remove try block not in its finally clause + rewind_conditionals(&cstack, idx, CSF_WHILE | CSF_FOR, + &cstack.cs_looplevel); + } + while (cstack.cs_idx >= 0); + trylevel = initial_trylevel; + } + + // If a missing ":endtry", ":endwhile", ":endfor", or ":endif" or a memory + // lack was reported above and the error message is to be converted to an + // exception, do this now after rewinding the cstack. + do_errthrow(&cstack, getline_equal(fgetline, cookie, get_func_line) + ? (char_u *)"endfunction" : (char_u *)NULL); + + if (trylevel == 0) + { + // Just in case did_throw got set but current_exception wasn't. + if (current_exception == NULL) + did_throw = FALSE; + + /* + * When an exception is being thrown out of the outermost try + * conditional, discard the uncaught exception, disable the conversion + * of interrupts or errors to exceptions, and ensure that no more + * commands are executed. + */ + if (did_throw) + handle_did_throw(); + + /* + * On an interrupt or an aborting error not converted to an exception, + * disable the conversion of errors to exceptions. (Interrupts are not + * converted anymore, here.) This enables also the interrupt message + * when force_abort is set and did_emsg unset in case of an interrupt + * from a finally clause after an error. + */ + else if (got_int || (did_emsg && force_abort)) + suppress_errthrow = TRUE; + } + + /* + * The current cstack will be freed when do_cmdline() returns. An uncaught + * exception will have to be rethrown in the previous cstack. If a function + * has just returned or a script file was just finished and the previous + * cstack belongs to the same function or, respectively, script file, it + * will have to be checked for finally clauses to be executed due to the + * ":return" or ":finish". This is done in do_one_cmd(). + */ + if (did_throw) + need_rethrow = TRUE; + if ((getline_equal(fgetline, cookie, getsourceline) + && ex_nesting_level > source_level(real_cookie)) + || (getline_equal(fgetline, cookie, get_func_line) + && ex_nesting_level > func_level(real_cookie) + 1)) + { + if (!did_throw) + check_cstack = TRUE; + } + else + { + // When leaving a function, reduce nesting level. + if (getline_equal(fgetline, cookie, get_func_line)) + --ex_nesting_level; + /* + * Go to debug mode when returning from a function in which we are + * single-stepping. + */ + if ((getline_equal(fgetline, cookie, getsourceline) + || getline_equal(fgetline, cookie, get_func_line)) + && ex_nesting_level + 1 <= debug_break_level) + do_debug(getline_equal(fgetline, cookie, getsourceline) + ? (char_u *)_("End of sourced file") + : (char_u *)_("End of function")); + } + + /* + * Restore the exception environment (done after returning from the + * debugger). + */ + if (flags & DOCMD_EXCRESET) + restore_dbg_stuff(&debug_saved); + + msg_list = saved_msg_list; + + // Cleanup if "cs_emsg_silent_list" remains. + if (cstack.cs_emsg_silent_list != NULL) + { + eslist_T *elem, *temp; + + for (elem = cstack.cs_emsg_silent_list; elem != NULL; elem = temp) + { + temp = elem->next; + vim_free(elem); + } + } + #endif // FEAT_EVAL + + /* + * If there was too much output to fit on the command line, ask the user to + * hit return before redrawing the screen. With the ":global" command we do + * this only once after the command is finished. + */ + if (did_inc) + { + --RedrawingDisabled; + --no_wait_return; + msg_scroll = FALSE; + + /* + * When just finished an ":if"-":else" which was typed, no need to + * wait for hit-return. Also for an error situation. + */ + if (retval == FAIL + #ifdef FEAT_EVAL + || (did_endif && KeyTyped && !did_emsg) + #endif + ) + { + need_wait_return = FALSE; + msg_didany = FALSE; // don't wait when restarting edit + } + else if (need_wait_return) + { + /* + * The msg_start() above clears msg_didout. The wait_return we do + * here should not overwrite the command that may be shown before + * doing that. + */ + msg_didout |= msg_didout_before_start; + wait_return(FALSE); + } + } + + #ifdef FEAT_EVAL + did_endif = FALSE; // in case do_cmdline used recursively + #else + /* + * Reset if_level, in case a sourced script file contains more ":if" than + * ":endif" (could be ":if x | foo | endif"). + */ + if_level = 0; + #endif + + --call_depth; + return retval; + } + + #if defined(FEAT_EVAL) || defined(PROTO) + /* + * Handle when "did_throw" is set after executing commands. + */ + void + handle_did_throw() + { + char *p = NULL; + msglist_T *messages = NULL; + ESTACK_CHECK_DECLARATION + + /* + * If the uncaught exception is a user exception, report it as an + * error. If it is an error exception, display the saved error + * message now. For an interrupt exception, do nothing; the + * interrupt message is given elsewhere. + */ + switch (current_exception->type) + { + case ET_USER: + vim_snprintf((char *)IObuff, IOSIZE, + _(e_exception_not_caught_str), + current_exception->value); + p = (char *)vim_strsave(IObuff); + break; + case ET_ERROR: + messages = current_exception->messages; + current_exception->messages = NULL; + break; + case ET_INTERRUPT: + break; + } + + estack_push(ETYPE_EXCEPT, current_exception->throw_name, + current_exception->throw_lnum); + ESTACK_CHECK_SETUP + current_exception->throw_name = NULL; + + discard_current_exception(); // uses IObuff if 'verbose' + suppress_errthrow = TRUE; + force_abort = TRUE; + + if (messages != NULL) + { + do + { + msglist_T *next = messages->next; + int save_compiling = estack_compiling; + + estack_compiling = messages->msg_compiling; + emsg(messages->msg); + vim_free(messages->msg); + vim_free(messages->sfile); + vim_free(messages); + messages = next; + estack_compiling = save_compiling; + } + while (messages != NULL); + } + else if (p != NULL) + { + emsg(p); + vim_free(p); + } + vim_free(SOURCING_NAME); + ESTACK_CHECK_NOW + estack_pop(); + } + + /* + * Obtain a line when inside a ":while" or ":for" loop. + */ + static char_u * + get_loop_line(int c, void *cookie, int indent, getline_opt_T options) + { + struct loop_cookie *cp = (struct loop_cookie *)cookie; + wcmd_T *wp; + char_u *line; + + if (cp->current_line + 1 >= cp->lines_gap->ga_len) + { + if (cp->repeating) + return NULL; // trying to read past ":endwhile"/":endfor" + + // First time inside the ":while"/":for": get line normally. + if (cp->getline == NULL) + line = getcmdline(c, 0L, indent, 0); + else + line = cp->getline(c, cp->cookie, indent, options); + if (line != NULL && store_loop_line(cp->lines_gap, line) == OK) + ++cp->current_line; + + return line; + } + + KeyTyped = FALSE; + ++cp->current_line; + wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line; + SOURCING_LNUM = wp->lnum; + return vim_strsave(wp->line); + } + + /* + * Store a line in "gap" so that a ":while" loop can execute it again. + */ + static int + store_loop_line(garray_T *gap, char_u *line) + { + if (ga_grow(gap, 1) == FAIL) + return FAIL; + ((wcmd_T *)(gap->ga_data))[gap->ga_len].line = vim_strsave(line); + ((wcmd_T *)(gap->ga_data))[gap->ga_len].lnum = SOURCING_LNUM; + ++gap->ga_len; + return OK; + } + + /* + * Free the lines stored for a ":while" or ":for" loop. + */ + static void + free_cmdlines(garray_T *gap) + { + while (gap->ga_len > 0) + { + vim_free(((wcmd_T *)(gap->ga_data))[gap->ga_len - 1].line); + --gap->ga_len; + } + } + #endif + + /* + * If "fgetline" is get_loop_line(), return TRUE if the getline it uses equals + * "func". * Otherwise return TRUE when "fgetline" equals "func". + */ + int + getline_equal( + char_u *(*fgetline)(int, void *, int, getline_opt_T), + void *cookie UNUSED, // argument for fgetline() + char_u *(*func)(int, void *, int, getline_opt_T)) + { + #ifdef FEAT_EVAL + char_u *(*gp)(int, void *, int, getline_opt_T); + struct loop_cookie *cp; + + // When "fgetline" is "get_loop_line()" use the "cookie" to find the + // function that's originally used to obtain the lines. This may be + // nested several levels. + gp = fgetline; + cp = (struct loop_cookie *)cookie; + while (gp == get_loop_line) + { + gp = cp->getline; + cp = cp->cookie; + } + return gp == func; + #else + return fgetline == func; + #endif + } + + /* + * If "fgetline" is get_loop_line(), return the cookie used by the original + * getline function. Otherwise return "cookie". + */ + void * + getline_cookie( + char_u *(*fgetline)(int, void *, int, getline_opt_T) UNUSED, + void *cookie) // argument for fgetline() + { + #ifdef FEAT_EVAL + char_u *(*gp)(int, void *, int, getline_opt_T); + struct loop_cookie *cp; + + // When "fgetline" is "get_loop_line()" use the "cookie" to find the + // cookie that's originally used to obtain the lines. This may be nested + // several levels. + gp = fgetline; + cp = (struct loop_cookie *)cookie; + while (gp == get_loop_line) + { + gp = cp->getline; + cp = cp->cookie; + } + return cp; + #else + return cookie; + #endif + } + + #if defined(FEAT_EVAL) || defined(PROT) + /* + * Get the next line source line without advancing. + */ + char_u * + getline_peek( + char_u *(*fgetline)(int, void *, int, getline_opt_T) UNUSED, + void *cookie) // argument for fgetline() + { + char_u *(*gp)(int, void *, int, getline_opt_T); + struct loop_cookie *cp; + wcmd_T *wp; + + // When "fgetline" is "get_loop_line()" use the "cookie" to find the + // cookie that's originally used to obtain the lines. This may be nested + // several levels. + gp = fgetline; + cp = (struct loop_cookie *)cookie; + while (gp == get_loop_line) + { + if (cp->current_line + 1 < cp->lines_gap->ga_len) + { + // executing lines a second time, use the stored copy + wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line + 1; + return wp->line; + } + gp = cp->getline; + cp = cp->cookie; + } + if (gp == getsourceline) + return source_nextline(cp); + return NULL; + } + #endif + + + /* + * Helper function to apply an offset for buffer commands, i.e. ":bdelete", + * ":bwipeout", etc. + * Returns the buffer number. + */ + static int + compute_buffer_local_count(int addr_type, int lnum, int offset) + { + buf_T *buf; + buf_T *nextbuf; + int count = offset; + + buf = firstbuf; + while (buf->b_next != NULL && buf->b_fnum < lnum) + buf = buf->b_next; + while (count != 0) + { + count += (offset < 0) ? 1 : -1; + nextbuf = (offset < 0) ? buf->b_prev : buf->b_next; + if (nextbuf == NULL) + break; + buf = nextbuf; + if (addr_type == ADDR_LOADED_BUFFERS) + // skip over unloaded buffers + while (buf->b_ml.ml_mfp == NULL) + { + nextbuf = (offset < 0) ? buf->b_prev : buf->b_next; + if (nextbuf == NULL) + break; + buf = nextbuf; + } + } + // we might have gone too far, last buffer is not loadedd + if (addr_type == ADDR_LOADED_BUFFERS) + while (buf->b_ml.ml_mfp == NULL) + { + nextbuf = (offset >= 0) ? buf->b_prev : buf->b_next; + if (nextbuf == NULL) + break; + buf = nextbuf; + } + return buf->b_fnum; + } + + /* + * Return the window number of "win". + * When "win" is NULL return the number of windows. + */ + static int + current_win_nr(win_T *win) + { + win_T *wp; + int nr = 0; + + FOR_ALL_WINDOWS(wp) + { + ++nr; + if (wp == win) + break; + } + return nr; + } + + static int + current_tab_nr(tabpage_T *tab) + { + tabpage_T *tp; + int nr = 0; + + FOR_ALL_TABPAGES(tp) + { + ++nr; + if (tp == tab) + break; + } + return nr; + } + + static int + comment_start(char_u *p, int starts_with_colon UNUSED) + { + #ifdef FEAT_EVAL + if (in_vim9script()) + return p[0] == '#' && !starts_with_colon; + #endif + return *p == '"'; + } + + # define CURRENT_WIN_NR current_win_nr(curwin) + # define LAST_WIN_NR current_win_nr(NULL) + # define CURRENT_TAB_NR current_tab_nr(curtab) + # define LAST_TAB_NR current_tab_nr(NULL) + + /* + * Execute one Ex command. + * + * If "flags" has DOCMD_VERBOSE, the command will be included in the error + * message. + * + * 1. skip comment lines and leading space + * 2. handle command modifiers + * 3. find the command + * 4. parse range + * 5. Parse the command. + * 6. parse arguments + * 7. switch on command name + * + * Note: "fgetline" can be NULL. + * + * This function may be called recursively! + */ + static char_u * + do_one_cmd( + char_u **cmdlinep, + int flags, + #ifdef FEAT_EVAL + cstack_T *cstack, + #endif + char_u *(*fgetline)(int, void *, int, getline_opt_T), + void *cookie) // argument for fgetline() + { + char_u *p; + linenr_T lnum; + long n; + char *errormsg = NULL; // error message + char_u *after_modifier = NULL; + exarg_T ea; // Ex command arguments + cmdmod_T save_cmdmod; + int save_reg_executing = reg_executing; + int save_pending_end_reg_executing = pending_end_reg_executing; + int ni; // set when Not Implemented + char_u *cmd; + int starts_with_colon = FALSE; + #ifdef FEAT_EVAL + int may_have_range; + int vim9script; + int did_set_expr_line = FALSE; + #endif + int sourcing = flags & DOCMD_VERBOSE; + int did_append_cmd = FALSE; + + CLEAR_FIELD(ea); + ea.line1 = 1; + ea.line2 = 1; + #ifdef FEAT_EVAL + ++ex_nesting_level; + #endif + + // When the last file has not been edited :q has to be typed twice. + if (quitmore + #ifdef FEAT_EVAL + // avoid that a function call in 'statusline' does this + && !getline_equal(fgetline, cookie, get_func_line) + #endif + // avoid that an autocommand, e.g. QuitPre, does this + && !getline_equal(fgetline, cookie, getnextac)) + --quitmore; + + /* + * Reset browse, confirm, etc.. They are restored when returning, for + * recursive calls. + */ + save_cmdmod = cmdmod; + + // "#!anything" is handled like a comment. + if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') + goto doend; + + /* + * 1. Skip comment lines and leading white space and colons. + * 2. Handle command modifiers. + */ + // The "ea" structure holds the arguments that can be used. + ea.cmd = *cmdlinep; + ea.cmdlinep = cmdlinep; + ea.getline = fgetline; + ea.cookie = cookie; + #ifdef FEAT_EVAL + ea.cstack = cstack; + starts_with_colon = *skipwhite(ea.cmd) == ':'; + #endif + if (parse_command_modifiers(&ea, &errormsg, &cmdmod, FALSE) == FAIL) + goto doend; + apply_cmdmod(&cmdmod); + #ifdef FEAT_EVAL + vim9script = in_vim9script(); + #endif + after_modifier = ea.cmd; + + #ifdef FEAT_EVAL + ea.skip = did_emsg || got_int || did_throw || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)); + #else + ea.skip = (if_level > 0); + #endif + + /* + * 3. Skip over the range to find the command. Let "p" point to after it. + * + * We need the command to know what kind of range it uses. + */ + cmd = ea.cmd; + #ifdef FEAT_EVAL + // In Vim9 script a colon is required before the range. This may also be + // after command modifiers. + if (vim9script && (flags & DOCMD_RANGEOK) == 0) + { + may_have_range = FALSE; + for (p = ea.cmd; p >= *cmdlinep; --p) + { + if (*p == ':') + may_have_range = TRUE; + if (p < ea.cmd && !VIM_ISWHITE(*p)) + break; + } + } + else + may_have_range = TRUE; + if (may_have_range) + #endif + ea.cmd = skip_range(ea.cmd, TRUE, NULL); + + #ifdef FEAT_EVAL + if (vim9script && !may_have_range) + { + if (ea.cmd == cmd + 1 && *cmd == '$') + // should be "$VAR = val" + --ea.cmd; + p = find_ex_command(&ea, NULL, lookup_scriptitem, NULL); + if (ea.cmdidx == CMD_SIZE) + { + char_u *ar = skip_range(ea.cmd, TRUE, NULL); + + // If a ':' before the range is missing, give a clearer error + // message. + if (ar > ea.cmd && !ea.skip) + { + semsg(_(e_colon_required_before_range_str), ea.cmd); + goto doend; + } + } + } + else + #endif + p = find_ex_command(&ea, NULL, NULL, NULL); + + #ifdef FEAT_EVAL + # ifdef FEAT_PROFILE + // Count this line for profiling if skip is TRUE. + if (do_profiling == PROF_YES + && (!ea.skip || cstack->cs_idx == 0 || (cstack->cs_idx > 0 + && (cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE)))) + { + int skip = did_emsg || got_int || did_throw; + + if (ea.cmdidx == CMD_catch) + skip = !skip && !(cstack->cs_idx >= 0 + && (cstack->cs_flags[cstack->cs_idx] & CSF_THROWN) + && !(cstack->cs_flags[cstack->cs_idx] & CSF_CAUGHT)); + else if (ea.cmdidx == CMD_else || ea.cmdidx == CMD_elseif) + skip = skip || !(cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] + & (CSF_ACTIVE | CSF_TRUE))); + else if (ea.cmdidx == CMD_finally) + skip = FALSE; + else if (ea.cmdidx != CMD_endif + && ea.cmdidx != CMD_endfor + && ea.cmdidx != CMD_endtry + && ea.cmdidx != CMD_endwhile) + skip = ea.skip; + + if (!skip) + { + if (getline_equal(fgetline, cookie, get_func_line)) + func_line_exec(getline_cookie(fgetline, cookie)); + else if (getline_equal(fgetline, cookie, getsourceline)) + script_line_exec(); + } + } + # endif + + // May go to debug mode. If this happens and the ">quit" debug command is + // used, throw an interrupt exception and skip the next command. + dbg_check_breakpoint(&ea); + if (!ea.skip && got_int) + { + ea.skip = TRUE; + (void)do_intthrow(cstack); + } + #endif + + /* + * 4. parse a range specifier of the form: addr [,addr] [;addr] .. + * + * where 'addr' is: + * + * % (entire file) + * $ [+-NUM] + * 'x [+-NUM] (where x denotes a currently defined mark) + * . [+-NUM] + * [+-NUM].. + * NUM + * + * The ea.cmd pointer is updated to point to the first character following the + * range spec. If an initial address is found, but no second, the upper bound + * is equal to the lower. + */ + + // ea.addr_type for user commands is set by find_ucmd + if (!IS_USER_CMDIDX(ea.cmdidx)) + { + if (ea.cmdidx != CMD_SIZE) + ea.addr_type = cmdnames[(int)ea.cmdidx].cmd_addr_type; + else + ea.addr_type = ADDR_LINES; + + // :wincmd range depends on the argument. + if (ea.cmdidx == CMD_wincmd && p != NULL) + get_wincmd_addr_type(skipwhite(p), &ea); + #ifdef FEAT_QUICKFIX + // :.cc in quickfix window uses line number + if ((ea.cmdidx == CMD_cc || ea.cmdidx == CMD_ll) && bt_quickfix(curbuf)) + ea.addr_type = ADDR_OTHER; + #endif + } + + ea.cmd = cmd; + #ifdef FEAT_EVAL + if (!may_have_range) + ea.line1 = ea.line2 = default_address(&ea); + else + #endif + if (parse_cmd_address(&ea, &errormsg, FALSE) == FAIL) + goto doend; + + /* + * 5. Parse the command. + */ + + /* + * Skip ':' and any white space + */ + ea.cmd = skipwhite(ea.cmd); + while (*ea.cmd == ':') + ea.cmd = skipwhite(ea.cmd + 1); + + /* + * If we got a line, but no command, then go to the line. + * If we find a '|' or '\n' we set ea.nextcmd. + */ + if (*ea.cmd == NUL || comment_start(ea.cmd, starts_with_colon) + || (ea.nextcmd = check_nextcmd(ea.cmd)) != NULL) + { + /* + * strange vi behaviour: + * ":3" jumps to line 3 + * ":3|..." prints line 3 (not in Vim9 script) + * ":|" prints current line (not in Vim9 script) + */ + if (ea.skip) // skip this if inside :if + goto doend; + errormsg = ex_range_without_command(&ea); + goto doend; + } + + // If this looks like an undefined user command and there are CmdUndefined + // autocommands defined, trigger the matching autocommands. + if (p != NULL && ea.cmdidx == CMD_SIZE && !ea.skip + && ASCII_ISUPPER(*ea.cmd) + && has_cmdundefined()) + { + int ret; + + p = ea.cmd; + while (ASCII_ISALNUM(*p)) + ++p; + p = vim_strnsave(ea.cmd, p - ea.cmd); + ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, TRUE, NULL); + vim_free(p); + // If the autocommands did something and didn't cause an error, try + // finding the command again. + p = (ret + #ifdef FEAT_EVAL + && !aborting() + #endif + ) ? find_ex_command(&ea, NULL, NULL, NULL) : ea.cmd; + } + + if (p == NULL) + { + if (!ea.skip) + errormsg = _(e_ambiguous_use_of_user_defined_command); + goto doend; + } + // Check for wrong commands. + if (*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78 + && !IS_USER_CMDIDX(ea.cmdidx)) + { + errormsg = uc_fun_cmd(); + goto doend; + } + + if (ea.cmdidx == CMD_SIZE) + { + if (!ea.skip) + { + STRCPY(IObuff, _(e_not_an_editor_command)); + if (!sourcing) + { + // If the modifier was parsed OK the error must be in the + // following command + if (after_modifier != NULL) + append_command(after_modifier); + else + append_command(*cmdlinep); + did_append_cmd = TRUE; + } + errormsg = (char *)IObuff; + did_emsg_syntax = TRUE; + } + goto doend; + } + + ni = (!IS_USER_CMDIDX(ea.cmdidx) + && (cmdnames[ea.cmdidx].cmd_func == ex_ni + #ifdef HAVE_EX_SCRIPT_NI + || cmdnames[ea.cmdidx].cmd_func == ex_script_ni + #endif + )); + + #ifndef FEAT_EVAL + /* + * When the expression evaluation is disabled, recognize the ":if" and + * ":endif" commands and ignore everything in between it. + */ + if (ea.cmdidx == CMD_if) + ++if_level; + if (if_level) + { + if (ea.cmdidx == CMD_endif) + --if_level; + goto doend; + } + + #endif + + // forced commands + if (*p == '!' && ea.cmdidx != CMD_substitute + && ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic) + { + ++p; + ea.forceit = TRUE; + } + else + ea.forceit = FALSE; + + /* + * 6. Parse arguments. Then check for errors. + */ + if (!IS_USER_CMDIDX(ea.cmdidx)) + ea.argt = (long)cmdnames[(int)ea.cmdidx].cmd_argt; + + if (!ea.skip) + { + #ifdef HAVE_SANDBOX + if (sandbox != 0 && !(ea.argt & EX_SBOXOK)) + { + // Command not allowed in sandbox. + errormsg = _(e_not_allowed_in_sandbox); + goto doend; + } + #endif + if (restricted != 0 && (ea.argt & EX_RESTRICT)) + { + errormsg = _(e_command_not_allowed_in_rvim); + goto doend; + } + if (!curbuf->b_p_ma && (ea.argt & EX_MODIFY)) + { + // Command not allowed in non-'modifiable' buffer + errormsg = _(e_cannot_make_changes_modifiable_is_off); + goto doend; + } + + if (!IS_USER_CMDIDX(ea.cmdidx)) + { + #ifdef FEAT_CMDWIN + if (cmdwin_type != 0 && !(ea.argt & EX_CMDWIN)) + { + // Command not allowed in the command line window + errormsg = _(e_invalid_in_cmdline_window); + goto doend; + } + #endif + if (text_locked() && !(ea.argt & EX_LOCK_OK)) + { + // Command not allowed when text is locked + errormsg = _(get_text_locked_msg()); + goto doend; + } + } + + // Disallow editing another buffer when "curbuf_lock" is set. + // Do allow ":checktime" (it is postponed). + // Do allow ":edit" (check for an argument later). + // Do allow ":file" with no arguments (check for an argument later). + if (!(ea.argt & (EX_CMDWIN | EX_LOCK_OK)) + && ea.cmdidx != CMD_checktime + && ea.cmdidx != CMD_edit + && ea.cmdidx != CMD_file + && !IS_USER_CMDIDX(ea.cmdidx) + && curbuf_locked()) + goto doend; + + if (!ni && !(ea.argt & EX_RANGE) && ea.addr_count > 0) + { + errormsg = _(e_no_range_allowed); + goto doend; + } + } + + if (!ni && !(ea.argt & EX_BANG) && ea.forceit) + { + errormsg = _(e_no_bang_allowed); + goto doend; + } + + /* + * Don't complain about the range if it is not used + * (could happen if line_count is accidentally set to 0). + */ + if (!ea.skip && !ni && (ea.argt & EX_RANGE)) + { + /* + * If the range is backwards, ask for confirmation and, if given, swap + * ea.line1 & ea.line2 so it's forwards again. + * When global command is busy, don't ask, will fail below. + */ + if (!global_busy && ea.line1 > ea.line2) + { + if (msg_silent == 0) + { + if (sourcing || exmode_active) + { + errormsg = _(e_backwards_range_given); + goto doend; + } + if (ask_yesno((char_u *) + _("Backwards range given, OK to swap"), FALSE) != 'y') + goto doend; + } + lnum = ea.line1; + ea.line1 = ea.line2; + ea.line2 = lnum; + } + if ((errormsg = invalid_range(&ea)) != NULL) + goto doend; + } + + if ((ea.addr_type == ADDR_OTHER) && ea.addr_count == 0) + // default is 1, not cursor + ea.line2 = 1; + + correct_range(&ea); + + #ifdef FEAT_FOLDING + if (((ea.argt & EX_WHOLEFOLD) || ea.addr_count >= 2) && !global_busy + && ea.addr_type == ADDR_LINES) + { + // Put the first line at the start of a closed fold, put the last line + // at the end of a closed fold. + (void)hasFolding(ea.line1, &ea.line1, NULL); + (void)hasFolding(ea.line2, NULL, &ea.line2); + } + #endif + + #ifdef FEAT_QUICKFIX + /* + * For the ":make" and ":grep" commands we insert the 'makeprg'/'grepprg' + * option here, so things like % get expanded. + */ + p = replace_makeprg(&ea, p, cmdlinep); + if (p == NULL) + goto doend; + #endif + + /* + * Skip to start of argument. + * Don't do this for the ":!" command, because ":!! -l" needs the space. + */ + if (ea.cmdidx == CMD_bang) + ea.arg = p; + else + ea.arg = skipwhite(p); + + // ":file" cannot be run with an argument when "curbuf_lock" is set + if (ea.cmdidx == CMD_file && *ea.arg != NUL && curbuf_locked()) + goto doend; + + /* + * Check for "++opt=val" argument. + * Must be first, allow ":w ++enc=utf8 !cmd" + */ + if (ea.argt & EX_ARGOPT) + while (ea.arg[0] == '+' && ea.arg[1] == '+') + if (getargopt(&ea) == FAIL && !ni) + { + errormsg = _(e_invalid_argument); + goto doend; + } + + if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) + { + if (*ea.arg == '>') // append + { + if (*++ea.arg != '>') // typed wrong + { + errormsg = _(e_use_w_or_w_gt_gt); + goto doend; + } + ea.arg = skipwhite(ea.arg + 1); + ea.append = TRUE; + } + else if (*ea.arg == '!' && ea.cmdidx == CMD_write) // :w !filter + { + ++ea.arg; + ea.usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_read) + { + if (ea.forceit) + { + ea.usefilter = TRUE; // :r! filter if ea.forceit + ea.forceit = FALSE; + } + else if (*ea.arg == '!') // :r !filter + { + ++ea.arg; + ea.usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) + { + ea.amount = 1; + while (*ea.arg == *ea.cmd) // count number of '>' or '<' + { + ++ea.arg; + ++ea.amount; + } + ea.arg = skipwhite(ea.arg); + } + + /* + * Check for "+command" argument, before checking for next command. + * Don't do this for ":read !cmd" and ":write !cmd". + */ + if ((ea.argt & EX_CMDARG) && !ea.usefilter) + ea.do_ecmd_cmd = getargcmd(&ea.arg); + + /* + * For commands that do not use '|' inside their argument: Check for '|' to + * separate commands and '"' or '#' to start comments. + * + * Otherwise: Check for to end a shell command. + * Also do this for ":read !cmd", ":write !cmd" and ":global". + * Also do this inside a { - } block after :command and :autocmd. + * Any others? + */ + if ((ea.argt & EX_TRLBAR) && !ea.usefilter) + { + separate_nextcmd(&ea, FALSE); + } + else if (ea.cmdidx == CMD_bang + || ea.cmdidx == CMD_terminal + || ea.cmdidx == CMD_global + || ea.cmdidx == CMD_vglobal + || ea.usefilter + #ifdef FEAT_EVAL + || inside_block(&ea) + #endif + ) + { + for (p = ea.arg; *p; ++p) + { + // Remove one backslash before a newline, so that it's possible to + // pass a newline to the shell and also a newline that is preceded + // with a backslash. This makes it impossible to end a shell + // command in a backslash, but that doesn't appear useful. + // Halving the number of backslashes is incompatible with previous + // versions. + if (*p == '\\' && p[1] == '\n') + STRMOVE(p, p + 1); + else if (*p == '\n' && !(ea.argt & EX_EXPR_ARG)) + { + ea.nextcmd = p + 1; + *p = NUL; + break; + } + } + } + + if ((ea.argt & EX_DFLALL) && ea.addr_count == 0) + address_default_all(&ea); + + // accept numbered register only when no count allowed (:put) + if ( (ea.argt & EX_REGSTR) + && *ea.arg != NUL + // Do not allow register = for user commands + && (!IS_USER_CMDIDX(ea.cmdidx) || *ea.arg != '=') + && !((ea.argt & EX_COUNT) && VIM_ISDIGIT(*ea.arg))) + { + #ifndef FEAT_CLIPBOARD + // check these explicitly for a more specific error message + if (*ea.arg == '*' || *ea.arg == '+') + { + errormsg = _(e_invalid_register_name); + goto doend; + } + #endif + if (valid_yank_reg(*ea.arg, (ea.cmdidx != CMD_put + && !IS_USER_CMDIDX(ea.cmdidx)))) + { + ea.regname = *ea.arg++; + #ifdef FEAT_EVAL + // for '=' register: accept the rest of the line as an expression + if (ea.arg[-1] == '=' && ea.arg[0] != NUL) + { + if (!ea.skip) + { + set_expr_line(vim_strsave(ea.arg), &ea); + did_set_expr_line = TRUE; + } + ea.arg += STRLEN(ea.arg); + } + #endif + ea.arg = skipwhite(ea.arg); + } + } + + /* + * Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a + * count, it's a buffer name. + */ + if ((ea.argt & EX_COUNT) && VIM_ISDIGIT(*ea.arg) + && (!(ea.argt & EX_BUFNAME) || *(p = skipdigits(ea.arg + 1)) == NUL + || VIM_ISWHITE(*p))) + { + n = getdigits_quoted(&ea.arg); + ea.arg = skipwhite(ea.arg); + if (n <= 0 && !ni && (ea.argt & EX_ZEROR) == 0) + { + errormsg = _(e_positive_count_required); + goto doend; + } + if (ea.addr_type != ADDR_LINES) // e.g. :buffer 2, :sleep 3 + { + ea.line2 = n; + if (ea.addr_count == 0) + ea.addr_count = 1; + } + else + { + ea.line1 = ea.line2; + if (ea.line2 >= LONG_MAX - (n - 1)) + ea.line2 = LONG_MAX; // avoid overflow + else + ea.line2 += n - 1; + ++ea.addr_count; + /* + * Be vi compatible: no error message for out of range. + */ + if (ea.line2 > curbuf->b_ml.ml_line_count) + ea.line2 = curbuf->b_ml.ml_line_count; + } + } + + /* + * Check for flags: 'l', 'p' and '#'. + */ + if (ea.argt & EX_FLAGS) + get_flags(&ea); + if (!ni && !(ea.argt & EX_EXTRA) && *ea.arg != NUL + && *ea.arg != '"' && (*ea.arg != '|' || (ea.argt & EX_TRLBAR) == 0)) + { + // no arguments allowed but there is something + errormsg = ex_errmsg(e_trailing_characters_str, ea.arg); + goto doend; + } + + if (!ni && (ea.argt & EX_NEEDARG) && *ea.arg == NUL) + { + errormsg = _(e_argument_required); + goto doend; + } + + #ifdef FEAT_EVAL + /* + * Skip the command when it's not going to be executed. + * The commands like :if, :endif, etc. always need to be executed. + * Also make an exception for commands that handle a trailing command + * themselves. + */ + if (ea.skip) + { + switch (ea.cmdidx) + { + // commands that need evaluation + case CMD_while: + case CMD_endwhile: + case CMD_for: + case CMD_endfor: + case CMD_if: + case CMD_elseif: + case CMD_else: + case CMD_endif: + case CMD_try: + case CMD_catch: + case CMD_finally: + case CMD_endtry: + case CMD_function: + case CMD_def: + break; + + // Commands that handle '|' themselves. Check: A command should + // either have the EX_TRLBAR flag, appear in this list or appear in + // the list at ":help :bar". + case CMD_aboveleft: + case CMD_and: + case CMD_belowright: + case CMD_botright: + case CMD_browse: + case CMD_call: + case CMD_confirm: + case CMD_const: + case CMD_delfunction: + case CMD_djump: + case CMD_dlist: + case CMD_dsearch: + case CMD_dsplit: + case CMD_echo: + case CMD_echoerr: + case CMD_echomsg: + case CMD_echon: + case CMD_eval: + case CMD_execute: + case CMD_filter: + case CMD_final: + case CMD_help: + case CMD_hide: + case CMD_ijump: + case CMD_ilist: + case CMD_isearch: + case CMD_isplit: + case CMD_keepalt: + case CMD_keepjumps: + case CMD_keepmarks: + case CMD_keeppatterns: + case CMD_leftabove: + case CMD_let: + case CMD_lockmarks: + case CMD_lockvar: + case CMD_lua: + case CMD_match: + case CMD_mzscheme: + case CMD_noautocmd: + case CMD_noswapfile: + case CMD_perl: + case CMD_psearch: + case CMD_py3: + case CMD_python3: + case CMD_python: + case CMD_return: + case CMD_rightbelow: + case CMD_ruby: + case CMD_silent: + case CMD_smagic: + case CMD_snomagic: + case CMD_substitute: + case CMD_syntax: + case CMD_tab: + case CMD_tcl: + case CMD_throw: + case CMD_tilde: + case CMD_topleft: + case CMD_unlet: + case CMD_unlockvar: + case CMD_var: + case CMD_verbose: + case CMD_vertical: + case CMD_wincmd: + break; + + default: goto doend; + } + } + #endif + + if (ea.argt & EX_XFILE) + { + if (expand_filename(&ea, cmdlinep, &errormsg) == FAIL) + goto doend; + } + + /* + * Accept buffer name. Cannot be used at the same time with a buffer + * number. Don't do this for a user command. + */ + if ((ea.argt & EX_BUFNAME) && *ea.arg != NUL && ea.addr_count == 0 + && !IS_USER_CMDIDX(ea.cmdidx)) + { + /* + * :bdelete, :bwipeout and :bunload take several arguments, separated + * by spaces: find next space (skipping over escaped characters). + * The others take one argument: ignore trailing spaces. + */ + if (ea.cmdidx == CMD_bdelete || ea.cmdidx == CMD_bwipeout + || ea.cmdidx == CMD_bunload) + p = skiptowhite_esc(ea.arg); + else + { + p = ea.arg + STRLEN(ea.arg); + while (p > ea.arg && VIM_ISWHITE(p[-1])) + --p; + } + ea.line2 = buflist_findpat(ea.arg, p, (ea.argt & EX_BUFUNL) != 0, + FALSE, FALSE); + if (ea.line2 < 0) // failed + goto doend; + ea.addr_count = 1; + ea.arg = skipwhite(p); + } + + // The :try command saves the emsg_silent flag, reset it here when + // ":silent! try" was used, it should only apply to :try itself. + if (ea.cmdidx == CMD_try && cmdmod.cmod_did_esilent > 0) + { + emsg_silent -= cmdmod.cmod_did_esilent; + if (emsg_silent < 0) + emsg_silent = 0; + cmdmod.cmod_did_esilent = 0; + } + + /* + * 7. Execute the command. + */ + + if (IS_USER_CMDIDX(ea.cmdidx)) + { + /* + * Execute a user-defined command. + */ + do_ucmd(&ea); + } + else + { + /* + * Call the function to execute the builtin command. + */ + ea.errmsg = NULL; + (cmdnames[ea.cmdidx].cmd_func)(&ea); + if (ea.errmsg != NULL) + errormsg = ea.errmsg; + } + + #ifdef FEAT_EVAL + // Set flag that any command was executed, used by ex_vim9script(). + // Not if this was a command that wasn't executed or :endif. + if (sourcing_a_script(&ea) + && current_sctx.sc_sid > 0 + && ea.cmdidx != CMD_endif + && (cstack->cs_idx < 0 + || (cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))) + SCRIPT_ITEM(current_sctx.sc_sid)->sn_state = SN_STATE_HAD_COMMAND; + + /* + * If the command just executed called do_cmdline(), any throw or ":return" + * or ":finish" encountered there must also check the cstack of the still + * active do_cmdline() that called this do_one_cmd(). Rethrow an uncaught + * exception, or reanimate a returned function or finished script file and + * return or finish it again. + */ + if (need_rethrow) + do_throw(cstack); + else if (check_cstack) + { + if (source_finished(fgetline, cookie)) + do_finish(&ea, TRUE); + else if (getline_equal(fgetline, cookie, get_func_line) + && current_func_returned()) + do_return(&ea, TRUE, FALSE, NULL); + } + need_rethrow = check_cstack = FALSE; + #endif + + doend: + if (curwin->w_cursor.lnum == 0) // can happen with zero line number + { + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + } + + if (errormsg != NULL && *errormsg != NUL && !did_emsg) + { + if ((sourcing || !KeyTyped) && !did_append_cmd) + { + if (errormsg != (char *)IObuff) + { + STRCPY(IObuff, errormsg); + errormsg = (char *)IObuff; + } + append_command(*cmdlinep); + } + emsg(errormsg); + } + #ifdef FEAT_EVAL + do_errthrow(cstack, + (ea.cmdidx != CMD_SIZE && !IS_USER_CMDIDX(ea.cmdidx)) + ? cmdnames[(int)ea.cmdidx].cmd_name : (char_u *)NULL); + + if (did_set_expr_line) + set_expr_line(NULL, NULL); + #endif + + undo_cmdmod(&cmdmod); + cmdmod = save_cmdmod; + reg_executing = save_reg_executing; + pending_end_reg_executing = save_pending_end_reg_executing; + + if (ea.nextcmd && *ea.nextcmd == NUL) // not really a next command + ea.nextcmd = NULL; + + #ifdef FEAT_EVAL + --ex_nesting_level; + vim_free(ea.cmdline_tofree); + #endif + + return ea.nextcmd; + } + + static char ex_error_buf[MSG_BUF_LEN]; + + /* + * Return an error message with argument included. + * Uses a static buffer, only the last error will be kept. + * "msg" will be translated, caller should use N_(). + */ + char * + ex_errmsg(char *msg, char_u *arg) + { + vim_snprintf(ex_error_buf, MSG_BUF_LEN, _(msg), arg); + return ex_error_buf; + } + + /* + * Handle a range without a command. + * Returns an error message on failure. + */ + char * + ex_range_without_command(exarg_T *eap) + { + char *errormsg = NULL; + + if ((*eap->cmd == '|' || (exmode_active && eap->line1 != eap->line2)) + #ifdef FEAT_EVAL + && !in_vim9script() + #endif + ) + { + eap->cmdidx = CMD_print; + eap->argt = EX_RANGE+EX_COUNT+EX_TRLBAR; + if ((errormsg = invalid_range(eap)) == NULL) + { + correct_range(eap); + ex_print(eap); + } + } + else if (eap->addr_count != 0) + { + if (eap->line2 > curbuf->b_ml.ml_line_count) + { + // With '-' in 'cpoptions' a line number past the file is an + // error, otherwise put it at the end of the file. + if (vim_strchr(p_cpo, CPO_MINUS) != NULL) + eap->line2 = -1; + else + eap->line2 = curbuf->b_ml.ml_line_count; + } + + if (eap->line2 < 0) + errormsg = _(e_invalid_range); + else + { + if (eap->line2 == 0) + curwin->w_cursor.lnum = 1; + else + curwin->w_cursor.lnum = eap->line2; + beginline(BL_SOL | BL_FIX); + } + } + return errormsg; + } + + /* + * Check for an Ex command with optional tail. + * If there is a match advance "pp" to the argument and return TRUE. + * If "noparen" is TRUE do not recognize the command followed by "(" or ".". + */ + static int + checkforcmd_opt( + char_u **pp, // start of command + char *cmd, // name of command + int len, // required length + int noparen) + { + int i; + + for (i = 0; cmd[i] != NUL; ++i) + if (((char_u *)cmd)[i] != (*pp)[i]) + break; + if (i >= len && !isalpha((*pp)[i]) && (*pp)[i] != '_' + && (!noparen || ((*pp)[i] != '(' && (*pp)[i] != '.'))) + { + *pp = skipwhite(*pp + i); + return TRUE; + } + return FALSE; + } + + /* + * Check for an Ex command with optional tail. + * If there is a match advance "pp" to the argument and return TRUE. + */ + int + checkforcmd( + char_u **pp, // start of command + char *cmd, // name of command + int len) // required length + { + return checkforcmd_opt(pp, cmd, len, FALSE); + } + + /* + * Check for an Ex command with optional tail, not followed by "(" or ".". + * If there is a match advance "pp" to the argument and return TRUE. + */ + int + checkforcmd_noparen( + char_u **pp, // start of command + char *cmd, // name of command + int len) // required length + { + return checkforcmd_opt(pp, cmd, len, TRUE); + } + + /* + * Parse and skip over command modifiers: + * - update eap->cmd + * - store flags in "cmod". + * - Set ex_pressedreturn for an empty command line. + * When "skip_only" is TRUE the global variables are not changed, except for + * "cmdmod". + * When "skip_only" is FALSE then undo_cmdmod() must be called later to free + * any cmod_filter_regmatch.regprog. + * Call apply_cmdmod() to get the side effects of the modifiers: + * - Increment "sandbox" for ":sandbox" + * - set p_verbose for ":verbose" + * - set msg_silent for ":silent" + * - set 'eventignore' to "all" for ":noautocmd" + * Return FAIL when the command is not to be executed. + * May set "errormsg" to an error message. + */ + int + parse_command_modifiers( + exarg_T *eap, + char **errormsg, + cmdmod_T *cmod, + int skip_only) + { + char_u *orig_cmd = eap->cmd; + char_u *cmd_start = NULL; + int use_plus_cmd = FALSE; + int starts_with_colon = FALSE; + int vim9script = in_vim9script(); + int has_visual_range = FALSE; + + CLEAR_POINTER(cmod); + cmod->cmod_flags = sticky_cmdmod_flags; + + if (STRNCMP(eap->cmd, "'<,'>", 5) == 0) + { + // The automatically inserted Visual area range is skipped, so that + // typing ":cmdmod cmd" in Visual mode works without having to move the + // range to after the modififiers. The command will be + // "'<,'>cmdmod cmd", parse "cmdmod cmd" and then put back "'<,'>" + // before "cmd" below. + eap->cmd += 5; + cmd_start = eap->cmd; + has_visual_range = TRUE; + } + + // Repeat until no more command modifiers are found. + for (;;) + { + char_u *p; + + while (*eap->cmd == ' ' || *eap->cmd == '\t' || *eap->cmd == ':') + { + if (*eap->cmd == ':') + starts_with_colon = TRUE; + ++eap->cmd; + } + + // in ex mode, an empty command (after modifiers) works like :+ + if (*eap->cmd == NUL && exmode_active + && (getline_equal(eap->getline, eap->cookie, getexmodeline) + || getline_equal(eap->getline, eap->cookie, getexline)) + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + { + use_plus_cmd = TRUE; + if (!skip_only) + ex_pressedreturn = TRUE; + break; // no modifiers following + } + + // ignore comment and empty lines + if (comment_start(eap->cmd, starts_with_colon)) + { + // a comment ends at a NL + if (eap->nextcmd == NULL) + { + eap->nextcmd = vim_strchr(eap->cmd, '\n'); + if (eap->nextcmd != NULL) + ++eap->nextcmd; + } + if (vim9script) + { + if (has_cmdmod(cmod, FALSE)) + *errormsg = _(e_command_modifier_without_command); + #ifdef FEAT_EVAL + if (eap->cmd[0] == '#' && eap->cmd[1] == '{' + && eap->cmd[2] != '{') + *errormsg = _(e_cannot_use_hash_curly_to_start_comment); + #endif + } + return FAIL; + } + if (*eap->cmd == NUL) + { + if (!skip_only) + { + ex_pressedreturn = TRUE; + if (vim9script && has_cmdmod(cmod, FALSE)) + *errormsg = _(e_command_modifier_without_command); + } + return FAIL; + } + + p = skip_range(eap->cmd, TRUE, NULL); + + // In Vim9 script a variable can shadow a command modifier: + // verbose = 123 + // verbose += 123 + // silent! verbose = func() + // verbose.member = 2 + // verbose[expr] = 2 + // But not: + // verbose [a, b] = list + if (vim9script) + { + char_u *s, *n; + + for (s = eap->cmd; ASCII_ISALPHA(*s); ++s) + ; + n = skipwhite(s); + if (*n == '.' || *n == '=' || (*n != NUL && n[1] == '=') + || *s == '[') + break; + } + + switch (*p) + { + // When adding an entry, also modify cmd_exists(). + case 'a': if (!checkforcmd_noparen(&eap->cmd, "aboveleft", 3)) + break; + cmod->cmod_split |= WSP_ABOVE; + continue; + + case 'b': if (checkforcmd_noparen(&eap->cmd, "belowright", 3)) + { + cmod->cmod_split |= WSP_BELOW; + continue; + } + if (checkforcmd_opt(&eap->cmd, "browse", 3, TRUE)) + { + #ifdef FEAT_BROWSE_CMD + cmod->cmod_flags |= CMOD_BROWSE; + #endif + continue; + } + if (!checkforcmd_noparen(&eap->cmd, "botright", 2)) + break; + cmod->cmod_split |= WSP_BOT; + continue; + + case 'c': if (!checkforcmd_opt(&eap->cmd, "confirm", 4, TRUE)) + break; + #if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + cmod->cmod_flags |= CMOD_CONFIRM; + #endif + continue; + + case 'k': if (checkforcmd_noparen(&eap->cmd, "keepmarks", 3)) + { + cmod->cmod_flags |= CMOD_KEEPMARKS; + continue; + } + if (checkforcmd_noparen(&eap->cmd, "keepalt", 5)) + { + cmod->cmod_flags |= CMOD_KEEPALT; + continue; + } + if (checkforcmd_noparen(&eap->cmd, "keeppatterns", 5)) + { + cmod->cmod_flags |= CMOD_KEEPPATTERNS; + continue; + } + if (!checkforcmd_noparen(&eap->cmd, "keepjumps", 5)) + break; + cmod->cmod_flags |= CMOD_KEEPJUMPS; + continue; + + case 'f': // only accept ":filter {pat} cmd" + { + char_u *reg_pat; + char_u *nulp = NULL; + int c = 0; + + if (!checkforcmd_noparen(&p, "filter", 4) + || *p == NUL + || (ends_excmd(*p) + #ifdef FEAT_EVAL + // in ":filter #pat# cmd" # does not + // start a comment + && (!vim9script || VIM_ISWHITE(p[1])) + #endif + )) + break; + if (*p == '!') + { + cmod->cmod_filter_force = TRUE; + p = skipwhite(p + 1); + if (*p == NUL || ends_excmd(*p)) + break; + } + #ifdef FEAT_EVAL + // Avoid that "filter(arg)" is recognized. + if (vim9script && !VIM_ISWHITE(p[-1])) + break; + #endif + if (skip_only) + p = skip_vimgrep_pat(p, NULL, NULL); + else + // NOTE: This puts a NUL after the pattern. + p = skip_vimgrep_pat_ext(p, ®_pat, NULL, + &nulp, &c); + if (p == NULL || *p == NUL) + break; + if (!skip_only) + { + cmod->cmod_filter_regmatch.regprog = + vim_regcomp(reg_pat, RE_MAGIC); + if (cmod->cmod_filter_regmatch.regprog == NULL) + break; + // restore the character overwritten by NUL + if (nulp != NULL) + *nulp = c; + } + eap->cmd = p; + continue; + } + + // ":hide" and ":hide | cmd" are not modifiers + case 'h': if (p != eap->cmd || !checkforcmd_noparen(&p, "hide", 3) + || *p == NUL || ends_excmd(*p)) + break; + eap->cmd = p; + cmod->cmod_flags |= CMOD_HIDE; + continue; + + case 'l': if (checkforcmd_noparen(&eap->cmd, "lockmarks", 3)) + { + cmod->cmod_flags |= CMOD_LOCKMARKS; + continue; + } + if (checkforcmd_noparen(&eap->cmd, "legacy", 3)) + { + if (ends_excmd2(p, eap->cmd)) + { + *errormsg = + _(e_legacy_must_be_followed_by_command); + return FAIL; + } + cmod->cmod_flags |= CMOD_LEGACY; + continue; + } + + if (!checkforcmd_noparen(&eap->cmd, "leftabove", 5)) + break; + cmod->cmod_split |= WSP_ABOVE; + continue; + + case 'n': if (checkforcmd_noparen(&eap->cmd, "noautocmd", 3)) + { + cmod->cmod_flags |= CMOD_NOAUTOCMD; + continue; + } + if (!checkforcmd_noparen(&eap->cmd, "noswapfile", 3)) + break; + cmod->cmod_flags |= CMOD_NOSWAPFILE; + continue; + + case 'r': if (!checkforcmd_noparen(&eap->cmd, "rightbelow", 6)) + break; + cmod->cmod_split |= WSP_BELOW; + continue; + + case 's': if (checkforcmd_noparen(&eap->cmd, "sandbox", 3)) + { + cmod->cmod_flags |= CMOD_SANDBOX; + continue; + } + if (!checkforcmd_noparen(&eap->cmd, "silent", 3)) + break; + cmod->cmod_flags |= CMOD_SILENT; + if (*eap->cmd == '!' && !VIM_ISWHITE(eap->cmd[-1])) + { + // ":silent!", but not "silent !cmd" + eap->cmd = skipwhite(eap->cmd + 1); + cmod->cmod_flags |= CMOD_ERRSILENT; + } + continue; + + case 't': if (checkforcmd_noparen(&p, "tab", 3)) + { + if (!skip_only) + { + long tabnr = get_address(eap, &eap->cmd, + ADDR_TABS, eap->skip, + skip_only, FALSE, 1); + if (tabnr == MAXLNUM) + cmod->cmod_tab = tabpage_index(curtab) + 1; + else + { + if (tabnr < 0 || tabnr > LAST_TAB_NR) + { + *errormsg = _(e_invalid_range); + return FAIL; + } + cmod->cmod_tab = tabnr + 1; + } + } + eap->cmd = p; + continue; + } + if (!checkforcmd_noparen(&eap->cmd, "topleft", 2)) + break; + cmod->cmod_split |= WSP_TOP; + continue; + + case 'u': if (!checkforcmd_noparen(&eap->cmd, "unsilent", 3)) + break; + cmod->cmod_flags |= CMOD_UNSILENT; + continue; + + case 'v': if (checkforcmd_noparen(&eap->cmd, "vertical", 4)) + { + cmod->cmod_split |= WSP_VERT; + continue; + } + if (checkforcmd_noparen(&eap->cmd, "vim9cmd", 4)) + { + if (ends_excmd2(p, eap->cmd)) + { + *errormsg = + _(e_vim9cmd_must_be_followed_by_command); + return FAIL; + } + cmod->cmod_flags |= CMOD_VIM9CMD; + continue; + } + if (!checkforcmd_noparen(&p, "verbose", 4)) + break; + if (vim_isdigit(*eap->cmd)) + { + // zero means not set, one is verbose == 0, etc. + cmod->cmod_verbose = atoi((char *)eap->cmd) + 1; + } + else + cmod->cmod_verbose = 2; // default: verbose == 1 + eap->cmd = p; + continue; + } + break; + } + + if (has_visual_range) + { + if (eap->cmd > cmd_start) + { + // Move the '<,'> range to after the modifiers and insert a colon. + // Since the modifiers have been parsed put the colon on top of the + // space: "'<,'>mod cmd" -> "mod:'<,'>cmd + // Put eap->cmd after the colon. + if (use_plus_cmd) + { + size_t len = STRLEN(cmd_start); + + // Special case: empty command uses "+": + // "'<,'>mods" -> "mods *+ + // Use "*" instead of "'<,'>" to avoid the command getting + // longer, in case it was allocated. + mch_memmove(orig_cmd, cmd_start, len); + STRCPY(orig_cmd + len, " *+"); + } + else + { + mch_memmove(cmd_start - 5, cmd_start, eap->cmd - cmd_start); + eap->cmd -= 5; + mch_memmove(eap->cmd - 1, ":'<,'>", 6); + } + } + else + // No modifiers, move the pointer back. + // Special case: change empty command to "+". + if (use_plus_cmd) + eap->cmd = (char_u *)"'<,'>+"; + else + eap->cmd = orig_cmd; + } + else if (use_plus_cmd) + eap->cmd = (char_u *)"+"; + + return OK; + } + + /* + * Return TRUE if "cmod" has anything set. + */ + int + has_cmdmod(cmdmod_T *cmod, int ignore_silent) + { + return (cmod->cmod_flags != 0 && (!ignore_silent + || (cmod->cmod_flags + & ~(CMOD_SILENT | CMOD_ERRSILENT | CMOD_UNSILENT)) != 0)) + || cmod->cmod_split != 0 + || cmod->cmod_verbose > 0 + || cmod->cmod_tab != 0 + || cmod->cmod_filter_regmatch.regprog != NULL; + } + + #if defined(FEAT_EVAL) || defined(PROTO) + /* + * If Vim9 script and "cmdmod" has anything set give an error and return TRUE. + */ + int + cmdmod_error(int ignore_silent) + { + if (in_vim9script() && has_cmdmod(&cmdmod, ignore_silent)) + { + emsg(_(e_misplaced_command_modifier)); + return TRUE; + } + return FALSE; + } + #endif + + /* + * Apply the command modifiers. Saves current state in "cmdmod", call + * undo_cmdmod() later. + */ + void + apply_cmdmod(cmdmod_T *cmod) + { + #ifdef HAVE_SANDBOX + if ((cmod->cmod_flags & CMOD_SANDBOX) && !cmod->cmod_did_sandbox) + { + ++sandbox; + cmod->cmod_did_sandbox = TRUE; + } + #endif + if (cmod->cmod_verbose > 0) + { + if (cmod->cmod_verbose_save == 0) + cmod->cmod_verbose_save = p_verbose + 1; + p_verbose = cmod->cmod_verbose - 1; + } + + if ((cmod->cmod_flags & (CMOD_SILENT | CMOD_UNSILENT)) + && cmod->cmod_save_msg_silent == 0) + { + cmod->cmod_save_msg_silent = msg_silent + 1; + cmod->cmod_save_msg_scroll = msg_scroll; + } + if (cmod->cmod_flags & CMOD_SILENT) + ++msg_silent; + if (cmod->cmod_flags & CMOD_UNSILENT) + msg_silent = 0; + + if (cmod->cmod_flags & CMOD_ERRSILENT) + { + ++emsg_silent; + ++cmod->cmod_did_esilent; + } + + if ((cmod->cmod_flags & CMOD_NOAUTOCMD) && cmod->cmod_save_ei == NULL) + { + // Set 'eventignore' to "all". + // First save the existing option value for restoring it later. + cmod->cmod_save_ei = vim_strsave(p_ei); + set_string_option_direct((char_u *)"ei", -1, + (char_u *)"all", OPT_FREE, SID_NONE); + } + } + + /* + * Undo and free contents of "cmod". + */ + void + undo_cmdmod(cmdmod_T *cmod) + { + if (cmod->cmod_verbose_save > 0) + { + p_verbose = cmod->cmod_verbose_save - 1; + cmod->cmod_verbose_save = 0; + } + + #ifdef HAVE_SANDBOX + if (cmod->cmod_did_sandbox) + { + --sandbox; + cmod->cmod_did_sandbox = FALSE; + } + #endif + + if (cmod->cmod_save_ei != NULL) + { + // Restore 'eventignore' to the value before ":noautocmd". + set_string_option_direct((char_u *)"ei", -1, cmod->cmod_save_ei, + OPT_FREE, SID_NONE); + free_string_option(cmod->cmod_save_ei); + cmod->cmod_save_ei = NULL; + } + + vim_regfree(cmod->cmod_filter_regmatch.regprog); + + if (cmod->cmod_save_msg_silent > 0) + { + // messages could be enabled for a serious error, need to check if the + // counters don't become negative + if (!did_emsg || msg_silent > cmod->cmod_save_msg_silent - 1) + msg_silent = cmod->cmod_save_msg_silent - 1; + emsg_silent -= cmod->cmod_did_esilent; + if (emsg_silent < 0) + emsg_silent = 0; + // Restore msg_scroll, it's set by file I/O commands, even when no + // message is actually displayed. + msg_scroll = cmod->cmod_save_msg_scroll; + + // "silent reg" or "silent echo x" inside "redir" leaves msg_col + // somewhere in the line. Put it back in the first column. + if (redirecting()) + msg_col = 0; + + cmod->cmod_save_msg_silent = 0; + cmod->cmod_did_esilent = 0; + } + } + + /* + * Parse the address range, if any, in "eap". + * May set the last search pattern, unless "silent" is TRUE. + * Return FAIL and set "errormsg" or return OK. + */ + int + parse_cmd_address(exarg_T *eap, char **errormsg, int silent) + { + int address_count = 1; + linenr_T lnum; + int need_check_cursor = FALSE; + int ret = FAIL; + + // Repeat for all ',' or ';' separated addresses. + for (;;) + { + eap->line1 = eap->line2; + eap->line2 = default_address(eap); + eap->cmd = skipwhite(eap->cmd); + lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent, + eap->addr_count == 0, address_count++); + if (eap->cmd == NULL) // error detected + goto theend; + if (lnum == MAXLNUM) + { + if (*eap->cmd == '%') // '%' - all lines + { + ++eap->cmd; + switch (eap->addr_type) + { + case ADDR_LINES: + case ADDR_OTHER: + eap->line1 = 1; + eap->line2 = curbuf->b_ml.ml_line_count; + break; + case ADDR_LOADED_BUFFERS: + { + buf_T *buf = firstbuf; + + while (buf->b_next != NULL + && buf->b_ml.ml_mfp == NULL) + buf = buf->b_next; + eap->line1 = buf->b_fnum; + buf = lastbuf; + while (buf->b_prev != NULL + && buf->b_ml.ml_mfp == NULL) + buf = buf->b_prev; + eap->line2 = buf->b_fnum; + break; + } + case ADDR_BUFFERS: + eap->line1 = firstbuf->b_fnum; + eap->line2 = lastbuf->b_fnum; + break; + case ADDR_WINDOWS: + case ADDR_TABS: + if (IS_USER_CMDIDX(eap->cmdidx)) + { + eap->line1 = 1; + eap->line2 = eap->addr_type == ADDR_WINDOWS + ? LAST_WIN_NR : LAST_TAB_NR; + } + else + { + // there is no Vim command which uses '%' and + // ADDR_WINDOWS or ADDR_TABS + *errormsg = _(e_invalid_range); + goto theend; + } + break; + case ADDR_TABS_RELATIVE: + case ADDR_UNSIGNED: + case ADDR_QUICKFIX: + *errormsg = _(e_invalid_range); + goto theend; + case ADDR_ARGUMENTS: + if (ARGCOUNT == 0) + eap->line1 = eap->line2 = 0; + else + { + eap->line1 = 1; + eap->line2 = ARGCOUNT; + } + break; + case ADDR_QUICKFIX_VALID: + #ifdef FEAT_QUICKFIX + eap->line1 = 1; + eap->line2 = qf_get_valid_size(eap); + if (eap->line2 == 0) + eap->line2 = 1; + #endif + break; + case ADDR_NONE: + // Will give an error later if a range is found. + break; + } + ++eap->addr_count; + } + else if (*eap->cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL) + { + pos_T *fp; + + // '*' - visual area + if (eap->addr_type != ADDR_LINES) + { + *errormsg = _(e_invalid_range); + goto theend; + } + + ++eap->cmd; + if (!eap->skip) + { + fp = getmark('<', FALSE); + if (check_mark(fp) == FAIL) + goto theend; + eap->line1 = fp->lnum; + fp = getmark('>', FALSE); + if (check_mark(fp) == FAIL) + goto theend; + eap->line2 = fp->lnum; + ++eap->addr_count; + } + } + } + else + eap->line2 = lnum; + eap->addr_count++; + + if (*eap->cmd == ';') + { + if (!eap->skip) + { + curwin->w_cursor.lnum = eap->line2; + + // Don't leave the cursor on an illegal line or column, but do + // accept zero as address, so 0;/PATTERN/ works correctly + // (where zero usually means to use the first line). + // Check the cursor position before returning. + if (eap->line2 > 0) + check_cursor(); + else + check_cursor_col(); + need_check_cursor = TRUE; + } + } + else if (*eap->cmd != ',') + break; + ++eap->cmd; + } + + // One address given: set start and end lines. + if (eap->addr_count == 1) + { + eap->line1 = eap->line2; + // ... but only implicit: really no address given + if (lnum == MAXLNUM) + eap->addr_count = 0; + } + ret = OK; + + theend: + if (need_check_cursor) + check_cursor(); + return ret; + } + + /* + * Append "cmd" to the error message in IObuff. + * Takes care of limiting the length and handling 0xa0, which would be + * invisible otherwise. + */ + static void + append_command(char_u *cmd) + { + size_t len = STRLEN(IObuff); + char_u *s = cmd; + char_u *d; + + if (len > IOSIZE - 100) + { + // Not enough space, truncate and put in "...". + d = IObuff + IOSIZE - 100; + d -= mb_head_off(IObuff, d); + STRCPY(d, "..."); + } + STRCAT(IObuff, ": "); + d = IObuff + STRLEN(IObuff); + while (*s != NUL && d - IObuff + 5 < IOSIZE) + { + if (enc_utf8 ? (s[0] == 0xc2 && s[1] == 0xa0) : *s == 0xa0) + { + s += enc_utf8 ? 2 : 1; + STRCPY(d, ""); + d += 4; + } + else if (d - IObuff + (*mb_ptr2len)(s) + 1 >= IOSIZE) + break; + else + MB_COPY_CHAR(s, d); + } + *d = NUL; + } + + #if defined(FEAT_EVAL) || defined(PROTO) + /* + * If "start" points "&opt", "&l:opt", "&g:opt" or "$ENV" return a pointer to + * the name. Otherwise just return "start". + */ + char_u * + skip_option_env_lead(char_u *start) + { + char_u *name = start; + + if (*start == '&') + { + if ((start[1] == 'l' || start[1] == 'g') && start[2] == ':') + name += 3; + else + name += 1; + } + else if (*start == '$') + name += 1; + return name; + } + #endif + + /* + * Return TRUE and set "*idx" if "p" points to a one letter command. + * If not in Vim9 script: + * - The 'k' command can directly be followed by any character. + * - The 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' + * but :sre[wind] is another command, as are :scr[iptnames], + * :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. + */ + static int + one_letter_cmd(char_u *p, cmdidx_T *idx) + { + if (in_vim9script()) + return FALSE; + if (*p == 'k') + { + *idx = CMD_k; + return TRUE; + } + if (p[0] == 's' + && ((p[1] == 'c' && (p[2] == NUL || (p[2] != 's' && p[2] != 'r' + && (p[3] == NUL || (p[3] != 'i' && p[4] != 'p'))))) + || p[1] == 'g' + || (p[1] == 'i' && p[2] != 'm' && p[2] != 'l' && p[2] != 'g') + || p[1] == 'I' + || (p[1] == 'r' && p[2] != 'e'))) + { + *idx = CMD_substitute; + return TRUE; + } + return FALSE; + } + + /* + * Return TRUE if "cmd" starts with "123->", a number followed by a method + * call. + */ + int + number_method(char_u *cmd) + { + char_u *p = skipdigits(cmd); + + return p > cmd && (p = skipwhite(p))[0] == '-' && p[1] == '>'; + } + + /* + * Find an Ex command by its name, either built-in or user. + * Start of the name can be found at eap->cmd. + * Sets eap->cmdidx and returns a pointer to char after the command name. + * "full" is set to TRUE if the whole command name matched. + * + * If "lookup" is not NULL recognize expression without "eval" or "call" and + * assignment without "let". Sets eap->cmdidx to the command while returning + * "eap->cmd". + * + * Returns NULL for an ambiguous user command. + */ + char_u * + find_ex_command( + exarg_T *eap, + int *full UNUSED, + int (*lookup)(char_u *, size_t, int cmd, cctx_T *) UNUSED, + cctx_T *cctx UNUSED) + { + int len; + char_u *p; + int i; + #ifndef FEAT_EVAL + int vim9 = FALSE; + #else + int vim9 = in_vim9script(); + + /* + * Recognize a Vim9 script function/method call and assignment: + * "lvar = value", "lvar(arg)", "[1, 2 3]->Func()" + */ + p = eap->cmd; + if (lookup != NULL) + { + char_u *pskip = skip_option_env_lead(eap->cmd); + + if (vim_strchr((char_u *)"{('[\"@&$", *p) != NULL + || ((p = to_name_const_end(pskip)) > eap->cmd && *p != NUL) + || (p[0] == '0' && p[1] == 'z')) + { + int oplen; + int heredoc; + char_u *swp; + + if (*eap->cmd == '&' + || (eap->cmd[0] == '$' + && eap->cmd[1] != '\'' && eap->cmd[1] != '"') + || (eap->cmd[0] == '@' + && (valid_yank_reg(eap->cmd[1], FALSE) + || eap->cmd[1] == '@'))) + { + if (*eap->cmd == '&') + { + p = eap->cmd + 1; + if (STRNCMP("l:", p, 2) == 0 || STRNCMP("g:", p, 2) == 0) + p += 2; + p = to_name_end(p, FALSE); + } + else if (*eap->cmd == '$') + p = to_name_end(eap->cmd + 1, FALSE); + else + p = eap->cmd + 2; + if (ends_excmd(*skipwhite(p))) + { + // "&option ", "$ENV " and "@r " are the start + // of an expression. + eap->cmdidx = CMD_eval; + return eap->cmd; + } + // "&option" can be followed by "->" or "=", check below + } + + swp = skipwhite(p); + + if ( + // "(..." is an expression. + // "funcname(" is always a function call. + *p == '(' + || (p == eap->cmd + ? ( + // "{..." is a dict expression or block start. + *eap->cmd == '{' + // "'string'->func()" is an expression. + || *eap->cmd == '\'' + // '"string"->func()' is an expression. + || *eap->cmd == '"' + // '$"string"->func()' is an expression. + // "$'string'->func()" is an expression. + || (eap->cmd[0] == '$' + && (eap->cmd[1] == '\'' || eap->cmd[1] == '"')) + // '0z1234->func()' is an expression. + || (eap->cmd[0] == '0' && eap->cmd[1] == 'z') + // "g:varname" is an expression. + || eap->cmd[1] == ':' + ) + // "varname->func()" is an expression. + : (*swp == '-' && swp[1] == '>'))) + { + if (*eap->cmd == '{' && ends_excmd(*skipwhite(eap->cmd + 1))) + { + // "{" by itself is the start of a block. + eap->cmdidx = CMD_block; + return eap->cmd + 1; + } + eap->cmdidx = CMD_eval; + return eap->cmd; + } + + if ((p != eap->cmd && ( + // "varname[]" is an expression. + *p == '[' + // "varname.key" is an expression. + || (*p == '.' + && (ASCII_ISALPHA(p[1]) || p[1] == '_')))) + // g:[key] is an expression + || STRNCMP(eap->cmd, "g:[", 3) == 0) + { + char_u *after = eap->cmd; + + // When followed by "=" or "+=" then it is an assignment. + // Skip over the whole thing, it can be: + // name.member = val + // name[a : b] = val + // name[idx] = val + // name[idx].member = val + // etc. + eap->cmdidx = CMD_eval; + ++emsg_silent; + if (skip_expr(&after, NULL) == OK) + { + after = skipwhite(after); + if (*after == '=' || (*after != NUL && after[1] == '=') + || (after[0] == '.' && after[1] == '.' + && after[2] == '=')) + eap->cmdidx = CMD_var; + } + --emsg_silent; + return eap->cmd; + } + + // "[...]->Method()" is a list expression, but "[a, b] = Func()" is + // an assignment. + // If there is no line break inside the "[...]" then "p" is + // advanced to after the "]" by to_name_const_end(): check if a "=" + // follows. + // If "[...]" has a line break "p" still points at the "[" and it + // can't be an assignment. + if (*eap->cmd == '[') + { + char_u *eq; + + p = to_name_const_end(eap->cmd); + if (p == eap->cmd && *p == '[') + { + int count = 0; + int semicolon = FALSE; + + p = skip_var_list(eap->cmd, TRUE, &count, &semicolon, TRUE); + } + eq = p; + if (eq != NULL) + { + eq = skipwhite(eq); + if (vim_strchr((char_u *)"+-*/%", *eq) != NULL) + ++eq; + } + if (p == NULL || p == eap->cmd || *eq != '=') + { + eap->cmdidx = CMD_eval; + return eap->cmd; + } + if (p > eap->cmd && *eq == '=') + { + eap->cmdidx = CMD_var; + return eap->cmd; + } + } + + // Recognize an assignment if we recognize the variable name: + // "g:var = expr" + // "@r = expr" + // "&opt = expr" + // "var = expr" where "var" is a variable name or we are skipping + // (variable declaration might have been skipped). + // Not "redir => var" (when skipping). + oplen = assignment_len(skipwhite(p), &heredoc); + if (oplen > 0) + { + if (((p - eap->cmd) > 2 && eap->cmd[1] == ':') + || *eap->cmd == '&' + || *eap->cmd == '$' + || *eap->cmd == '@' + || (eap->skip && IS_WHITE_OR_NUL( + *(skipwhite(p) + oplen))) + || lookup(eap->cmd, p - eap->cmd, TRUE, cctx) == OK) + { + eap->cmdidx = CMD_var; + return eap->cmd; + } + } + + // Recognize using a type for a w:, b:, t: or g: variable: + // "w:varname: number = 123". + if (eap->cmd[1] == ':' && *p == ':') + { + eap->cmdidx = CMD_eval; + return eap->cmd; + } + } + + // 1234->func() is a method call + if (number_method(eap->cmd)) + { + eap->cmdidx = CMD_eval; + return eap->cmd; + } + + // "g:", "s:" and "l:" are always assumed to be a variable, thus start + // an expression. A global/substitute/list command needs to use a + // longer name. + if (vim_strchr((char_u *)"gsl", *p) != NULL && p[1] == ':') + { + eap->cmdidx = CMD_eval; + return eap->cmd; + } + + // If it is an ID it might be a variable with an operator on the next + // line, if the variable exists it can't be an Ex command. + if (p > eap->cmd && ends_excmd(*skipwhite(p)) + && (lookup(eap->cmd, p - eap->cmd, TRUE, cctx) == OK + || (ASCII_ISALPHA(eap->cmd[0]) && eap->cmd[1] == ':'))) + { + eap->cmdidx = CMD_eval; + return eap->cmd; + } + + // Check for "++nr" and "--nr". + if (p == eap->cmd && p[0] != NUL && p[0] == p[1] + && (*p == '+' || *p == '-')) + { + eap->cmdidx = *p == '+' ? CMD_increment : CMD_decrement; + return eap->cmd + 2; + } + } + #endif + + /* + * Isolate the command and search for it in the command table. + */ + p = eap->cmd; + if (one_letter_cmd(p, &eap->cmdidx)) + { + ++p; + } + else + { + while (ASCII_ISALPHA(*p)) + ++p; + // for python 3.x support ":py3", ":python3", ":py3file", etc. + if (eap->cmd[0] == 'p' && eap->cmd[1] == 'y') + { + while (ASCII_ISALNUM(*p)) + ++p; + } + else if (*p == '9' && STRNCMP("vim9", eap->cmd, 4) == 0) + { + // include "9" for "vim9*" commands; "vim9cmd" and "vim9script". + ++p; + while (ASCII_ISALPHA(*p)) + ++p; + } + + // check for non-alpha command + if (p == eap->cmd && vim_strchr((char_u *)"@*!=><&~#}", *p) != NULL) + ++p; + len = (int)(p - eap->cmd); + // The "d" command can directly be followed by 'l' or 'p' flag, when + // not in Vim9 script. + if (!vim9 && *eap->cmd == 'd' && (p[-1] == 'l' || p[-1] == 'p')) + { + // Check for ":dl", ":dell", etc. to ":deletel": that's + // :delete with the 'l' flag. Same for 'p'. + for (i = 0; i < len; ++i) + if (eap->cmd[i] != ((char_u *)"delete")[i]) + break; + if (i == len - 1) + { + --len; + if (p[-1] == 'l') + eap->flags |= EXFLAG_LIST; + else + eap->flags |= EXFLAG_PRINT; + } + } + + if (ASCII_ISLOWER(eap->cmd[0])) + { + int c1 = eap->cmd[0]; + int c2 = len == 1 ? NUL : eap->cmd[1]; + + if (command_count != (int)CMD_SIZE) + { + iemsg(_(e_command_table_needs_to_be_updated_run_make_cmdidxs)); + getout(1); + } + + // Use a precomputed index for fast look-up in cmdnames[] + // taking into account the first 2 letters of eap->cmd. + eap->cmdidx = cmdidxs1[CharOrdLow(c1)]; + if (ASCII_ISLOWER(c2)) + eap->cmdidx += cmdidxs2[CharOrdLow(c1)][CharOrdLow(c2)]; + } + else if (ASCII_ISUPPER(eap->cmd[0])) + eap->cmdidx = CMD_Next; + else + eap->cmdidx = CMD_bang; + + for ( ; (int)eap->cmdidx < (int)CMD_SIZE; + eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) + if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, (char *)eap->cmd, + (size_t)len) == 0) + { + #ifdef FEAT_EVAL + if (full != NULL && cmdnames[eap->cmdidx].cmd_name[len] == NUL) + *full = TRUE; + #endif + break; + } + + // :Print and :mode are not supported in Vim9 script. + // Some commands cannot be shortened in Vim9 script. + if (vim9 && eap->cmdidx != CMD_SIZE) + { + if (eap->cmdidx == CMD_mode || eap->cmdidx == CMD_Print) + eap->cmdidx = CMD_SIZE; + else if ((cmdnames[eap->cmdidx].cmd_argt & EX_WHOLE) + && len < (int)STRLEN(cmdnames[eap->cmdidx].cmd_name)) + { + semsg(_(e_command_cannot_be_shortened_str), eap->cmd); + eap->cmdidx = CMD_SIZE; + } + } + + // Do not recognize ":*" as the star command unless '*' is in + // 'cpoptions'. + if (eap->cmdidx == CMD_star && vim_strchr(p_cpo, CPO_STAR) == NULL) + p = eap->cmd; + + // Look for a user defined command as a last resort. Let ":Print" be + // overruled by a user defined command. + if ((eap->cmdidx == CMD_SIZE || eap->cmdidx == CMD_Print) + && *eap->cmd >= 'A' && *eap->cmd <= 'Z') + { + // User defined commands may contain digits. + while (ASCII_ISALNUM(*p)) + ++p; + p = find_ucmd(eap, p, full, NULL, NULL); + } + if (p == NULL || p == eap->cmd) + eap->cmdidx = CMD_SIZE; + } + + // ":fina" means ":finally" in legacy script, for backwards compatibility. + if (eap->cmdidx == CMD_final && p - eap->cmd == 4 && !vim9) + eap->cmdidx = CMD_finally; + + #ifdef FEAT_EVAL + if (eap->cmdidx < CMD_SIZE + && vim9 + && !IS_WHITE_OR_NUL(*p) && *p != '\n' && *p != '!' && *p != '|' + && (eap->cmdidx < 0 || + (cmdnames[eap->cmdidx].cmd_argt & EX_NONWHITE_OK) == 0)) + { + char_u *cmd = vim_strnsave(eap->cmd, p - eap->cmd); + + semsg(_(e_command_str_not_followed_by_white_space_str), cmd, eap->cmd); + eap->cmdidx = CMD_SIZE; + vim_free(cmd); + } + #endif + + return p; + } + + #if defined(FEAT_EVAL) || defined(PROTO) + static struct cmdmod + { + char *name; + int minlen; + int has_count; // :123verbose :3tab + } cmdmods[] = { + {"aboveleft", 3, FALSE}, + {"belowright", 3, FALSE}, + {"botright", 2, FALSE}, + {"browse", 3, FALSE}, + {"confirm", 4, FALSE}, + {"filter", 4, FALSE}, + {"hide", 3, FALSE}, + {"keepalt", 5, FALSE}, + {"keepjumps", 5, FALSE}, + {"keepmarks", 3, FALSE}, + {"keeppatterns", 5, FALSE}, + {"leftabove", 5, FALSE}, + {"lockmarks", 3, FALSE}, + {"noautocmd", 3, FALSE}, + {"noswapfile", 3, FALSE}, + {"rightbelow", 6, FALSE}, + {"sandbox", 3, FALSE}, + {"silent", 3, FALSE}, + {"tab", 3, TRUE}, + {"topleft", 2, FALSE}, + {"unsilent", 3, FALSE}, + {"verbose", 4, TRUE}, + {"vertical", 4, FALSE}, + }; + + /* + * Return length of a command modifier (including optional count). + * Return zero when it's not a modifier. + */ + int + modifier_len(char_u *cmd) + { + int i, j; + char_u *p = cmd; + + if (VIM_ISDIGIT(*cmd)) + p = skipwhite(skipdigits(cmd + 1)); + for (i = 0; i < (int)ARRAY_LENGTH(cmdmods); ++i) + { + for (j = 0; p[j] != NUL; ++j) + if (p[j] != cmdmods[i].name[j]) + break; + if (!ASCII_ISALPHA(p[j]) && j >= cmdmods[i].minlen + && (p == cmd || cmdmods[i].has_count)) + return j + (int)(p - cmd); + } + return 0; + } + + /* + * Return > 0 if an Ex command "name" exists. + * Return 2 if there is an exact match. + * Return 3 if there is an ambiguous match. + */ + int + cmd_exists(char_u *name) + { + exarg_T ea; + int full = FALSE; + int i; + int j; + char_u *p; + + // Check command modifiers. + for (i = 0; i < (int)ARRAY_LENGTH(cmdmods); ++i) + { + for (j = 0; name[j] != NUL; ++j) + if (name[j] != cmdmods[i].name[j]) + break; + if (name[j] == NUL && j >= cmdmods[i].minlen) + return (cmdmods[i].name[j] == NUL ? 2 : 1); + } + + // Check built-in commands and user defined commands. + // For ":2match" and ":3match" we need to skip the number. + ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; + ea.cmdidx = (cmdidx_T)0; + ea.flags = 0; + p = find_ex_command(&ea, &full, NULL, NULL); + if (p == NULL) + return 3; + if (vim_isdigit(*name) && ea.cmdidx != CMD_match) + return 0; + if (*skipwhite(p) != NUL) + return 0; // trailing garbage + return (ea.cmdidx == CMD_SIZE ? 0 : (full ? 2 : 1)); + } + + /* + * "fullcommand" function + */ + void + f_fullcommand(typval_T *argvars, typval_T *rettv) + { + exarg_T ea; + char_u *name; + char_u *p; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) + return; + + name = argvars[0].vval.v_string; + if (name == NULL) + return; + + while (*name == ':') + name++; + name = skip_range(name, TRUE, NULL); + + ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; + ea.cmdidx = (cmdidx_T)0; + ea.addr_count = 0; + p = find_ex_command(&ea, NULL, NULL, NULL); + if (p == NULL || ea.cmdidx == CMD_SIZE) + return; + if (in_vim9script()) + { + int res; + + ++emsg_silent; + res = not_in_vim9(&ea); + --emsg_silent; + + if (res == FAIL) + return; + } + + rettv->vval.v_string = vim_strsave(IS_USER_CMDIDX(ea.cmdidx) + ? get_user_command_name(ea.useridx, ea.cmdidx) + : cmdnames[ea.cmdidx].cmd_name); + } + #endif + + cmdidx_T + excmd_get_cmdidx(char_u *cmd, int len) + { + cmdidx_T idx; + + if (!one_letter_cmd(cmd, &idx)) + for (idx = (cmdidx_T)0; (int)idx < (int)CMD_SIZE; + idx = (cmdidx_T)((int)idx + 1)) + if (STRNCMP(cmdnames[(int)idx].cmd_name, cmd, (size_t)len) == 0) + break; + + return idx; + } + + long + excmd_get_argt(cmdidx_T idx) + { + return (long)cmdnames[(int)idx].cmd_argt; + } + + /* + * Skip a range specifier of the form: addr [,addr] [;addr] .. + * + * Backslashed delimiters after / or ? will be skipped, and commands will + * not be expanded between /'s and ?'s or after "'". + * + * Also skip white space and ":" characters after the range. + * Returns the "cmd" pointer advanced to beyond the range. + */ + char_u * + skip_range( + char_u *cmd_start, + int skip_star, // skip "*" used for Visual range + int *ctx) // pointer to xp_context or NULL + { + char_u *cmd = cmd_start; + unsigned delim; + + while (vim_strchr((char_u *)" \t0123456789.$%'/?-+,;\\", *cmd) != NULL) + { + if (*cmd == '\\') + { + if (cmd[1] == '?' || cmd[1] == '/' || cmd[1] == '&') + ++cmd; + else + break; + } + else if (*cmd == '\'') + { + char_u *p = cmd; + + // a quote is only valid at the start or after a separator + while (p > cmd_start) + { + --p; + if (!VIM_ISWHITE(*p)) + break; + } + if (cmd > cmd_start && !VIM_ISWHITE(*p) && *p != ',' && *p != ';') + break; + if (*++cmd == NUL && ctx != NULL) + *ctx = EXPAND_NOTHING; + } + else if (*cmd == '/' || *cmd == '?') + { + delim = *cmd++; + while (*cmd != NUL && *cmd != delim) + if (*cmd++ == '\\' && *cmd != NUL) + ++cmd; + if (*cmd == NUL && ctx != NULL) + *ctx = EXPAND_NOTHING; + } + if (*cmd != NUL) + ++cmd; + } + + // Skip ":" and white space. + while (*cmd == ':') + cmd = skipwhite(cmd + 1); + + // Skip "*" used for Visual range. + if (skip_star && *cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL) + cmd = skipwhite(cmd + 1); + + return cmd; + } + + static void + addr_error(cmd_addr_T addr_type) + { + if (addr_type == ADDR_NONE) + emsg(_(e_no_range_allowed)); + else + emsg(_(e_invalid_range)); + } + + /* + * Return the default address for an address type. + */ + static linenr_T + default_address(exarg_T *eap) + { + linenr_T lnum = 0; + + switch (eap->addr_type) + { + case ADDR_LINES: + case ADDR_OTHER: + // Default is the cursor line number. Avoid using an invalid + // line number though. + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + lnum = curbuf->b_ml.ml_line_count; + else + lnum = curwin->w_cursor.lnum; + break; + case ADDR_WINDOWS: + lnum = CURRENT_WIN_NR; + break; + case ADDR_ARGUMENTS: + lnum = curwin->w_arg_idx + 1; + if (lnum > ARGCOUNT) + lnum = ARGCOUNT; + break; + case ADDR_LOADED_BUFFERS: + case ADDR_BUFFERS: + lnum = curbuf->b_fnum; + break; + case ADDR_TABS: + lnum = CURRENT_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + case ADDR_UNSIGNED: + lnum = 1; + break; + case ADDR_QUICKFIX: + #ifdef FEAT_QUICKFIX + lnum = qf_get_cur_idx(eap); + #endif + break; + case ADDR_QUICKFIX_VALID: + #ifdef FEAT_QUICKFIX + lnum = qf_get_cur_valid_idx(eap); + #endif + break; + case ADDR_NONE: + // Will give an error later if a range is found. + break; + } + return lnum; + } + + /* + * Get a single EX address. + * + * Set ptr to the next character after the part that was interpreted. + * Set ptr to NULL when an error is encountered. + * This may set the last used search pattern. + * + * Return MAXLNUM when no Ex address was found. + */ + static linenr_T + get_address( + exarg_T *eap UNUSED, + char_u **ptr, + cmd_addr_T addr_type, + int skip, // only skip the address, don't use it + int silent, // no errors or side effects + int to_other_file, // flag: may jump to other file + int address_count UNUSED) // 1 for first address, >1 after comma + { + int c; + int i; + long n; + char_u *cmd; + pos_T pos; + pos_T *fp; + linenr_T lnum; + buf_T *buf; + + cmd = skipwhite(*ptr); + lnum = MAXLNUM; + do + { + switch (*cmd) + { + case '.': // '.' - Cursor position + ++cmd; + switch (addr_type) + { + case ADDR_LINES: + case ADDR_OTHER: + lnum = curwin->w_cursor.lnum; + break; + case ADDR_WINDOWS: + lnum = CURRENT_WIN_NR; + break; + case ADDR_ARGUMENTS: + lnum = curwin->w_arg_idx + 1; + break; + case ADDR_LOADED_BUFFERS: + case ADDR_BUFFERS: + lnum = curbuf->b_fnum; + break; + case ADDR_TABS: + lnum = CURRENT_TAB_NR; + break; + case ADDR_NONE: + case ADDR_TABS_RELATIVE: + case ADDR_UNSIGNED: + addr_error(addr_type); + cmd = NULL; + goto error; + break; + case ADDR_QUICKFIX: + #ifdef FEAT_QUICKFIX + lnum = qf_get_cur_idx(eap); + #endif + break; + case ADDR_QUICKFIX_VALID: + #ifdef FEAT_QUICKFIX + lnum = qf_get_cur_valid_idx(eap); + #endif + break; + } + break; + + case '$': // '$' - last line + ++cmd; + switch (addr_type) + { + case ADDR_LINES: + case ADDR_OTHER: + lnum = curbuf->b_ml.ml_line_count; + break; + case ADDR_WINDOWS: + lnum = LAST_WIN_NR; + break; + case ADDR_ARGUMENTS: + lnum = ARGCOUNT; + break; + case ADDR_LOADED_BUFFERS: + buf = lastbuf; + while (buf->b_ml.ml_mfp == NULL) + { + if (buf->b_prev == NULL) + break; + buf = buf->b_prev; + } + lnum = buf->b_fnum; + break; + case ADDR_BUFFERS: + lnum = lastbuf->b_fnum; + break; + case ADDR_TABS: + lnum = LAST_TAB_NR; + break; + case ADDR_NONE: + case ADDR_TABS_RELATIVE: + case ADDR_UNSIGNED: + addr_error(addr_type); + cmd = NULL; + goto error; + break; + case ADDR_QUICKFIX: + #ifdef FEAT_QUICKFIX + lnum = qf_get_size(eap); + if (lnum == 0) + lnum = 1; + #endif + break; + case ADDR_QUICKFIX_VALID: + #ifdef FEAT_QUICKFIX + lnum = qf_get_valid_size(eap); + if (lnum == 0) + lnum = 1; + #endif + break; + } + break; + + case '\'': // ''' - mark + if (*++cmd == NUL) + { + cmd = NULL; + goto error; + } + if (addr_type != ADDR_LINES) + { + addr_error(addr_type); + cmd = NULL; + goto error; + } + if (skip) + ++cmd; + else + { + // Only accept a mark in another file when it is + // used by itself: ":'M". + fp = getmark(*cmd, to_other_file && cmd[1] == NUL); + ++cmd; + if (fp == (pos_T *)-1) + // Jumped to another file. + lnum = curwin->w_cursor.lnum; + else + { + if (check_mark(fp) == FAIL) + { + cmd = NULL; + goto error; + } + lnum = fp->lnum; + } + } + break; + + case '/': + case '?': // '/' or '?' - search + c = *cmd++; + if (addr_type != ADDR_LINES) + { + addr_error(addr_type); + cmd = NULL; + goto error; + } + if (skip) // skip "/pat/" + { + cmd = skip_regexp(cmd, c, magic_isset()); + if (*cmd == c) + ++cmd; + } + else + { + int flags; + + pos = curwin->w_cursor; // save curwin->w_cursor + + // When '/' or '?' follows another address, start from + // there. + if (lnum > 0 && lnum != MAXLNUM) + curwin->w_cursor.lnum = + lnum > curbuf->b_ml.ml_line_count + ? curbuf->b_ml.ml_line_count : lnum; + + // Start a forward search at the end of the line (unless + // before the first line). + // Start a backward search at the start of the line. + // This makes sure we never match in the current + // line, and can match anywhere in the + // next/previous line. + if (c == '/' && curwin->w_cursor.lnum > 0) + curwin->w_cursor.col = MAXCOL; + else + curwin->w_cursor.col = 0; + searchcmdlen = 0; + flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG; + if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) + { + curwin->w_cursor = pos; + cmd = NULL; + goto error; + } + lnum = curwin->w_cursor.lnum; + curwin->w_cursor = pos; + // adjust command string pointer + cmd += searchcmdlen; + } + break; + + case '\\': // "\?", "\/" or "\&", repeat search + ++cmd; + if (addr_type != ADDR_LINES) + { + addr_error(addr_type); + cmd = NULL; + goto error; + } + if (*cmd == '&') + i = RE_SUBST; + else if (*cmd == '?' || *cmd == '/') + i = RE_SEARCH; + else + { + emsg(_(e_backslash_should_be_followed_by)); + cmd = NULL; + goto error; + } + + if (!skip) + { + /* + * When search follows another address, start from + * there. + */ + if (lnum != MAXLNUM) + pos.lnum = lnum; + else + pos.lnum = curwin->w_cursor.lnum; + + /* + * Start the search just like for the above + * do_search(). + */ + if (*cmd != '?') + pos.col = MAXCOL; + else + pos.col = 0; + pos.coladd = 0; + if (searchit(curwin, curbuf, &pos, NULL, + *cmd == '?' ? BACKWARD : FORWARD, + (char_u *)"", 1L, SEARCH_MSG, i, NULL) != FAIL) + lnum = pos.lnum; + else + { + cmd = NULL; + goto error; + } + } + ++cmd; + break; + + default: + if (VIM_ISDIGIT(*cmd)) // absolute line number + lnum = getdigits(&cmd); + } + + for (;;) + { + cmd = skipwhite(cmd); + if (*cmd != '-' && *cmd != '+' && !VIM_ISDIGIT(*cmd)) + break; + + if (lnum == MAXLNUM) + { + switch (addr_type) + { + case ADDR_LINES: + case ADDR_OTHER: + // "+1" is same as ".+1" + lnum = curwin->w_cursor.lnum; + break; + case ADDR_WINDOWS: + lnum = CURRENT_WIN_NR; + break; + case ADDR_ARGUMENTS: + lnum = curwin->w_arg_idx + 1; + break; + case ADDR_LOADED_BUFFERS: + case ADDR_BUFFERS: + lnum = curbuf->b_fnum; + break; + case ADDR_TABS: + lnum = CURRENT_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + lnum = 1; + break; + case ADDR_QUICKFIX: + #ifdef FEAT_QUICKFIX + lnum = qf_get_cur_idx(eap); + #endif + break; + case ADDR_QUICKFIX_VALID: + #ifdef FEAT_QUICKFIX + lnum = qf_get_cur_valid_idx(eap); + #endif + break; + case ADDR_NONE: + case ADDR_UNSIGNED: + lnum = 0; + break; + } + } + + if (VIM_ISDIGIT(*cmd)) + i = '+'; // "number" is same as "+number" + else + i = *cmd++; + if (!VIM_ISDIGIT(*cmd)) // '+' is '+1', but '+0' is not '+1' + n = 1; + else + { + n = getdigits(&cmd); + if (n == MAXLNUM) + { + emsg(_(e_line_number_out_of_range)); + goto error; + } + } + + if (addr_type == ADDR_TABS_RELATIVE) + { + emsg(_(e_invalid_range)); + cmd = NULL; + goto error; + } + else if (addr_type == ADDR_LOADED_BUFFERS + || addr_type == ADDR_BUFFERS) + lnum = compute_buffer_local_count( + addr_type, lnum, (i == '-') ? -1 * n : n); + else + { + #ifdef FEAT_FOLDING + // Relative line addressing, need to adjust for folded lines + // now, but only do it after the first address. + if (addr_type == ADDR_LINES && (i == '-' || i == '+') + && address_count >= 2) + (void)hasFolding(lnum, NULL, &lnum); + #endif + if (i == '-') + lnum -= n; + else + { + if (n >= LONG_MAX - lnum) + { + emsg(_(e_line_number_out_of_range)); + goto error; + } + lnum += n; + } + } + } + } while (*cmd == '/' || *cmd == '?'); + + error: + *ptr = cmd; + return lnum; + } + + /* + * Set eap->line1 and eap->line2 to the whole range. + * Used for commands with the EX_DFLALL flag and no range given. + */ + static void + address_default_all(exarg_T *eap) + { + eap->line1 = 1; + switch (eap->addr_type) + { + case ADDR_LINES: + case ADDR_OTHER: + eap->line2 = curbuf->b_ml.ml_line_count; + break; + case ADDR_LOADED_BUFFERS: + { + buf_T *buf = firstbuf; + + while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) + buf = buf->b_next; + eap->line1 = buf->b_fnum; + buf = lastbuf; + while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) + buf = buf->b_prev; + eap->line2 = buf->b_fnum; + } + break; + case ADDR_BUFFERS: + eap->line1 = firstbuf->b_fnum; + eap->line2 = lastbuf->b_fnum; + break; + case ADDR_WINDOWS: + eap->line2 = LAST_WIN_NR; + break; + case ADDR_TABS: + eap->line2 = LAST_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + eap->line2 = 1; + break; + case ADDR_ARGUMENTS: + if (ARGCOUNT == 0) + eap->line1 = eap->line2 = 0; + else + eap->line2 = ARGCOUNT; + break; + case ADDR_QUICKFIX_VALID: + #ifdef FEAT_QUICKFIX + eap->line2 = qf_get_valid_size(eap); + if (eap->line2 == 0) + eap->line2 = 1; + #endif + break; + case ADDR_NONE: + case ADDR_UNSIGNED: + case ADDR_QUICKFIX: + iemsg(_("INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX")); + break; + } + } + + + /* + * Get flags from an Ex command argument. + */ + static void + get_flags(exarg_T *eap) + { + while (vim_strchr((char_u *)"lp#", *eap->arg) != NULL) + { + if (*eap->arg == 'l') + eap->flags |= EXFLAG_LIST; + else if (*eap->arg == 'p') + eap->flags |= EXFLAG_PRINT; + else + eap->flags |= EXFLAG_NR; + eap->arg = skipwhite(eap->arg + 1); + } + } + + /* + * Function called for command which is Not Implemented. NI! + */ + void + ex_ni(exarg_T *eap) + { + if (!eap->skip) + eap->errmsg = + _(e_sorry_command_is_not_available_in_this_version); + } + + #ifdef HAVE_EX_SCRIPT_NI + /* + * Function called for script command which is Not Implemented. NI! + * Skips over ":perl <skip) + ex_ni(eap); + else + vim_free(script_get(eap, eap->arg)); + } + #endif + + /* + * Check range in Ex command for validity. + * Return NULL when valid, error message when invalid. + */ + static char * + invalid_range(exarg_T *eap) + { + buf_T *buf; + + if ( eap->line1 < 0 + || eap->line2 < 0 + || eap->line1 > eap->line2) + return _(e_invalid_range); + + if (eap->argt & EX_RANGE) + { + switch (eap->addr_type) + { + case ADDR_LINES: + if (eap->line2 > curbuf->b_ml.ml_line_count + #ifdef FEAT_DIFF + + (eap->cmdidx == CMD_diffget) + #endif + ) + return _(e_invalid_range); + break; + case ADDR_ARGUMENTS: + // add 1 if ARGCOUNT is 0 + if (eap->line2 > ARGCOUNT + (!ARGCOUNT)) + return _(e_invalid_range); + break; + case ADDR_BUFFERS: + // Only a boundary check, not whether the buffers actually + // exist. + if (eap->line1 < 1 || eap->line2 > get_highest_fnum()) + return _(e_invalid_range); + break; + case ADDR_LOADED_BUFFERS: + buf = firstbuf; + while (buf->b_ml.ml_mfp == NULL) + { + if (buf->b_next == NULL) + return _(e_invalid_range); + buf = buf->b_next; + } + if (eap->line1 < buf->b_fnum) + return _(e_invalid_range); + buf = lastbuf; + while (buf->b_ml.ml_mfp == NULL) + { + if (buf->b_prev == NULL) + return _(e_invalid_range); + buf = buf->b_prev; + } + if (eap->line2 > buf->b_fnum) + return _(e_invalid_range); + break; + case ADDR_WINDOWS: + if (eap->line2 > LAST_WIN_NR) + return _(e_invalid_range); + break; + case ADDR_TABS: + if (eap->line2 > LAST_TAB_NR) + return _(e_invalid_range); + break; + case ADDR_TABS_RELATIVE: + case ADDR_OTHER: + // Any range is OK. + break; + case ADDR_QUICKFIX: + #ifdef FEAT_QUICKFIX + // No error for value that is too big, will use the last entry. + if (eap->line2 <= 0) + { + if (eap->addr_count == 0) + return _(e_no_errors); + return _(e_invalid_range); + } + #endif + break; + case ADDR_QUICKFIX_VALID: + #ifdef FEAT_QUICKFIX + if ((eap->line2 != 1 && eap->line2 > qf_get_valid_size(eap)) + || eap->line2 < 0) + return _(e_invalid_range); + #endif + break; + case ADDR_UNSIGNED: + case ADDR_NONE: + // Will give an error elsewhere. + break; + } + } + return NULL; + } + + /* + * Correct the range for zero line number, if required. + */ + static void + correct_range(exarg_T *eap) + { + if (!(eap->argt & EX_ZEROR)) // zero in range not allowed + { + if (eap->line1 == 0) + eap->line1 = 1; + if (eap->line2 == 0) + eap->line2 = 1; + } + } + + #ifdef FEAT_QUICKFIX + /* + * For a ":vimgrep" or ":vimgrepadd" command return a pointer past the + * pattern. Otherwise return eap->arg. + */ + static char_u * + skip_grep_pat(exarg_T *eap) + { + char_u *p = eap->arg; + + if (*p != NUL && (eap->cmdidx == CMD_vimgrep || eap->cmdidx == CMD_lvimgrep + || eap->cmdidx == CMD_vimgrepadd + || eap->cmdidx == CMD_lvimgrepadd + || grep_internal(eap->cmdidx))) + { + p = skip_vimgrep_pat(p, NULL, NULL); + if (p == NULL) + p = eap->arg; + } + return p; + } + + /* + * For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option + * in the command line, so that things like % get expanded. + */ + static char_u * + replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) + { + char_u *new_cmdline; + char_u *program; + char_u *pos; + char_u *ptr; + int len; + int i; + + /* + * Don't do it when ":vimgrep" is used for ":grep". + */ + if ((eap->cmdidx == CMD_make || eap->cmdidx == CMD_lmake + || eap->cmdidx == CMD_grep || eap->cmdidx == CMD_lgrep + || eap->cmdidx == CMD_grepadd + || eap->cmdidx == CMD_lgrepadd) + && !grep_internal(eap->cmdidx)) + { + if (eap->cmdidx == CMD_grep || eap->cmdidx == CMD_lgrep + || eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd) + { + if (*curbuf->b_p_gp == NUL) + program = p_gp; + else + program = curbuf->b_p_gp; + } + else + { + if (*curbuf->b_p_mp == NUL) + program = p_mp; + else + program = curbuf->b_p_mp; + } + + p = skipwhite(p); + + if ((pos = (char_u *)strstr((char *)program, "$*")) != NULL) + { + // replace $* by given arguments + i = 1; + while ((pos = (char_u *)strstr((char *)pos + 2, "$*")) != NULL) + ++i; + len = (int)STRLEN(p); + new_cmdline = alloc(STRLEN(program) + (size_t)i * (len - 2) + 1); + if (new_cmdline == NULL) + return NULL; // out of memory + ptr = new_cmdline; + while ((pos = (char_u *)strstr((char *)program, "$*")) != NULL) + { + i = (int)(pos - program); + STRNCPY(ptr, program, i); + STRCPY(ptr += i, p); + ptr += len; + program = pos + 2; + } + STRCPY(ptr, program); + } + else + { + new_cmdline = alloc(STRLEN(program) + STRLEN(p) + 2); + if (new_cmdline == NULL) + return NULL; // out of memory + STRCPY(new_cmdline, program); + STRCAT(new_cmdline, " "); + STRCAT(new_cmdline, p); + } + msg_make(p); + + // 'eap->cmd' is not set here, because it is not used at CMD_make + vim_free(*cmdlinep); + *cmdlinep = new_cmdline; + p = new_cmdline; + } + return p; + } + #endif + + /* + * Expand file name in Ex command argument. + * When an error is detected, "errormsgp" is set to a non-NULL pointer. + * Return FAIL for failure, OK otherwise. + */ + int + expand_filename( + exarg_T *eap, + char_u **cmdlinep, + char **errormsgp) + { + int has_wildcards; // need to expand wildcards + char_u *repl; + int srclen; + char_u *p; + int n; + int escaped; + + #ifdef FEAT_QUICKFIX + // Skip a regexp pattern for ":vimgrep[add] pat file..." + p = skip_grep_pat(eap); + #else + p = eap->arg; + #endif + + /* + * Decide to expand wildcards *before* replacing '%', '#', etc. If + * the file name contains a wildcard it should not cause expanding. + * (it will be expanded anyway if there is a wildcard before replacing). + */ + has_wildcards = mch_has_wildcard(p); + while (*p != NUL) + { + #ifdef FEAT_EVAL + // Skip over `=expr`, wildcards in it are not expanded. + if (p[0] == '`' && p[1] == '=') + { + p += 2; + (void)skip_expr(&p, NULL); + if (*p == '`') + ++p; + continue; + } + #endif + /* + * Quick check if this cannot be the start of a special string. + * Also removes backslash before '%', '#' and '<'. + */ + if (vim_strchr((char_u *)"%#<", *p) == NULL) + { + ++p; + continue; + } + + /* + * Try to find a match at this position. + */ + repl = eval_vars(p, eap->arg, &srclen, &(eap->do_ecmd_lnum), + errormsgp, &escaped, TRUE); + if (*errormsgp != NULL) // error detected + return FAIL; + if (repl == NULL) // no match found + { + p += srclen; + continue; + } + + // Wildcards won't be expanded below, the replacement is taken + // literally. But do expand "~/file", "~user/file" and "$HOME/file". + if (vim_strchr(repl, '$') != NULL || vim_strchr(repl, '~') != NULL) + { + char_u *l = repl; + + repl = expand_env_save(repl); + vim_free(l); + } + + // Need to escape white space et al. with a backslash. + // Don't do this for: + // - replacement that already has been escaped: "##" + // - shell commands (may have to use quotes instead). + // - non-unix systems when there is a single argument (spaces don't + // separate arguments then). + if (!eap->usefilter + && !escaped + && eap->cmdidx != CMD_bang + && eap->cmdidx != CMD_grep + && eap->cmdidx != CMD_grepadd + && eap->cmdidx != CMD_hardcopy + && eap->cmdidx != CMD_lgrep + && eap->cmdidx != CMD_lgrepadd + && eap->cmdidx != CMD_lmake + && eap->cmdidx != CMD_make + && eap->cmdidx != CMD_terminal + #ifndef UNIX + && !(eap->argt & EX_NOSPC) + #endif + ) + { + char_u *l; + #ifdef BACKSLASH_IN_FILENAME + // Don't escape a backslash here, because rem_backslash() doesn't + // remove it later. + static char_u *nobslash = (char_u *)" \t\"|"; + # define ESCAPE_CHARS nobslash + #else + # define ESCAPE_CHARS escape_chars + #endif + + for (l = repl; *l; ++l) + if (vim_strchr(ESCAPE_CHARS, *l) != NULL) + { + l = vim_strsave_escaped(repl, ESCAPE_CHARS); + if (l != NULL) + { + vim_free(repl); + repl = l; + } + break; + } + } + + // For a shell command a '!' must be escaped. + if ((eap->usefilter || eap->cmdidx == CMD_bang + || eap->cmdidx == CMD_terminal) + && vim_strpbrk(repl, (char_u *)"!") != NULL) + { + char_u *l; + + l = vim_strsave_escaped(repl, (char_u *)"!"); + if (l != NULL) + { + vim_free(repl); + repl = l; + } + } + + p = repl_cmdline(eap, p, srclen, repl, cmdlinep); + vim_free(repl); + if (p == NULL) + return FAIL; + } + + /* + * One file argument: Expand wildcards. + * Don't do this with ":r !command" or ":w !command". + */ + if ((eap->argt & EX_NOSPC) && !eap->usefilter) + { + /* + * May do this twice: + * 1. Replace environment variables. + * 2. Replace any other wildcards, remove backslashes. + */ + for (n = 1; n <= 2; ++n) + { + if (n == 2) + { + /* + * Halve the number of backslashes (this is Vi compatible). + * For Unix and OS/2, when wildcards are expanded, this is + * done by ExpandOne() below. + */ + #if defined(UNIX) + if (!has_wildcards) + #endif + backslash_halve(eap->arg); + } + + if (has_wildcards) + { + if (n == 1) + { + /* + * First loop: May expand environment variables. This + * can be done much faster with expand_env() than with + * something else (e.g., calling a shell). + * After expanding environment variables, check again + * if there are still wildcards present. + */ + if (vim_strchr(eap->arg, '$') != NULL + || vim_strchr(eap->arg, '~') != NULL) + { + expand_env_esc(eap->arg, NameBuff, MAXPATHL, + TRUE, TRUE, NULL); + has_wildcards = mch_has_wildcard(NameBuff); + p = NameBuff; + } + else + p = NULL; + } + else // n == 2 + { + expand_T xpc; + int options = WILD_LIST_NOTFOUND + | WILD_NOERROR | WILD_ADD_SLASH; + + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + if (p_wic) + options += WILD_ICASE; + p = ExpandOne(&xpc, eap->arg, NULL, + options, WILD_EXPAND_FREE); + if (p == NULL) + return FAIL; + } + if (p != NULL) + { + (void)repl_cmdline(eap, eap->arg, (int)STRLEN(eap->arg), + p, cmdlinep); + if (n == 2) // p came from ExpandOne() + vim_free(p); + } + } + } + } + return OK; + } + + /* + * Replace part of the command line, keeping eap->cmd, eap->arg and + * eap->nextcmd correct. + * "src" points to the part that is to be replaced, of length "srclen". + * "repl" is the replacement string. + * Returns a pointer to the character after the replaced string. + * Returns NULL for failure. + */ + static char_u * + repl_cmdline( + exarg_T *eap, + char_u *src, + int srclen, + char_u *repl, + char_u **cmdlinep) + { + int len; + int i; + char_u *new_cmdline; + + /* + * The new command line is build in new_cmdline[]. + * First allocate it. + * Careful: a "+cmd" argument may have been NUL terminated. + */ + len = (int)STRLEN(repl); + i = (int)(src - *cmdlinep) + (int)STRLEN(src + srclen) + len + 3; + if (eap->nextcmd != NULL) + i += (int)STRLEN(eap->nextcmd);// add space for next command + if ((new_cmdline = alloc(i)) == NULL) + return NULL; // out of memory! + + /* + * Copy the stuff before the expanded part. + * Copy the expanded stuff. + * Copy what came after the expanded part. + * Copy the next commands, if there are any. + */ + i = (int)(src - *cmdlinep); // length of part before match + mch_memmove(new_cmdline, *cmdlinep, (size_t)i); + + mch_memmove(new_cmdline + i, repl, (size_t)len); + i += len; // remember the end of the string + STRCPY(new_cmdline + i, src + srclen); + src = new_cmdline + i; // remember where to continue + + if (eap->nextcmd != NULL) // append next command + { + i = (int)STRLEN(new_cmdline) + 1; + STRCPY(new_cmdline + i, eap->nextcmd); + eap->nextcmd = new_cmdline + i; + } + eap->cmd = new_cmdline + (eap->cmd - *cmdlinep); + eap->arg = new_cmdline + (eap->arg - *cmdlinep); + if (eap->do_ecmd_cmd != NULL && eap->do_ecmd_cmd != dollar_command) + eap->do_ecmd_cmd = new_cmdline + (eap->do_ecmd_cmd - *cmdlinep); + vim_free(*cmdlinep); + *cmdlinep = new_cmdline; + + return src; + } + + /* + * Check for '|' to separate commands and '"' to start comments. + * If "keep_backslash" is TRUE do not remove any backslash. + */ + void + separate_nextcmd(exarg_T *eap, int keep_backslash) + { + char_u *p; + + #ifdef FEAT_QUICKFIX + p = skip_grep_pat(eap); + #else + p = eap->arg; + #endif + + for ( ; *p; MB_PTR_ADV(p)) + { + if (*p == Ctrl_V) + { + if ((eap->argt & (EX_CTRLV | EX_XFILE)) || keep_backslash) + ++p; // skip CTRL-V and next char + else + // remove CTRL-V and skip next char + STRMOVE(p, p + 1); + if (*p == NUL) // stop at NUL after CTRL-V + break; + } + + #ifdef FEAT_EVAL + // Skip over `=expr` when wildcards are expanded. + else if (p[0] == '`' && p[1] == '=' && (eap->argt & EX_XFILE)) + { + p += 2; + (void)skip_expr(&p, NULL); + if (*p == NUL) // stop at NUL after CTRL-V + break; + } + #endif + + // Check for '"': start of comment or '|': next command + // :@" and :*" do not start a comment! + // :redir @" doesn't either. + else if ((*p == '"' + #ifdef FEAT_EVAL + && !in_vim9script() + #endif + && !(eap->argt & EX_NOTRLCOM) + && ((eap->cmdidx != CMD_at && eap->cmdidx != CMD_star) + || p != eap->arg) + && (eap->cmdidx != CMD_redir + || p != eap->arg + 1 || p[-1] != '@')) + #ifdef FEAT_EVAL + || (*p == '#' + && in_vim9script() + && !(eap->argt & EX_NOTRLCOM) + && p > eap->cmd && VIM_ISWHITE(p[-1])) + #endif + || *p == '|' || *p == '\n') + { + /* + * We remove the '\' before the '|', unless EX_CTRLV is used + * AND 'b' is present in 'cpoptions'. + */ + if ((vim_strchr(p_cpo, CPO_BAR) == NULL + || !(eap->argt & EX_CTRLV)) && *(p - 1) == '\\') + { + if (!keep_backslash) + { + STRMOVE(p - 1, p); // remove the '\' + --p; + } + } + else + { + eap->nextcmd = check_nextcmd(p); + *p = NUL; + break; + } + } + } + + if (!(eap->argt & EX_NOTRLCOM)) // remove trailing spaces + del_trailing_spaces(eap->arg); + } + + /* + * get + command from ex argument + */ + static char_u * + getargcmd(char_u **argp) + { + char_u *arg = *argp; + char_u *command = NULL; + + if (*arg == '+') // +[command] + { + ++arg; + if (vim_isspace(*arg) || *arg == NUL) + command = dollar_command; + else + { + command = arg; + arg = skip_cmd_arg(command, TRUE); + if (*arg != NUL) + *arg++ = NUL; // terminate command with NUL + } + + arg = skipwhite(arg); // skip over spaces + *argp = arg; + } + return command; + } + + /* + * Find end of "+command" argument. Skip over "\ " and "\\". + */ + char_u * + skip_cmd_arg( + char_u *p, + int rembs) // TRUE to halve the number of backslashes + { + while (*p && !vim_isspace(*p)) + { + if (*p == '\\' && p[1] != NUL) + { + if (rembs) + STRMOVE(p, p + 1); + else + ++p; + } + MB_PTR_ADV(p); + } + return p; + } + + int + get_bad_opt(char_u *p, exarg_T *eap) + { + if (STRICMP(p, "keep") == 0) + eap->bad_char = BAD_KEEP; + else if (STRICMP(p, "drop") == 0) + eap->bad_char = BAD_DROP; + else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL) + eap->bad_char = *p; + else + return FAIL; + return OK; + } + + /* + * Get "++opt=arg" argument. + * Return FAIL or OK. + */ + static int + getargopt(exarg_T *eap) + { + char_u *arg = eap->arg + 2; + int *pp = NULL; + int bad_char_idx; + char_u *p; + + // ":edit ++[no]bin[ary] file" + if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0) + { + if (*arg == 'n') + { + arg += 2; + eap->force_bin = FORCE_NOBIN; + } + else + eap->force_bin = FORCE_BIN; + if (!checkforcmd(&arg, "binary", 3)) + return FAIL; + eap->arg = skipwhite(arg); + return OK; + } + + // ":read ++edit file" + if (STRNCMP(arg, "edit", 4) == 0) + { + eap->read_edit = TRUE; + eap->arg = skipwhite(arg + 4); + return OK; + } + + if (STRNCMP(arg, "ff", 2) == 0) + { + arg += 2; + pp = &eap->force_ff; + } + else if (STRNCMP(arg, "fileformat", 10) == 0) + { + arg += 10; + pp = &eap->force_ff; + } + else if (STRNCMP(arg, "enc", 3) == 0) + { + if (STRNCMP(arg, "encoding", 8) == 0) + arg += 8; + else + arg += 3; + pp = &eap->force_enc; + } + else if (STRNCMP(arg, "bad", 3) == 0) + { + arg += 3; + pp = &bad_char_idx; + } + + if (pp == NULL || *arg != '=') + return FAIL; + + ++arg; + *pp = (int)(arg - eap->cmd); + arg = skip_cmd_arg(arg, FALSE); + eap->arg = skipwhite(arg); + *arg = NUL; + + if (pp == &eap->force_ff) + { + if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) + return FAIL; + eap->force_ff = eap->cmd[eap->force_ff]; + } + else if (pp == &eap->force_enc) + { + // Make 'fileencoding' lower case. + for (p = eap->cmd + eap->force_enc; *p != NUL; ++p) + *p = TOLOWER_ASC(*p); + } + else + { + // Check ++bad= argument. Must be a single-byte character, "keep" or + // "drop". + if (get_bad_opt(eap->cmd + bad_char_idx, eap) == FAIL) + return FAIL; + } + + return OK; + } + + static void + ex_autocmd(exarg_T *eap) + { + /* + * Disallow autocommands from .exrc and .vimrc in current + * directory for security reasons. + */ + if (secure) + { + secure = 2; + eap->errmsg = + _(e_command_not_allowed_from_vimrc_in_current_dir_or_tag_search); + } + else if (eap->cmdidx == CMD_autocmd) + do_autocmd(eap, eap->arg, eap->forceit); + else + do_augroup(eap->arg, eap->forceit); + } + + /* + * ":doautocmd": Apply the automatic commands to the current buffer. + */ + static void + ex_doautocmd(exarg_T *eap) + { + char_u *arg = eap->arg; + int call_do_modelines = check_nomodeline(&arg); + int did_aucmd; + + (void)do_doautocmd(arg, TRUE, &did_aucmd); + // Only when there is no . + if (call_do_modelines && did_aucmd) + do_modelines(0); + } + + /* + * :[N]bunload[!] [N] [bufname] unload buffer + * :[N]bdelete[!] [N] [bufname] delete buffer from buffer list + * :[N]bwipeout[!] [N] [bufname] delete buffer really + */ + static void + ex_bunload(exarg_T *eap) + { + if (ERROR_IF_ANY_POPUP_WINDOW) + return; + eap->errmsg = do_bufdel( + eap->cmdidx == CMD_bdelete ? DOBUF_DEL + : eap->cmdidx == CMD_bwipeout ? DOBUF_WIPE + : DOBUF_UNLOAD, eap->arg, + eap->addr_count, (int)eap->line1, (int)eap->line2, eap->forceit); + } + + /* + * :[N]buffer [N] to buffer N + * :[N]sbuffer [N] to buffer N + */ + static void + ex_buffer(exarg_T *eap) + { + if (ERROR_IF_ANY_POPUP_WINDOW) + return; + if (*eap->arg) + eap->errmsg = ex_errmsg(e_trailing_characters_str, eap->arg); + else + { + if (eap->addr_count == 0) // default is current buffer + goto_buffer(eap, DOBUF_CURRENT, FORWARD, 0); + else + goto_buffer(eap, DOBUF_FIRST, FORWARD, (int)eap->line2); + if (eap->do_ecmd_cmd != NULL) + do_cmd_argument(eap->do_ecmd_cmd); + } + } + + /* + * :[N]bmodified [N] to next mod. buffer + * :[N]sbmodified [N] to next mod. buffer + */ + static void + ex_bmodified(exarg_T *eap) + { + goto_buffer(eap, DOBUF_MOD, FORWARD, (int)eap->line2); + if (eap->do_ecmd_cmd != NULL) + do_cmd_argument(eap->do_ecmd_cmd); + } + + /* + * :[N]bnext [N] to next buffer + * :[N]sbnext [N] split and to next buffer + */ + static void + ex_bnext(exarg_T *eap) + { + if (ERROR_IF_ANY_POPUP_WINDOW) + return; + + goto_buffer(eap, DOBUF_CURRENT, FORWARD, (int)eap->line2); + if (eap->do_ecmd_cmd != NULL) + do_cmd_argument(eap->do_ecmd_cmd); + } + + /* + * :[N]bNext [N] to previous buffer + * :[N]bprevious [N] to previous buffer + * :[N]sbNext [N] split and to previous buffer + * :[N]sbprevious [N] split and to previous buffer + */ + static void + ex_bprevious(exarg_T *eap) + { + if (ERROR_IF_ANY_POPUP_WINDOW) + return; + + goto_buffer(eap, DOBUF_CURRENT, BACKWARD, (int)eap->line2); + if (eap->do_ecmd_cmd != NULL) + do_cmd_argument(eap->do_ecmd_cmd); + } + + /* + * :brewind to first buffer + * :bfirst to first buffer + * :sbrewind split and to first buffer + * :sbfirst split and to first buffer + */ + static void + ex_brewind(exarg_T *eap) + { + if (ERROR_IF_ANY_POPUP_WINDOW) + return; + + goto_buffer(eap, DOBUF_FIRST, FORWARD, 0); + if (eap->do_ecmd_cmd != NULL) + do_cmd_argument(eap->do_ecmd_cmd); + } + + /* + * :blast to last buffer + * :sblast split and to last buffer + */ + static void + ex_blast(exarg_T *eap) + { + if (ERROR_IF_ANY_POPUP_WINDOW) + return; + + goto_buffer(eap, DOBUF_LAST, BACKWARD, 0); + if (eap->do_ecmd_cmd != NULL) + do_cmd_argument(eap->do_ecmd_cmd); + } + + /* + * Check if "c" ends an Ex command. + * In Vim9 script does not check for white space before #. + */ + int + ends_excmd(int c) + { + int comment_char = '"'; + + #ifdef FEAT_EVAL + if (in_vim9script()) + comment_char = '#'; + #endif + return (c == NUL || c == '|' || c == comment_char || c == '\n'); + } + + /* + * Like ends_excmd() but checks that a # in Vim9 script either has "cmd" equal + * to "cmd_start" or has a white space character before it. + */ + int + ends_excmd2(char_u *cmd_start UNUSED, char_u *cmd) + { + int c = *cmd; + + if (c == NUL || c == '|' || c == '\n') + return TRUE; + #ifdef FEAT_EVAL + if (in_vim9script()) + // # starts a comment, #{ might be a mistake, #{{ can start a fold + return c == '#' && (cmd[1] != '{' || cmd[2] == '{') + && (cmd == cmd_start || VIM_ISWHITE(cmd[-1])); + #endif + return c == '"'; + } + + #if defined(FEAT_SYN_HL) || defined(FEAT_SEARCH_EXTRA) || defined(FEAT_EVAL) \ + || defined(PROTO) + /* + * Return the next command, after the first '|' or '\n'. + * Return NULL if not found. + */ + char_u * + find_nextcmd(char_u *p) + { + while (*p != '|' && *p != '\n') + { + if (*p == NUL) + return NULL; + ++p; + } + return (p + 1); + } + #endif + + /* + * Check if *p is a separator between Ex commands, skipping over white space. + * Return NULL if it isn't, the following character if it is. + */ + char_u * + check_nextcmd(char_u *p) + { + char_u *s = skipwhite(p); + + if (*s == '|' || *s == '\n') + return (s + 1); + else + return NULL; + } + + /* + * If "eap->nextcmd" is not set, check for a next command at "p". + */ + void + set_nextcmd(exarg_T *eap, char_u *arg) + { + char_u *p = check_nextcmd(arg); + + if (eap->nextcmd == NULL) + eap->nextcmd = p; + else if (p != NULL) + // cannot use "| command" inside a {} block + semsg(_(e_cannot_use_bar_to_separate_commands_here_str), arg); + } + + /* + * - if there are more files to edit + * - and this is the last window + * - and forceit not used + * - and not repeated twice on a row + * return FAIL and give error message if 'message' TRUE + * return OK otherwise + */ + static int + check_more( + int message, // when FALSE check only, no messages + int forceit) + { + int n = ARGCOUNT - curwin->w_arg_idx - 1; + + if (!forceit && only_one_window() + && ARGCOUNT > 1 && !arg_had_last && n > 0 && quitmore == 0) + { + if (message) + { + #if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) + && curbuf->b_fname != NULL) + { + char_u buff[DIALOG_MSG_SIZE]; + + vim_snprintf((char *)buff, DIALOG_MSG_SIZE, + NGETTEXT("%d more file to edit. Quit anyway?", + "%d more files to edit. Quit anyway?", n), n); + if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1) == VIM_YES) + return OK; + return FAIL; + } + #endif + semsg(NGETTEXT(e_nr_more_file_to_edit, + e_nr_more_files_to_edit , n), n); + quitmore = 2; // next try to quit is allowed + } + return FAIL; + } + return OK; + } + + /* + * Function given to ExpandGeneric() to obtain the list of command names. + */ + char_u * + get_command_name(expand_T *xp UNUSED, int idx) + { + if (idx >= (int)CMD_SIZE) + return expand_user_command_name(idx); + return cmdnames[idx].cmd_name; + } + + static void + ex_colorscheme(exarg_T *eap) + { + if (*eap->arg == NUL) + { + #ifdef FEAT_EVAL + char_u *expr = vim_strsave((char_u *)"g:colors_name"); + char_u *p = NULL; + + if (expr != NULL) + { + ++emsg_off; + p = eval_to_string(expr, FALSE); + --emsg_off; + vim_free(expr); + } + if (p != NULL) + { + msg((char *)p); + vim_free(p); + } + else + msg("default"); + #else + msg(_("unknown")); + #endif + } + else if (load_colors(eap->arg) == FAIL) + semsg(_(e_cannot_find_color_scheme_str), eap->arg); + + #ifdef FEAT_VTP + else if (has_vtp_working()) + { + // background color change requires clear + redraw + update_screen(UPD_CLEAR); + redrawcmd(); + } + #endif + } + + static void + ex_highlight(exarg_T *eap) + { + if (*eap->arg == NUL && eap->cmd[2] == '!') + msg(_("Greetings, Vim user!")); + do_highlight(eap->arg, eap->forceit, FALSE); + } + + + /* + * Call this function if we thought we were going to exit, but we won't + * (because of an error). May need to restore the terminal mode. + */ + void + not_exiting(void) + { + exiting = FALSE; + settmode(TMODE_RAW); + } + + int + before_quit_autocmds(win_T *wp, int quit_all, int forceit) + { + apply_autocmds(EVENT_QUITPRE, NULL, NULL, FALSE, wp->w_buffer); + + // Bail out when autocommands closed the window. + // Refuse to quit when the buffer in the last window is being closed (can + // only happen in autocommands). + if (!win_valid(wp) + || curbuf_locked() + || (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) + return TRUE; + + if (quit_all || (check_more(FALSE, forceit) == OK && only_one_window())) + { + apply_autocmds(EVENT_EXITPRE, NULL, NULL, FALSE, curbuf); + // Refuse to quit when locked or when the window was closed or the + // buffer in the last window is being closed (can only happen in + // autocommands). + if (!win_valid(wp) || curbuf_locked() + || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) + return TRUE; + } + + return FALSE; + } + + /* + * ":quit": quit current window, quit Vim if the last window is closed. + * ":{nr}quit": quit window {nr} + * Also used when closing a terminal window that's the last one. + */ + void + ex_quit(exarg_T *eap) + { + win_T *wp; + + #ifdef FEAT_CMDWIN + if (cmdwin_type != 0) + { + cmdwin_result = Ctrl_C; + return; + } + #endif + // Don't quit while editing the command line. + if (text_locked()) + { + text_locked_msg(); + return; + } + if (eap->addr_count > 0) + { + int wnr = eap->line2; + + for (wp = firstwin; wp->w_next != NULL; wp = wp->w_next) + if (--wnr <= 0) + break; + } + else + wp = curwin; + + // Refuse to quit when locked. + if (curbuf_locked()) + return; + + // Trigger QuitPre and maybe ExitPre + if (before_quit_autocmds(wp, FALSE, eap->forceit)) + return; + + #ifdef FEAT_NETBEANS_INTG + netbeansForcedQuit = eap->forceit; + #endif + + /* + * If there is only one relevant window we will exit. + */ + if (check_more(FALSE, eap->forceit) == OK && only_one_window()) + exiting = TRUE; + if ((!buf_hide(wp->w_buffer) + && check_changed(wp->w_buffer, (p_awa ? CCGD_AW : 0) + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) + || check_more(TRUE, eap->forceit) == FAIL + || (only_one_window() && check_changed_any(eap->forceit, TRUE))) + { + not_exiting(); + } + else + { + // quit last window + // Note: only_one_window() returns true, even so a help window is + // still open. In that case only quit, if no address has been + // specified. Example: + // :h|wincmd w|1q - don't quit + // :h|wincmd w|q - quit + if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) + getout(0); + not_exiting(); + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + // close window; may free buffer + win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit); + } + } + + /* + * ":cquit". + */ + static void + ex_cquit(exarg_T *eap UNUSED) + { + // this does not always pass on the exit code to the Manx compiler. why? + getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE); + } + + /* + * ":qall": try to quit all windows + */ + static void + ex_quit_all(exarg_T *eap) + { + # ifdef FEAT_CMDWIN + if (cmdwin_type != 0) + { + if (eap->forceit) + cmdwin_result = K_XF1; // ex_window() takes care of this + else + cmdwin_result = K_XF2; + return; + } + # endif + + // Don't quit while editing the command line. + if (text_locked()) + { + text_locked_msg(); + return; + } + + if (before_quit_autocmds(curwin, TRUE, eap->forceit)) + return; + + exiting = TRUE; + if (eap->forceit || !check_changed_any(FALSE, FALSE)) + getout(0); + not_exiting(); + } + + /* + * ":close": close current window, unless it is the last one + */ + static void + ex_close(exarg_T *eap) + { + win_T *win; + int winnr = 0; + #ifdef FEAT_CMDWIN + if (cmdwin_type != 0) + cmdwin_result = Ctrl_C; + else + #endif + if (!text_locked() && !curbuf_locked()) + { + if (eap->addr_count == 0) + ex_win_close(eap->forceit, curwin, NULL); + else + { + FOR_ALL_WINDOWS(win) + { + winnr++; + if (winnr == eap->line2) + break; + } + if (win == NULL) + win = lastwin; + ex_win_close(eap->forceit, win, NULL); + } + } + } + + #ifdef FEAT_QUICKFIX + /* + * ":pclose": Close any preview window. + */ + static void + ex_pclose(exarg_T *eap) + { + win_T *win; + + // First close any normal window. + FOR_ALL_WINDOWS(win) + if (win->w_p_pvw) + { + ex_win_close(eap->forceit, win, NULL); + return; + } + # ifdef FEAT_PROP_POPUP + // Also when 'previewpopup' is empty, it might have been cleared. + popup_close_preview(); + # endif + } + #endif + + /* + * Close window "win" and take care of handling closing the last window for a + * modified buffer. + */ + static void + ex_win_close( + int forceit, + win_T *win, + tabpage_T *tp) // NULL or the tab page "win" is in + { + int need_hide; + buf_T *buf = win->w_buffer; + + // Never close the autocommand window. + if (win == aucmd_win) + { + emsg(_(e_cannot_close_autocmd_or_popup_window)); + return; + } + + need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); + if (need_hide && !buf_hide(buf) && !forceit) + { + #if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) + { + bufref_T bufref; + + set_bufref(&bufref, buf); + dialog_changed(buf, FALSE); + if (bufref_valid(&bufref) && bufIsChanged(buf)) + return; + need_hide = FALSE; + } + else + #endif + { + no_write_message(); + return; + } + } + + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + + // free buffer when not hiding it or when it's a scratch buffer + if (tp == NULL) + win_close(win, !need_hide && !buf_hide(buf)); + else + win_close_othertab(win, !need_hide && !buf_hide(buf), tp); + } + + /* + * Handle the argument for a tabpage related ex command. + * Returns a tabpage number. + * When an error is encountered then eap->errmsg is set. + */ + static int + get_tabpage_arg(exarg_T *eap) + { + int tab_number; + int unaccept_arg0 = (eap->cmdidx == CMD_tabmove) ? 0 : 1; + + if (eap->arg && *eap->arg != NUL) + { + char_u *p = eap->arg; + char_u *p_save; + int relative = 0; // argument +N/-N means: go to N places to the + // right/left relative to the current position. + + if (*p == '-') + { + relative = -1; + p++; + } + else if (*p == '+') + { + relative = 1; + p++; + } + + p_save = p; + tab_number = getdigits(&p); + + if (relative == 0) + { + if (STRCMP(p, "$") == 0) + tab_number = LAST_TAB_NR; + else if (STRCMP(p, "#") == 0) + if (valid_tabpage(lastused_tabpage)) + tab_number = tabpage_index(lastused_tabpage); + else + { + eap->errmsg = ex_errmsg(e_invalid_value_for_argument_str, eap->arg); + tab_number = 0; + goto theend; + } + else if (p == p_save || *p_save == '-' || *p != NUL + || tab_number > LAST_TAB_NR) + { + // No numbers as argument. + eap->errmsg = ex_errmsg(e_invalid_argument_str, eap->arg); + goto theend; + } + } + else + { + if (*p_save == NUL) + tab_number = 1; + else if (p == p_save || *p_save == '-' || *p != NUL + || tab_number == 0) + { + // No numbers as argument. + eap->errmsg = ex_errmsg(e_invalid_argument_str, eap->arg); + goto theend; + } + tab_number = tab_number * relative + tabpage_index(curtab); + if (!unaccept_arg0 && relative == -1) + --tab_number; + } + if (tab_number < unaccept_arg0 || tab_number > LAST_TAB_NR) + eap->errmsg = ex_errmsg(e_invalid_argument_str, eap->arg); + } + else if (eap->addr_count > 0) + { + if (unaccept_arg0 && eap->line2 == 0) + { + eap->errmsg = _(e_invalid_range); + tab_number = 0; + } + else + { + tab_number = eap->line2; + if (!unaccept_arg0 && *skipwhite(*eap->cmdlinep) == '-') + { + --tab_number; + if (tab_number < unaccept_arg0) + eap->errmsg = _(e_invalid_range); + } + } + } + else + { + switch (eap->cmdidx) + { + case CMD_tabnext: + tab_number = tabpage_index(curtab) + 1; + if (tab_number > LAST_TAB_NR) + tab_number = 1; + break; + case CMD_tabmove: + tab_number = LAST_TAB_NR; + break; + default: + tab_number = tabpage_index(curtab); + } + } + + theend: + return tab_number; + } + + /* + * ":tabclose": close current tab page, unless it is the last one. + * ":tabclose N": close tab page N. + */ + static void + ex_tabclose(exarg_T *eap) + { + tabpage_T *tp; + int tab_number; + + # ifdef FEAT_CMDWIN + if (cmdwin_type != 0) + cmdwin_result = K_IGNORE; + else + # endif + if (first_tabpage->tp_next == NULL) + emsg(_(e_cannot_close_last_tab_page)); + else + { + tab_number = get_tabpage_arg(eap); + if (eap->errmsg == NULL) + { + tp = find_tabpage(tab_number); + if (tp == NULL) + { + beep_flush(); + return; + } + if (tp != curtab) + { + tabpage_close_other(tp, eap->forceit); + return; + } + else if (!text_locked() && !curbuf_locked()) + tabpage_close(eap->forceit); + } + } + } + + /* + * ":tabonly": close all tab pages except the current one + */ + static void + ex_tabonly(exarg_T *eap) + { + tabpage_T *tp; + int done; + int tab_number; + + # ifdef FEAT_CMDWIN + if (cmdwin_type != 0) + cmdwin_result = K_IGNORE; + else + # endif + if (first_tabpage->tp_next == NULL) + msg(_("Already only one tab page")); + else + { + tab_number = get_tabpage_arg(eap); + if (eap->errmsg == NULL) + { + goto_tabpage(tab_number); + // Repeat this up to a 1000 times, because autocommands may + // mess up the lists. + for (done = 0; done < 1000; ++done) + { + FOR_ALL_TABPAGES(tp) + if (tp->tp_topframe != topframe) + { + tabpage_close_other(tp, eap->forceit); + // if we failed to close it quit + if (valid_tabpage(tp)) + done = 1000; + // start over, "tp" is now invalid + break; + } + if (first_tabpage->tp_next == NULL) + break; + } + } + } + } + + /* + * Close the current tab page. + */ + void + tabpage_close(int forceit) + { + // First close all the windows but the current one. If that worked then + // close the last window in this tab, that will close it. + if (!ONE_WINDOW) + close_others(TRUE, forceit); + if (ONE_WINDOW) + ex_win_close(forceit, curwin, NULL); + # ifdef FEAT_GUI + need_mouse_correct = TRUE; + # endif + } + + /* + * Close tab page "tp", which is not the current tab page. + * Note that autocommands may make "tp" invalid. + * Also takes care of the tab pages line disappearing when closing the + * last-but-one tab page. + */ + void + tabpage_close_other(tabpage_T *tp, int forceit) + { + int done = 0; + win_T *wp; + int h = tabline_height(); + + // Limit to 1000 windows, autocommands may add a window while we close + // one. OK, so I'm paranoid... + while (++done < 1000) + { + wp = tp->tp_firstwin; + ex_win_close(forceit, wp, tp); + + // Autocommands may delete the tab page under our fingers and we may + // fail to close a window with a modified buffer. + if (!valid_tabpage(tp) || tp->tp_firstwin == wp) + break; + } + + apply_autocmds(EVENT_TABCLOSED, NULL, NULL, FALSE, curbuf); + + redraw_tabline = TRUE; + if (h != tabline_height()) + shell_new_rows(); + } + + /* + * ":only". + */ + static void + ex_only(exarg_T *eap) + { + win_T *wp; + int wnr; + # ifdef FEAT_GUI + need_mouse_correct = TRUE; + # endif + if (eap->addr_count > 0) + { + wnr = eap->line2; + for (wp = firstwin; --wnr > 0; ) + { + if (wp->w_next == NULL) + break; + else + wp = wp->w_next; + } + win_goto(wp); + } + close_others(TRUE, eap->forceit); + } + + static void + ex_hide(exarg_T *eap UNUSED) + { + // ":hide" or ":hide | cmd": hide current window + if (!eap->skip) + { + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + if (eap->addr_count == 0) + win_close(curwin, FALSE); // don't free buffer + else + { + int winnr = 0; + win_T *win; + + FOR_ALL_WINDOWS(win) + { + winnr++; + if (winnr == eap->line2) + break; + } + if (win == NULL) + win = lastwin; + win_close(win, FALSE); + } + } + } + + /* + * ":stop" and ":suspend": Suspend Vim. + */ + void + ex_stop(exarg_T *eap) + { + /* + * Disallow suspending for "rvim". + */ + if (!check_restricted()) + { + if (!eap->forceit) + autowrite_all(); + apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, FALSE, NULL); + windgoto((int)Rows - 1, 0); + out_char('\n'); + out_flush(); + stoptermcap(); + out_flush(); // needed for SUN to restore xterm buffer + mch_restore_title(SAVE_RESTORE_BOTH); // restore window titles + ui_suspend(); // call machine specific function + maketitle(); + resettitle(); // force updating the title + starttermcap(); + scroll_start(); // scroll screen before redrawing + redraw_later_clear(); + shell_resized(); // may have resized window + apply_autocmds(EVENT_VIMRESUME, NULL, NULL, FALSE, NULL); + } + } + + /* + * ":exit", ":xit" and ":wq": Write file and quit the current window. + */ + static void + ex_exit(exarg_T *eap) + { + #ifdef FEAT_EVAL + if (not_in_vim9(eap) == FAIL) + return; + #endif + #ifdef FEAT_CMDWIN + if (cmdwin_type != 0) + { + cmdwin_result = Ctrl_C; + return; + } + #endif + // Don't quit while editing the command line. + if (text_locked()) + { + text_locked_msg(); + return; + } + + /* + * we plan to exit if there is only one relevant window + */ + if (check_more(FALSE, eap->forceit) == OK && only_one_window()) + exiting = TRUE; + + // Write the buffer for ":wq" or when it was changed. + // Trigger QuitPre and ExitPre. + // Check if we can exit now, after autocommands have changed things. + if (((eap->cmdidx == CMD_wq || curbufIsChanged()) && do_write(eap) == FAIL) + || before_quit_autocmds(curwin, FALSE, eap->forceit) + || check_more(TRUE, eap->forceit) == FAIL + || (only_one_window() && check_changed_any(eap->forceit, FALSE))) + { + not_exiting(); + } + else + { + if (only_one_window()) // quit last window, exit Vim + getout(0); + not_exiting(); + # ifdef FEAT_GUI + need_mouse_correct = TRUE; + # endif + // Quit current window, may free the buffer. + win_close(curwin, !buf_hide(curwin->w_buffer)); + } + } + + /* + * ":print", ":list", ":number". + */ + static void + ex_print(exarg_T *eap) + { + if (curbuf->b_ml.ml_flags & ML_EMPTY) + emsg(_(e_empty_buffer)); + else + { + for ( ;!got_int; ui_breakcheck()) + { + print_line(eap->line1, + (eap->cmdidx == CMD_number || eap->cmdidx == CMD_pound + || (eap->flags & EXFLAG_NR)), + eap->cmdidx == CMD_list || (eap->flags & EXFLAG_LIST)); + if (++eap->line1 > eap->line2) + break; + out_flush(); // show one line at a time + } + setpcmark(); + // put cursor at last line + curwin->w_cursor.lnum = eap->line2; + beginline(BL_SOL | BL_FIX); + } + + ex_no_reprint = TRUE; + } + + #ifdef FEAT_BYTEOFF + static void + ex_goto(exarg_T *eap) + { + goto_byte(eap->line2); + } + #endif + + /* + * ":shell". + */ + static void + ex_shell(exarg_T *eap UNUSED) + { + do_shell(NULL, 0); + } + + #if defined(HAVE_DROP_FILE) || defined(PROTO) + + static int drop_busy = FALSE; + static int drop_filec; + static char_u **drop_filev = NULL; + static int drop_split; + static void (*drop_callback)(void *); + static void *drop_cookie; + + static void + handle_drop_internal(void) + { + exarg_T ea; + int save_msg_scroll = msg_scroll; + + // Setting the argument list may cause screen updates and being called + // recursively. Avoid that by setting drop_busy. + drop_busy = TRUE; + + // Check whether the current buffer is changed. If so, we will need + // to split the current window or data could be lost. + // We don't need to check if the 'hidden' option is set, as in this + // case the buffer won't be lost. + if (!buf_hide(curbuf) && !drop_split) + { + ++emsg_off; + drop_split = check_changed(curbuf, CCGD_AW); + --emsg_off; + } + if (drop_split) + { + if (win_split(0, 0) == FAIL) + return; + RESET_BINDING(curwin); + + // When splitting the window, create a new alist. Otherwise the + // existing one is overwritten. + alist_unlink(curwin->w_alist); + alist_new(); + } + + /* + * Set up the new argument list. + */ + alist_set(ALIST(curwin), drop_filec, drop_filev, FALSE, NULL, 0); + + /* + * Move to the first file. + */ + // Fake up a minimal "next" command for do_argfile() + CLEAR_FIELD(ea); + ea.cmd = (char_u *)"next"; + do_argfile(&ea, 0); + + // do_ecmd() may set need_start_insertmode, but since we never left Insert + // mode that is not needed here. + need_start_insertmode = FALSE; + + // Restore msg_scroll, otherwise a following command may cause scrolling + // unexpectedly. The screen will be redrawn by the caller, thus + // msg_scroll being set by displaying a message is irrelevant. + msg_scroll = save_msg_scroll; + + if (drop_callback != NULL) + drop_callback(drop_cookie); + + drop_filev = NULL; + drop_busy = FALSE; + } + + /* + * Handle a file drop. The code is here because a drop is *nearly* like an + * :args command, but not quite (we have a list of exact filenames, so we + * don't want to (a) parse a command line, or (b) expand wildcards). So the + * code is very similar to :args and hence needs access to a lot of the static + * functions in this file. + * + * The "filev" list must have been allocated using alloc(), as should each item + * in the list. This function takes over responsibility for freeing the "filev" + * list. + */ + void + handle_drop( + int filec, // the number of files dropped + char_u **filev, // the list of files dropped + int split, // force splitting the window + void (*callback)(void *), // to be called after setting the argument + // list + void *cookie) // argument for "callback" (allocated) + { + // Cannot handle recursive drops, finish the pending one. + if (drop_busy) + { + FreeWild(filec, filev); + vim_free(cookie); + return; + } + + // When calling handle_drop() more than once in a row we only use the last + // one. + if (drop_filev != NULL) + { + FreeWild(drop_filec, drop_filev); + vim_free(drop_cookie); + } + + drop_filec = filec; + drop_filev = filev; + drop_split = split; + drop_callback = callback; + drop_cookie = cookie; + + // Postpone this when: + // - editing the command line + // - not possible to change the current buffer + // - updating the screen + // As it may change buffers and window structures that are in use and cause + // freed memory to be used. + if (text_locked() || curbuf_locked() || updating_screen) + return; + + handle_drop_internal(); + } + + /* + * To be called when text is unlocked, curbuf is unlocked or updating_screen is + * reset: Handle a postponed drop. + */ + void + handle_any_postponed_drop(void) + { + if (!drop_busy && drop_filev != NULL + && !text_locked() && !curbuf_locked() && !updating_screen) + handle_drop_internal(); + } + #endif + + /* + * ":preserve". + */ + static void + ex_preserve(exarg_T *eap UNUSED) + { + curbuf->b_flags |= BF_PRESERVED; + ml_preserve(curbuf, TRUE); + } + + /* + * ":recover". + */ + static void + ex_recover(exarg_T *eap) + { + // Set recoverymode right away to avoid the ATTENTION prompt. + recoverymode = TRUE; + if (!check_changed(curbuf, (p_awa ? CCGD_AW : 0) + | CCGD_MULTWIN + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD) + + && (*eap->arg == NUL + || setfname(curbuf, eap->arg, NULL, TRUE) == OK)) + ml_recover(TRUE); + recoverymode = FALSE; + } + + /* + * Command modifier used in a wrong way. + */ + static void + ex_wrongmodifier(exarg_T *eap) + { + eap->errmsg = _(e_invalid_command); + } + + /* + * :sview [+command] file split window with new file, read-only + * :split [[+command] file] split window with current or new file + * :vsplit [[+command] file] split window vertically with current or new file + * :new [[+command] file] split window with no or new file + * :vnew [[+command] file] split vertically window with no or new file + * :sfind [+command] file split window with file in 'path' + * + * :tabedit open new Tab page with empty window + * :tabedit [+command] file open new Tab page and edit "file" + * :tabnew [[+command] file] just like :tabedit + * :tabfind [+command] file open new Tab page and find "file" + */ + void + ex_splitview(exarg_T *eap) + { + win_T *old_curwin = curwin; +-#if defined(FEAT_SEARCHPATH) || defined(FEAT_BROWSE) + char_u *fname = NULL; +-#endif + #ifdef FEAT_BROWSE + char_u dot_path[] = "."; + int save_cmod_flags = cmdmod.cmod_flags; + #endif + int use_tab = eap->cmdidx == CMD_tabedit + || eap->cmdidx == CMD_tabfind + || eap->cmdidx == CMD_tabnew; + + if (ERROR_IF_ANY_POPUP_WINDOW) + return; + + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + + #ifdef FEAT_QUICKFIX + // A ":split" in the quickfix window works like ":new". Don't want two + // quickfix windows. But it's OK when doing ":tab split". + if (bt_quickfix(curbuf) && cmdmod.cmod_tab == 0) + { + if (eap->cmdidx == CMD_split) + eap->cmdidx = CMD_new; + if (eap->cmdidx == CMD_vsplit) + eap->cmdidx = CMD_vnew; + } + #endif + +-#ifdef FEAT_SEARCHPATH + if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) + { + fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg), + FNAME_MESS, TRUE, curbuf->b_ffname); + if (fname == NULL) + goto theend; + eap->arg = fname; + } + # ifdef FEAT_BROWSE +- else +-# endif +-#endif +-#ifdef FEAT_BROWSE +- if ((cmdmod.cmod_flags & CMOD_BROWSE) ++ else if ((cmdmod.cmod_flags & CMOD_BROWSE) + && eap->cmdidx != CMD_vnew + && eap->cmdidx != CMD_new) + { + if ( + # ifdef FEAT_GUI + !gui.in_use && + # endif + au_has_group((char_u *)"FileExplorer")) + { + // No browsing supported but we do have the file explorer: + // Edit the directory. + if (*eap->arg == NUL || !mch_isdir(eap->arg)) + eap->arg = dot_path; + } + else + { + fname = do_browse(0, (char_u *)(use_tab + ? _("Edit File in new tab page") + : _("Edit File in new window")), + eap->arg, NULL, NULL, NULL, curbuf); + if (fname == NULL) + goto theend; + eap->arg = fname; + } + } + cmdmod.cmod_flags &= ~CMOD_BROWSE; // Don't browse again in do_ecmd(). + #endif + + /* + * Either open new tab page or split the window. + */ + if (use_tab) + { + if (win_new_tabpage(cmdmod.cmod_tab != 0 ? cmdmod.cmod_tab + : eap->addr_count == 0 ? 0 + : (int)eap->line2 + 1) != FAIL) + { + do_exedit(eap, old_curwin); + + // set the alternate buffer for the window we came from + if (curwin != old_curwin + && win_valid(old_curwin) + && old_curwin->w_buffer != curbuf + && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) + old_curwin->w_alt_fnum = curbuf->b_fnum; + } + } + else if (win_split(eap->addr_count > 0 ? (int)eap->line2 : 0, + *eap->cmd == 'v' ? WSP_VERT : 0) != FAIL) + { + // Reset 'scrollbind' when editing another file, but keep it when + // doing ":split" without arguments. + if (*eap->arg != NUL) + RESET_BINDING(curwin); + else + do_check_scrollbind(FALSE); + do_exedit(eap, old_curwin); + } + + # ifdef FEAT_BROWSE + cmdmod.cmod_flags = save_cmod_flags; + # endif + +-# if defined(FEAT_SEARCHPATH) || defined(FEAT_BROWSE) + theend: + vim_free(fname); +-# endif + } + + /* + * Open a new tab page. + */ + void + tabpage_new(void) + { + exarg_T ea; + + CLEAR_FIELD(ea); + ea.cmdidx = CMD_tabnew; + ea.cmd = (char_u *)"tabn"; + ea.arg = (char_u *)""; + ex_splitview(&ea); + } + + /* + * :tabnext command + */ + static void + ex_tabnext(exarg_T *eap) + { + int tab_number; + + if (ERROR_IF_POPUP_WINDOW) + return; + switch (eap->cmdidx) + { + case CMD_tabfirst: + case CMD_tabrewind: + goto_tabpage(1); + break; + case CMD_tablast: + goto_tabpage(9999); + break; + case CMD_tabprevious: + case CMD_tabNext: + if (eap->arg && *eap->arg != NUL) + { + char_u *p = eap->arg; + char_u *p_save = p; + + tab_number = getdigits(&p); + if (p == p_save || *p_save == '-' || *p != NUL + || tab_number == 0) + { + // No numbers as argument. + eap->errmsg = ex_errmsg(e_invalid_argument_str, eap->arg); + return; + } + } + else + { + if (eap->addr_count == 0) + tab_number = 1; + else + { + tab_number = eap->line2; + if (tab_number < 1) + { + eap->errmsg = _(e_invalid_range); + return; + } + } + } + goto_tabpage(-tab_number); + break; + default: // CMD_tabnext + tab_number = get_tabpage_arg(eap); + if (eap->errmsg == NULL) + goto_tabpage(tab_number); + break; + } + } + + /* + * :tabmove command + */ + static void + ex_tabmove(exarg_T *eap) + { + int tab_number; + + tab_number = get_tabpage_arg(eap); + if (eap->errmsg == NULL) + tabpage_move(tab_number); + } + + /* + * :tabs command: List tabs and their contents. + */ + static void + ex_tabs(exarg_T *eap UNUSED) + { + tabpage_T *tp; + win_T *wp; + int tabcount = 1; + + msg_start(); + msg_scroll = TRUE; + for (tp = first_tabpage; tp != NULL && !got_int; tp = tp->tp_next) + { + msg_putchar('\n'); + vim_snprintf((char *)IObuff, IOSIZE, _("Tab page %d"), tabcount++); + msg_outtrans_attr(IObuff, HL_ATTR(HLF_T)); + out_flush(); // output one line at a time + ui_breakcheck(); + + if (tp == curtab) + wp = firstwin; + else + wp = tp->tp_firstwin; + for ( ; wp != NULL && !got_int; wp = wp->w_next) + { + msg_putchar('\n'); + msg_putchar(wp == curwin ? '>' : ' '); + msg_putchar(' '); + msg_putchar(bufIsChanged(wp->w_buffer) ? '+' : ' '); + msg_putchar(' '); + if (buf_spname(wp->w_buffer) != NULL) + vim_strncpy(IObuff, buf_spname(wp->w_buffer), IOSIZE - 1); + else + home_replace(wp->w_buffer, wp->w_buffer->b_fname, + IObuff, IOSIZE, TRUE); + msg_outtrans(IObuff); + out_flush(); // output one line at a time + ui_breakcheck(); + } + } + } + + /* + * ":mode": Set screen mode. + * If no argument given, just get the screen size and redraw. + */ + static void + ex_mode(exarg_T *eap) + { + if (*eap->arg == NUL) + shell_resized(); + else + emsg(_(e_screen_mode_setting_not_supported)); + } + + /* + * ":resize". + * set, increment or decrement current window height + */ + static void + ex_resize(exarg_T *eap) + { + int n; + win_T *wp = curwin; + + if (eap->addr_count > 0) + { + n = eap->line2; + for (wp = firstwin; wp->w_next != NULL && --n > 0; wp = wp->w_next) + ; + } + + # ifdef FEAT_GUI + need_mouse_correct = TRUE; + # endif + n = atol((char *)eap->arg); + if (cmdmod.cmod_split & WSP_VERT) + { + if (*eap->arg == '-' || *eap->arg == '+') + n += wp->w_width; + else if (n == 0 && eap->arg[0] == NUL) // default is very wide + n = 9999; + win_setwidth_win(n, wp); + } + else + { + if (*eap->arg == '-' || *eap->arg == '+') + n += wp->w_height; + else if (n == 0 && eap->arg[0] == NUL) // default is very high + n = 9999; + win_setheight_win(n, wp); + } + } + + /* + * ":find [+command] " command. + */ + static void + ex_find(exarg_T *eap) + { +-#ifdef FEAT_SEARCHPATH + char_u *fname; + int count; + + fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg), FNAME_MESS, + TRUE, curbuf->b_ffname); + if (eap->addr_count > 0) + { + // Repeat finding the file "count" times. This matters when it + // appears several times in the path. + count = eap->line2; + while (fname != NULL && --count > 0) + { + vim_free(fname); + fname = find_file_in_path(NULL, 0, FNAME_MESS, + FALSE, curbuf->b_ffname); + } + } + + if (fname != NULL) + { + eap->arg = fname; +-#endif + do_exedit(eap, NULL); +-#ifdef FEAT_SEARCHPATH + vim_free(fname); + } +-#endif + } + + /* + * ":open" simulation: for now just work like ":visual". + */ + static void + ex_open(exarg_T *eap) + { + regmatch_T regmatch; + char_u *p; + + #ifdef FEAT_EVAL + if (not_in_vim9(eap) == FAIL) + return; + #endif + curwin->w_cursor.lnum = eap->line2; + beginline(BL_SOL | BL_FIX); + if (*eap->arg == '/') + { + // ":open /pattern/": put cursor in column found with pattern + ++eap->arg; + p = skip_regexp(eap->arg, '/', magic_isset()); + *p = NUL; + regmatch.regprog = vim_regcomp(eap->arg, magic_isset() ? RE_MAGIC : 0); + if (regmatch.regprog != NULL) + { + // make a copy of the line, when searching for a mark it might be + // flushed + char_u *line = vim_strsave(ml_get_curline()); + + regmatch.rm_ic = p_ic; + if (vim_regexec(®match, line, (colnr_T)0)) + curwin->w_cursor.col = (colnr_T)(regmatch.startp[0] - line); + else + emsg(_(e_no_match)); + vim_regfree(regmatch.regprog); + vim_free(line); + } + // Move to the NUL, ignore any other arguments. + eap->arg += STRLEN(eap->arg); + } + check_cursor(); + + eap->cmdidx = CMD_visual; + do_exedit(eap, NULL); + } + + /* + * ":edit", ":badd", ":balt", ":visual". + */ + static void + ex_edit(exarg_T *eap) + { + do_exedit(eap, NULL); + } + + /* + * ":edit " command and alike. + */ + void + do_exedit( + exarg_T *eap, + win_T *old_curwin) // curwin before doing a split or NULL + { + int n; + int need_hide; + int exmode_was = exmode_active; + + if ((eap->cmdidx != CMD_pedit && ERROR_IF_POPUP_WINDOW) + || ERROR_IF_TERM_POPUP_WINDOW) + return; + /* + * ":vi" command ends Ex mode. + */ + if (exmode_active && (eap->cmdidx == CMD_visual + || eap->cmdidx == CMD_view)) + { + exmode_active = FALSE; + ex_pressedreturn = FALSE; + if (*eap->arg == NUL) + { + // Special case: ":global/pat/visual\NLvi-commands" + if (global_busy) + { + int rd = RedrawingDisabled; + int nwr = no_wait_return; + int ms = msg_scroll; + #ifdef FEAT_GUI + int he = hold_gui_events; + #endif + + if (eap->nextcmd != NULL) + { + stuffReadbuff(eap->nextcmd); + eap->nextcmd = NULL; + } + + if (exmode_was != EXMODE_VIM) + settmode(TMODE_RAW); + RedrawingDisabled = 0; + no_wait_return = 0; + need_wait_return = FALSE; + msg_scroll = 0; + #ifdef FEAT_GUI + hold_gui_events = 0; + #endif + set_must_redraw(UPD_CLEAR); + pending_exmode_active = TRUE; + + main_loop(FALSE, TRUE); + + pending_exmode_active = FALSE; + RedrawingDisabled = rd; + no_wait_return = nwr; + msg_scroll = ms; + #ifdef FEAT_GUI + hold_gui_events = he; + #endif + } + return; + } + } + + if ((eap->cmdidx == CMD_new + || eap->cmdidx == CMD_tabnew + || eap->cmdidx == CMD_tabedit + || eap->cmdidx == CMD_vnew) && *eap->arg == NUL) + { + // ":new" or ":tabnew" without argument: edit a new empty buffer + setpcmark(); + (void)do_ecmd(0, NULL, NULL, eap, ECMD_ONE, + ECMD_HIDE + (eap->forceit ? ECMD_FORCEIT : 0), + old_curwin == NULL ? curwin : NULL); + } + else if ((eap->cmdidx != CMD_split && eap->cmdidx != CMD_vsplit) + || *eap->arg != NUL + #ifdef FEAT_BROWSE + || (cmdmod.cmod_flags & CMOD_BROWSE) + #endif + ) + { + // Can't edit another file when "textlock" or "curbuf_lock" is set. + // Only ":edit" or ":script" can bring us here, others are stopped + // earlier. + if (*eap->arg != NUL && text_or_buf_locked()) + return; + + n = readonlymode; + if (eap->cmdidx == CMD_view || eap->cmdidx == CMD_sview) + readonlymode = TRUE; + else if (eap->cmdidx == CMD_enew) + readonlymode = FALSE; // 'readonly' doesn't make sense in an + // empty buffer + if (eap->cmdidx != CMD_balt && eap->cmdidx != CMD_badd) + setpcmark(); + if (do_ecmd(0, (eap->cmdidx == CMD_enew ? NULL : eap->arg), + NULL, eap, + // ":edit" goes to first line if Vi compatible + (*eap->arg == NUL && eap->do_ecmd_lnum == 0 + && vim_strchr(p_cpo, CPO_GOTO1) != NULL) + ? ECMD_ONE : eap->do_ecmd_lnum, + (buf_hide(curbuf) ? ECMD_HIDE : 0) + + (eap->forceit ? ECMD_FORCEIT : 0) + // after a split we can use an existing buffer + + (old_curwin != NULL ? ECMD_OLDBUF : 0) + + (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0) + + (eap->cmdidx == CMD_balt ? ECMD_ALTBUF : 0) + , old_curwin == NULL ? curwin : NULL) == FAIL) + { + // Editing the file failed. If the window was split, close it. + if (old_curwin != NULL) + { + need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1); + if (!need_hide || buf_hide(curbuf)) + { + #if defined(FEAT_EVAL) + cleanup_T cs; + + // Reset the error/interrupt/exception state here so that + // aborting() returns FALSE when closing a window. + enter_cleanup(&cs); + #endif + #ifdef FEAT_GUI + need_mouse_correct = TRUE; + #endif + win_close(curwin, !need_hide && !buf_hide(curbuf)); + + #if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not + // discarded by a new aborting error, interrupt, or + // uncaught exception. + leave_cleanup(&cs); + #endif + } + } + } + else if (readonlymode && curbuf->b_nwindows == 1) + { + // When editing an already visited buffer, 'readonly' won't be set + // but the previous value is kept. With ":view" and ":sview" we + // want the file to be readonly, except when another window is + // editing the same buffer. + curbuf->b_p_ro = TRUE; + } + readonlymode = n; + } + else + { + if (eap->do_ecmd_cmd != NULL) + do_cmd_argument(eap->do_ecmd_cmd); + n = curwin->w_arg_idx_invalid; + check_arg_idx(curwin); + if (n != curwin->w_arg_idx_invalid) + maketitle(); + } + + /* + * if ":split file" worked, set alternate file name in old window to new + * file + */ + if (old_curwin != NULL + && *eap->arg != NUL + && curwin != old_curwin + && win_valid(old_curwin) + && old_curwin->w_buffer != curbuf + && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) + old_curwin->w_alt_fnum = curbuf->b_fnum; + + ex_no_reprint = TRUE; + } + + #ifndef FEAT_GUI + /* + * ":gui" and ":gvim" when there is no GUI. + */ + static void + ex_nogui(exarg_T *eap) + { + eap->errmsg = _(e_gui_cannot_be_used_not_enabled_at_compile_time); + } + #endif + + #if defined(FEAT_GUI_MSWIN) && defined(FEAT_MENU) && defined(FEAT_TEAROFF) + static void + ex_tearoff(exarg_T *eap) + { + gui_make_tearoff(eap->arg); + } + #endif + + #if (defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) \ + || defined(FEAT_TERM_POPUP_MENU)) && defined(FEAT_MENU) + static void + ex_popup(exarg_T *eap) + { + # if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK) + if (gui.in_use) + gui_make_popup(eap->arg, eap->forceit); + # ifdef FEAT_TERM_POPUP_MENU + else + # endif + # endif + # ifdef FEAT_TERM_POPUP_MENU + pum_make_popup(eap->arg, eap->forceit); + # endif + } + #endif + + static void + ex_swapname(exarg_T *eap UNUSED) + { + if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL) + msg(_("No swap file")); + else + msg((char *)curbuf->b_ml.ml_mfp->mf_fname); + } + + /* + * ":syncbind" forces all 'scrollbind' windows to have the same relative + * offset. + * (1998-11-02 16:21:01 R. Edward Ralston ) + */ + static void + ex_syncbind(exarg_T *eap UNUSED) + { + win_T *wp; + win_T *save_curwin = curwin; + buf_T *save_curbuf = curbuf; + long topline; + long y; + linenr_T old_linenr = curwin->w_cursor.lnum; + + setpcmark(); + + /* + * determine max topline + */ + if (curwin->w_p_scb) + { + topline = curwin->w_topline; + FOR_ALL_WINDOWS(wp) + { + if (wp->w_p_scb && wp->w_buffer) + { + y = wp->w_buffer->b_ml.ml_line_count - get_scrolloff_value(); + if (topline > y) + topline = y; + } + } + if (topline < 1) + topline = 1; + } + else + { + topline = 1; + } + + + /* + * Set all scrollbind windows to the same topline. + */ + FOR_ALL_WINDOWS(curwin) + { + if (curwin->w_p_scb) + { + curbuf = curwin->w_buffer; + y = topline - curwin->w_topline; + if (y > 0) + scrollup(y, TRUE); + else + scrolldown(-y, TRUE); + curwin->w_scbind_pos = topline; + redraw_later(UPD_VALID); + cursor_correct(); + curwin->w_redr_status = TRUE; + } + } + curwin = save_curwin; + curbuf = save_curbuf; + if (curwin->w_p_scb) + { + did_syncbind = TRUE; + checkpcmark(); + if (old_linenr != curwin->w_cursor.lnum) + { + char_u ctrl_o[2]; + + ctrl_o[0] = Ctrl_O; + ctrl_o[1] = 0; + ins_typebuf(ctrl_o, REMAP_NONE, 0, TRUE, FALSE); + } + } + } + + + static void + ex_read(exarg_T *eap) + { + int i; + int empty = (curbuf->b_ml.ml_flags & ML_EMPTY); + linenr_T lnum; + + if (eap->usefilter) // :r!cmd + do_bang(1, eap, FALSE, FALSE, TRUE); + else + { + if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) + return; + + #ifdef FEAT_BROWSE + if (cmdmod.cmod_flags & CMOD_BROWSE) + { + char_u *browseFile; + + browseFile = do_browse(0, (char_u *)_("Append File"), eap->arg, + NULL, NULL, NULL, curbuf); + if (browseFile != NULL) + { + i = readfile(browseFile, NULL, + eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0); + vim_free(browseFile); + } + else + i = OK; + } + else + #endif + if (*eap->arg == NUL) + { + if (check_fname() == FAIL) // check for no file name + return; + i = readfile(curbuf->b_ffname, curbuf->b_fname, + eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0); + } + else + { + if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) + (void)setaltfname(eap->arg, eap->arg, (linenr_T)1); + i = readfile(eap->arg, NULL, + eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0); + + } + if (i != OK) + { + #if defined(FEAT_EVAL) + if (!aborting()) + #endif + semsg(_(e_cant_open_file_str), eap->arg); + } + else + { + if (empty && exmode_active) + { + // Delete the empty line that remains. Historically ex does + // this but vi doesn't. + if (eap->line2 == 0) + lnum = curbuf->b_ml.ml_line_count; + else + lnum = 1; + if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) + { + ml_delete(lnum); + if (curwin->w_cursor.lnum > 1 + && curwin->w_cursor.lnum >= lnum) + --curwin->w_cursor.lnum; + deleted_lines_mark(lnum, 1L); + } + } + redraw_curbuf_later(UPD_VALID); + } + } + } + + static char_u *prev_dir = NULL; + + #if defined(EXITFREE) || defined(PROTO) + void + free_cd_dir(void) + { + VIM_CLEAR(prev_dir); + VIM_CLEAR(globaldir); + } + #endif + + /* + * Get the previous directory for the given chdir scope. + */ + static char_u * + get_prevdir(cdscope_T scope) + { + if (scope == CDSCOPE_WINDOW) + return curwin->w_prevdir; + else if (scope == CDSCOPE_TABPAGE) + return curtab->tp_prevdir; + return prev_dir; + } + + /* + * Deal with the side effects of changing the current directory. + * When 'scope' is CDSCOPE_TABPAGE then this was after an ":tcd" command. + * When 'scope' is CDSCOPE_WINDOW then this was after an ":lcd" command. + */ + void + post_chdir(cdscope_T scope) + { + if (scope != CDSCOPE_WINDOW) + // Clear tab local directory for both :cd and :tcd + VIM_CLEAR(curtab->tp_localdir); + VIM_CLEAR(curwin->w_localdir); + if (scope != CDSCOPE_GLOBAL) + { + char_u *pdir = get_prevdir(scope); + + // If still in the global directory, need to remember current + // directory as the global directory. + if (globaldir == NULL && pdir != NULL) + globaldir = vim_strsave(pdir); + + // Remember this local directory for the window. + if (mch_dirname(NameBuff, MAXPATHL) == OK) + { + if (scope == CDSCOPE_TABPAGE) + curtab->tp_localdir = vim_strsave(NameBuff); + else + curwin->w_localdir = vim_strsave(NameBuff); + } + } + else + { + // We are now in the global directory, no need to remember its name. + VIM_CLEAR(globaldir); + } + + last_chdir_reason = NULL; + shorten_fnames(TRUE); + } + + /* + * Trigger DirChangedPre for "acmd_fname" with directory "new_dir". + */ + void + trigger_DirChangedPre(char_u *acmd_fname, char_u *new_dir) + { + #ifdef FEAT_EVAL + dict_T *v_event; + save_v_event_T save_v_event; + + v_event = get_v_event(&save_v_event); + (void)dict_add_string(v_event, "directory", new_dir); + dict_set_items_ro(v_event); + #endif + apply_autocmds(EVENT_DIRCHANGEDPRE, acmd_fname, new_dir, FALSE, curbuf); + #ifdef FEAT_EVAL + restore_v_event(v_event, &save_v_event); + #endif + } + + /* + * Change directory function used by :cd/:tcd/:lcd Ex commands and the + * chdir() function. + * scope == CDSCOPE_WINDOW: changes the window-local directory + * scope == CDSCOPE_TABPAGE: changes the tab-local directory + * Otherwise: changes the global directory + * Returns TRUE if the directory is successfully changed. + */ + int + changedir_func( + char_u *new_dir, + int forceit, + cdscope_T scope) + { + char_u *pdir = NULL; + int dir_differs; + char_u *acmd_fname = NULL; + char_u **pp; + char_u *tofree; + + if (new_dir == NULL || allbuf_locked()) + return FALSE; + + if (vim_strchr(p_cpo, CPO_CHDIR) != NULL && curbufIsChanged() && !forceit) + { + emsg(_(e_cannot_change_directory_buffer_is_modified_add_bang_to_override)); + return FALSE; + } + + // ":cd -": Change to previous directory + if (STRCMP(new_dir, "-") == 0) + { + pdir = get_prevdir(scope); + if (pdir == NULL) + { + emsg(_(e_no_previous_directory)); + return FALSE; + } + new_dir = pdir; + } + + // Save current directory for next ":cd -" + if (mch_dirname(NameBuff, MAXPATHL) == OK) + pdir = vim_strsave(NameBuff); + else + pdir = NULL; + + // For UNIX ":cd" means: go to home directory. + // On other systems too if 'cdhome' is set. + #if defined(UNIX) || defined(VMS) + if (*new_dir == NUL) + #else + if (*new_dir == NUL && p_cdh) + #endif + { + // use NameBuff for home directory name + # ifdef VMS + char_u *p; + + p = mch_getenv((char_u *)"SYS$LOGIN"); + if (p == NULL || *p == NUL) // empty is the same as not set + NameBuff[0] = NUL; + else + vim_strncpy(NameBuff, p, MAXPATHL - 1); + # else + expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); + # endif + new_dir = NameBuff; + } + dir_differs = pdir == NULL + || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; + if (dir_differs) + { + if (scope == CDSCOPE_WINDOW) + acmd_fname = (char_u *)"window"; + else if (scope == CDSCOPE_TABPAGE) + acmd_fname = (char_u *)"tabpage"; + else + acmd_fname = (char_u *)"global"; + trigger_DirChangedPre(acmd_fname, new_dir); + + if (vim_chdir(new_dir)) + { + emsg(_(e_command_failed)); + vim_free(pdir); + return FALSE; + } + } + + if (scope == CDSCOPE_WINDOW) + pp = &curwin->w_prevdir; + else if (scope == CDSCOPE_TABPAGE) + pp = &curtab->tp_prevdir; + else + pp = &prev_dir; + tofree = *pp; // new_dir may use this + *pp = pdir; + + post_chdir(scope); + + if (dir_differs) + apply_autocmds(EVENT_DIRCHANGED, acmd_fname, new_dir, FALSE, curbuf); + vim_free(tofree); + return TRUE; + } + + /* + * ":cd", ":tcd", ":lcd", ":chdir" ":tchdir" and ":lchdir". + */ + void + ex_cd(exarg_T *eap) + { + char_u *new_dir; + + new_dir = eap->arg; + #if !defined(UNIX) && !defined(VMS) + // for non-UNIX ":cd" means: print current directory unless 'cdhome' is set + if (*new_dir == NUL && !p_cdh) + ex_pwd(NULL); + else + #endif + { + cdscope_T scope = CDSCOPE_GLOBAL; + + if (eap->cmdidx == CMD_lcd || eap->cmdidx == CMD_lchdir) + scope = CDSCOPE_WINDOW; + else if (eap->cmdidx == CMD_tcd || eap->cmdidx == CMD_tchdir) + scope = CDSCOPE_TABPAGE; + + if (changedir_func(new_dir, eap->forceit, scope)) + { + // Echo the new current directory if the command was typed. + if (KeyTyped || p_verbose >= 5) + ex_pwd(eap); + } + } + } + + /* + * ":pwd". + */ + static void + ex_pwd(exarg_T *eap UNUSED) + { + if (mch_dirname(NameBuff, MAXPATHL) == OK) + { + #ifdef BACKSLASH_IN_FILENAME + slash_adjust(NameBuff); + #endif + if (p_verbose > 0) + { + char *context = "global"; + + if (last_chdir_reason != NULL) + context = last_chdir_reason; + else if (curwin->w_localdir != NULL) + context = "window"; + else if (curtab->tp_localdir != NULL) + context = "tabpage"; + smsg("[%s] %s", context, (char *)NameBuff); + } + else + msg((char *)NameBuff); + } + else + emsg(_(e_directory_unknown)); + } + + /* + * ":=". + */ + static void + ex_equal(exarg_T *eap) + { + smsg("%ld", (long)eap->line2); + ex_may_print(eap); + } + + static void + ex_sleep(exarg_T *eap) + { + int n; + long len; + + if (cursor_valid()) + { + n = W_WINROW(curwin) + curwin->w_wrow - msg_scrolled; + if (n >= 0) + windgoto(n, curwin->w_wincol + curwin->w_wcol); + } + + len = eap->line2; + switch (*eap->arg) + { + case 'm': break; + case NUL: len *= 1000L; break; + default: semsg(_(e_invalid_argument_str), eap->arg); return; + } + + // Hide the cursor if invoked with ! + do_sleep(len, eap->forceit); + } + + /* + * Sleep for "msec" milliseconds, but keep checking for a CTRL-C every second. + * Hide the cursor if "hide_cursor" is TRUE. + */ + void + do_sleep(long msec, int hide_cursor) + { + long done = 0; + long wait_now; + # ifdef ELAPSED_FUNC + elapsed_T start_tv; + + // Remember at what time we started, so that we know how much longer we + // should wait after waiting for a bit. + ELAPSED_INIT(start_tv); + # endif + + if (hide_cursor) + cursor_sleep(); + else + cursor_on(); + + out_flush_cursor(FALSE, FALSE); + while (!got_int && done < msec) + { + wait_now = msec - done > 1000L ? 1000L : msec - done; + #ifdef FEAT_TIMERS + { + long due_time = check_due_timer(); + + if (due_time > 0 && due_time < wait_now) + wait_now = due_time; + } + #endif + #ifdef FEAT_JOB_CHANNEL + if (has_any_channel() && wait_now > 20L) + wait_now = 20L; + #endif + #ifdef FEAT_SOUND + if (has_any_sound_callback() && wait_now > 20L) + wait_now = 20L; + #endif + ui_delay(wait_now, TRUE); + + #ifdef FEAT_JOB_CHANNEL + if (has_any_channel()) + ui_breakcheck_force(TRUE); + else + #endif + ui_breakcheck(); + #ifdef MESSAGE_QUEUE + // Process the netbeans and clientserver messages that may have been + // received in the call to ui_breakcheck() when the GUI is in use. This + // may occur when running a test case. + parse_queued_messages(); + #endif + + # ifdef ELAPSED_FUNC + // actual time passed + done = ELAPSED_FUNC(start_tv); + # else + // guestimate time passed (will actually be more) + done += wait_now; + # endif + } + + // If CTRL-C was typed to interrupt the sleep, drop the CTRL-C from the + // input buffer, otherwise a following call to input() fails. + if (got_int) + (void)vpeekc(); + + if (hide_cursor) + cursor_unsleep(); + } + + /* + * ":winsize" command (obsolete). + */ + static void + ex_winsize(exarg_T *eap) + { + int w, h; + char_u *arg = eap->arg; + char_u *p; + + if (!isdigit(*arg)) + { + semsg(_(e_invalid_argument_str), arg); + return; + } + w = getdigits(&arg); + arg = skipwhite(arg); + p = arg; + h = getdigits(&arg); + if (*p != NUL && *arg == NUL) + set_shellsize(w, h, TRUE); + else + emsg(_(e_winsize_requires_two_number_arguments)); + } + + static void + ex_wincmd(exarg_T *eap) + { + int xchar = NUL; + char_u *p; + + if (*eap->arg == 'g' || *eap->arg == Ctrl_G) + { + // CTRL-W g and CTRL-W CTRL-G have an extra command character + if (eap->arg[1] == NUL) + { + emsg(_(e_invalid_argument)); + return; + } + xchar = eap->arg[1]; + p = eap->arg + 2; + } + else + p = eap->arg + 1; + + set_nextcmd(eap, p); + p = skipwhite(p); + if (*p != NUL && *p != ( + #ifdef FEAT_EVAL + in_vim9script() ? '#' : + #endif + '"') + && eap->nextcmd == NULL) + emsg(_(e_invalid_argument)); + else if (!eap->skip) + { + // Pass flags on for ":vertical wincmd ]". + postponed_split_flags = cmdmod.cmod_split; + postponed_split_tab = cmdmod.cmod_tab; + do_window(*eap->arg, eap->addr_count > 0 ? eap->line2 : 0L, xchar); + postponed_split_flags = 0; + postponed_split_tab = 0; + } + } + + #if defined(FEAT_GUI) || defined(UNIX) || defined(VMS) || defined(MSWIN) + /* + * ":winpos". + */ + static void + ex_winpos(exarg_T *eap) + { + int x, y; + char_u *arg = eap->arg; + char_u *p; + + if (*arg == NUL) + { + # if defined(FEAT_GUI) || defined(MSWIN) + # ifdef VIMDLL + if (gui.in_use ? gui_mch_get_winpos(&x, &y) != FAIL : + mch_get_winpos(&x, &y) != FAIL) + # elif defined(FEAT_GUI) + if (gui.in_use && gui_mch_get_winpos(&x, &y) != FAIL) + # else + if (mch_get_winpos(&x, &y) != FAIL) + # endif + { + sprintf((char *)IObuff, _("Window position: X %d, Y %d"), x, y); + msg((char *)IObuff); + } + else + # endif + emsg(_(e_obtaining_window_position_not_implemented_for_this_platform)); + } + else + { + x = getdigits(&arg); + arg = skipwhite(arg); + p = arg; + y = getdigits(&arg); + if (*p == NUL || *arg != NUL) + { + emsg(_(e_winpos_requires_two_number_arguments)); + return; + } + # ifdef FEAT_GUI + if (gui.in_use) + gui_mch_set_winpos(x, y); + else if (gui.starting) + { + // Remember the coordinates for when the window is opened. + gui_win_x = x; + gui_win_y = y; + } + # if defined(HAVE_TGETENT) || defined(VIMDLL) + else + # endif + # endif + # if defined(MSWIN) && (!defined(FEAT_GUI) || defined(VIMDLL)) + mch_set_winpos(x, y); + # endif + # ifdef HAVE_TGETENT + if (*T_CWP) + term_set_winpos(x, y); + # endif + } + } + #endif + + /* + * Handle command that work like operators: ":delete", ":yank", ":>" and ":<". + */ + static void + ex_operators(exarg_T *eap) + { + oparg_T oa; + + clear_oparg(&oa); + oa.regname = eap->regname; + oa.start.lnum = eap->line1; + oa.end.lnum = eap->line2; + oa.line_count = eap->line2 - eap->line1 + 1; + oa.motion_type = MLINE; + virtual_op = FALSE; + if (eap->cmdidx != CMD_yank) // position cursor for undo + { + setpcmark(); + curwin->w_cursor.lnum = eap->line1; + beginline(BL_SOL | BL_FIX); + } + + if (VIsual_active) + end_visual_mode(); + + switch (eap->cmdidx) + { + case CMD_delete: + oa.op_type = OP_DELETE; + op_delete(&oa); + break; + + case CMD_yank: + oa.op_type = OP_YANK; + (void)op_yank(&oa, FALSE, TRUE); + break; + + default: // CMD_rshift or CMD_lshift + if ( + #ifdef FEAT_RIGHTLEFT + (eap->cmdidx == CMD_rshift) ^ curwin->w_p_rl + #else + eap->cmdidx == CMD_rshift + #endif + ) + oa.op_type = OP_RSHIFT; + else + oa.op_type = OP_LSHIFT; + op_shift(&oa, FALSE, eap->amount); + break; + } + virtual_op = MAYBE; + ex_may_print(eap); + } + + /* + * ":put". + */ + static void + ex_put(exarg_T *eap) + { + // ":0put" works like ":1put!". + if (eap->line2 == 0) + { + eap->line2 = 1; + eap->forceit = TRUE; + } + curwin->w_cursor.lnum = eap->line2; + check_cursor_col(); + do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1L, + PUT_LINE|PUT_CURSLINE); + } + + /* + * Handle ":copy" and ":move". + */ + static void + ex_copymove(exarg_T *eap) + { + long n; + + #ifdef FEAT_EVAL + if (not_in_vim9(eap) == FAIL) + return; + #endif + n = get_address(eap, &eap->arg, eap->addr_type, FALSE, FALSE, FALSE, 1); + if (eap->arg == NULL) // error detected + { + eap->nextcmd = NULL; + return; + } + get_flags(eap); + + /* + * move or copy lines from 'eap->line1'-'eap->line2' to below line 'n' + */ + if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count) + { + emsg(_(e_invalid_range)); + return; + } + + if (eap->cmdidx == CMD_move) + { + if (do_move(eap->line1, eap->line2, n) == FAIL) + return; + } + else + ex_copy(eap->line1, eap->line2, n); + u_clearline(); + beginline(BL_SOL | BL_FIX); + ex_may_print(eap); + } + + /* + * Print the current line if flags were given to the Ex command. + */ + void + ex_may_print(exarg_T *eap) + { + if (eap->flags != 0) + { + print_line(curwin->w_cursor.lnum, (eap->flags & EXFLAG_NR), + (eap->flags & EXFLAG_LIST)); + ex_no_reprint = TRUE; + } + } + + /* + * ":smagic" and ":snomagic". + */ + static void + ex_submagic(exarg_T *eap) + { + optmagic_T saved = magic_overruled; + + magic_overruled = eap->cmdidx == CMD_smagic + ? OPTION_MAGIC_ON : OPTION_MAGIC_OFF; + ex_substitute(eap); + magic_overruled = saved; + } + + /* + * ":join". + */ + static void + ex_join(exarg_T *eap) + { + curwin->w_cursor.lnum = eap->line1; + if (eap->line1 == eap->line2) + { + if (eap->addr_count >= 2) // :2,2join does nothing + return; + if (eap->line2 == curbuf->b_ml.ml_line_count) + { + beep_flush(); + return; + } + ++eap->line2; + } + (void)do_join(eap->line2 - eap->line1 + 1, !eap->forceit, TRUE, TRUE, TRUE); + beginline(BL_WHITE | BL_FIX); + ex_may_print(eap); + } + + /* + * ":[addr]@r" or ":[addr]*r": execute register + */ + static void + ex_at(exarg_T *eap) + { + int c; + int prev_len = typebuf.tb_len; + + curwin->w_cursor.lnum = eap->line2; + check_cursor_col(); + + #ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; // disallow scrolling here + #endif + + // get the register name. No name means to use the previous one + c = *eap->arg; + if (c == NUL || (c == '*' && *eap->cmd == '*')) + c = '@'; + // Put the register in the typeahead buffer with the "silent" flag. + if (do_execreg(c, TRUE, vim_strchr(p_cpo, CPO_EXECBUF) != NULL, TRUE) + == FAIL) + { + beep_flush(); + } + else + { + int save_efr = exec_from_reg; + + exec_from_reg = TRUE; + + /* + * Execute from the typeahead buffer. + * Continue until the stuff buffer is empty and all added characters + * have been consumed. + */ + while (!stuff_empty() || typebuf.tb_len > prev_len) + (void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE); + + exec_from_reg = save_efr; + } + } + + /* + * ":!". + */ + static void + ex_bang(exarg_T *eap) + { + do_bang(eap->addr_count, eap, eap->forceit, TRUE, TRUE); + } + + /* + * ":undo". + */ + static void + ex_undo(exarg_T *eap) + { + if (eap->addr_count == 1) // :undo 123 + undo_time(eap->line2, FALSE, FALSE, TRUE); + else + u_undo(1); + } + + #ifdef FEAT_PERSISTENT_UNDO + static void + ex_wundo(exarg_T *eap) + { + char_u hash[UNDO_HASH_SIZE]; + + u_compute_hash(hash); + u_write_undo(eap->arg, eap->forceit, curbuf, hash); + } + + static void + ex_rundo(exarg_T *eap) + { + char_u hash[UNDO_HASH_SIZE]; + + u_compute_hash(hash); + u_read_undo(eap->arg, hash, NULL); + } + #endif + + /* + * ":redo". + */ + static void + ex_redo(exarg_T *eap UNUSED) + { + u_redo(1); + } + + /* + * ":earlier" and ":later". + */ + static void + ex_later(exarg_T *eap) + { + long count = 0; + int sec = FALSE; + int file = FALSE; + char_u *p = eap->arg; + + if (*p == NUL) + count = 1; + else if (isdigit(*p)) + { + count = getdigits(&p); + switch (*p) + { + case 's': ++p; sec = TRUE; break; + case 'm': ++p; sec = TRUE; count *= 60; break; + case 'h': ++p; sec = TRUE; count *= 60 * 60; break; + case 'd': ++p; sec = TRUE; count *= 24 * 60 * 60; break; + case 'f': ++p; file = TRUE; break; + } + } + + if (*p != NUL) + semsg(_(e_invalid_argument_str), eap->arg); + else + undo_time(eap->cmdidx == CMD_earlier ? -count : count, + sec, file, FALSE); + } + + /* + * ":redir": start/stop redirection. + */ + static void + ex_redir(exarg_T *eap) + { + char *mode; + char_u *fname; + char_u *arg = eap->arg; + + #ifdef FEAT_EVAL + if (redir_execute) + { + emsg(_(e_cannot_use_redir_inside_execute)); + return; + } + #endif + + if (STRICMP(eap->arg, "END") == 0) + close_redir(); + else + { + if (*arg == '>') + { + ++arg; + if (*arg == '>') + { + ++arg; + mode = "a"; + } + else + mode = "w"; + arg = skipwhite(arg); + + close_redir(); + + // Expand environment variables and "~/". + fname = expand_env_save(arg); + if (fname == NULL) + return; + #ifdef FEAT_BROWSE + if (cmdmod.cmod_flags & CMOD_BROWSE) + { + char_u *browseFile; + + browseFile = do_browse(BROWSE_SAVE, + (char_u *)_("Save Redirection"), + fname, NULL, NULL, + (char_u *)_(BROWSE_FILTER_ALL_FILES), curbuf); + if (browseFile == NULL) + return; // operation cancelled + vim_free(fname); + fname = browseFile; + eap->forceit = TRUE; // since dialog already asked + } + #endif + + redir_fd = open_exfile(fname, eap->forceit, mode); + vim_free(fname); + } + #ifdef FEAT_EVAL + else if (*arg == '@') + { + // redirect to a register a-z (resp. A-Z for appending) + close_redir(); + ++arg; + if (ASCII_ISALPHA(*arg) + # ifdef FEAT_CLIPBOARD + || *arg == '*' + || *arg == '+' + # endif + || *arg == '"') + { + redir_reg = *arg++; + if (*arg == '>' && arg[1] == '>') // append + arg += 2; + else + { + // Can use both "@a" and "@a>". + if (*arg == '>') + arg++; + // Make register empty when not using @A-@Z and the + // command is valid. + if (*arg == NUL && !isupper(redir_reg)) + write_reg_contents(redir_reg, (char_u *)"", -1, FALSE); + } + } + if (*arg != NUL) + { + redir_reg = 0; + semsg(_(e_invalid_argument_str), eap->arg); + } + } + else if (*arg == '=' && arg[1] == '>') + { + int append; + + // redirect to a variable + close_redir(); + arg += 2; + + if (*arg == '>') + { + ++arg; + append = TRUE; + } + else + append = FALSE; + + if (var_redir_start(skipwhite(arg), append) == OK) + redir_vname = 1; + } + #endif + + // TODO: redirect to a buffer + + else + semsg(_(e_invalid_argument_str), eap->arg); + } + + // Make sure redirection is not off. Can happen for cmdline completion + // that indirectly invokes a command to catch its output. + if (redir_fd != NULL + #ifdef FEAT_EVAL + || redir_reg || redir_vname + #endif + ) + redir_off = FALSE; + } + + /* + * ":redraw": force redraw + */ + void + ex_redraw(exarg_T *eap) + { + int r = RedrawingDisabled; + int p = p_lz; + + RedrawingDisabled = 0; + p_lz = FALSE; + validate_cursor(); + update_topline(); + update_screen(eap->forceit ? UPD_CLEAR : VIsual_active ? UPD_INVERTED : 0); + if (need_maketitle) + maketitle(); + #if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL)) + # ifdef VIMDLL + if (!gui.in_use) + # endif + resize_console_buf(); + #endif + RedrawingDisabled = r; + p_lz = p; + + // After drawing the statusline screen_attr may still be set. + screen_stop_highlight(); + + // Reset msg_didout, so that a message that's there is overwritten. + msg_didout = FALSE; + msg_col = 0; + + // No need to wait after an intentional redraw. + need_wait_return = FALSE; + + // When invoked from a callback or autocmd the command line may be active. + if (State & MODE_CMDLINE) + redrawcmdline(); + + out_flush(); + } + + /* + * ":redrawstatus": force redraw of status line(s) + */ + static void + ex_redrawstatus(exarg_T *eap UNUSED) + { + int r = RedrawingDisabled; + int p = p_lz; + + RedrawingDisabled = 0; + p_lz = FALSE; + if (eap->forceit) + status_redraw_all(); + else + status_redraw_curbuf(); + update_screen(VIsual_active ? UPD_INVERTED : 0); + RedrawingDisabled = r; + p_lz = p; + out_flush(); + } + + /* + * ":redrawtabline": force redraw of the tabline + */ + static void + ex_redrawtabline(exarg_T *eap UNUSED) + { + int r = RedrawingDisabled; + int p = p_lz; + + RedrawingDisabled = 0; + p_lz = FALSE; + + draw_tabline(); + + RedrawingDisabled = r; + p_lz = p; + out_flush(); + } + + static void + close_redir(void) + { + if (redir_fd != NULL) + { + fclose(redir_fd); + redir_fd = NULL; + } + #ifdef FEAT_EVAL + redir_reg = 0; + if (redir_vname) + { + var_redir_stop(); + redir_vname = 0; + } + #endif + } + + #if (defined(FEAT_SESSION) || defined(FEAT_EVAL)) || defined(PROTO) + int + vim_mkdir_emsg(char_u *name, int prot UNUSED) + { + if (vim_mkdir(name, prot) != 0) + { + semsg(_(e_cannot_create_directory_str), name); + return FAIL; + } + return OK; + } + #endif + + /* + * Open a file for writing for an Ex command, with some checks. + * Return file descriptor, or NULL on failure. + */ + FILE * + open_exfile( + char_u *fname, + int forceit, + char *mode) // "w" for create new file or "a" for append + { + FILE *fd; + + #ifdef UNIX + // with Unix it is possible to open a directory + if (mch_isdir(fname)) + { + semsg(_(e_str_is_directory), fname); + return NULL; + } + #endif + if (!forceit && *mode != 'a' && vim_fexists(fname)) + { + semsg(_(e_str_exists_add_bang_to_override), fname); + return NULL; + } + + if ((fd = mch_fopen((char *)fname, mode)) == NULL) + semsg(_(e_cannot_open_str_for_writing_2), fname); + + return fd; + } + + /* + * ":mark" and ":k". + */ + static void + ex_mark(exarg_T *eap) + { + pos_T pos; + + #ifdef FEAT_EVAL + if (not_in_vim9(eap) == FAIL) + return; + #endif + if (*eap->arg == NUL) // No argument? + emsg(_(e_argument_required)); + else if (eap->arg[1] != NUL) // more than one character? + semsg(_(e_trailing_characters_str), eap->arg); + else + { + pos = curwin->w_cursor; // save curwin->w_cursor + curwin->w_cursor.lnum = eap->line2; + beginline(BL_WHITE | BL_FIX); + if (setmark(*eap->arg) == FAIL) // set mark + emsg(_(e_argument_must_be_letter_or_forward_backward_quote)); + curwin->w_cursor = pos; // restore curwin->w_cursor + } + } + + /* + * Update w_topline, w_leftcol and the cursor position. + */ + void + update_topline_cursor(void) + { + check_cursor(); // put cursor on valid line + update_topline(); + if (!curwin->w_p_wrap) + validate_cursor(); + update_curswant(); + } + + /* + * Save the current State and go to Normal mode. + * Return TRUE if the typeahead could be saved. + */ + int + save_current_state(save_state_T *sst) + { + sst->save_msg_scroll = msg_scroll; + sst->save_restart_edit = restart_edit; + sst->save_msg_didout = msg_didout; + sst->save_State = State; + sst->save_insertmode = p_im; + sst->save_finish_op = finish_op; + sst->save_opcount = opcount; + sst->save_reg_executing = reg_executing; + sst->save_pending_end_reg_executing = pending_end_reg_executing; + + msg_scroll = FALSE; // no msg scrolling in Normal mode + restart_edit = 0; // don't go to Insert mode + p_im = FALSE; // don't use 'insertmode' + + sst->save_script_version = current_sctx.sc_version; + current_sctx.sc_version = 1; // not in Vim9 script + + /* + * Save the current typeahead. This is required to allow using ":normal" + * from an event handler and makes sure we don't hang when the argument + * ends with half a command. + */ + save_typeahead(&sst->tabuf); + return sst->tabuf.typebuf_valid; + } + + void + restore_current_state(save_state_T *sst) + { + // Restore the previous typeahead. + restore_typeahead(&sst->tabuf, FALSE); + + msg_scroll = sst->save_msg_scroll; + restart_edit = sst->save_restart_edit; + p_im = sst->save_insertmode; + finish_op = sst->save_finish_op; + opcount = sst->save_opcount; + reg_executing = sst->save_reg_executing; + pending_end_reg_executing = sst->save_pending_end_reg_executing; + msg_didout |= sst->save_msg_didout; // don't reset msg_didout now + current_sctx.sc_version = sst->save_script_version; + + // Restore the state (needed when called from a function executed for + // 'indentexpr'). Update the mouse and cursor, they may have changed. + State = sst->save_State; + #ifdef CURSOR_SHAPE + ui_cursor_shape(); // may show different cursor shape + #endif + } + + /* + * ":normal[!] {commands}": Execute normal mode commands. + */ + void + ex_normal(exarg_T *eap) + { + save_state_T save_state; + char_u *arg = NULL; + int l; + char_u *p; + + if (ex_normal_lock > 0) + { + emsg(_(e_not_allowed_here)); + return; + } + if (ex_normal_busy >= p_mmd) + { + emsg(_(e_recursive_use_of_normal_too_deep)); + return; + } + + /* + * vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do + * this for the K_SPECIAL leading byte, otherwise special keys will not + * work. + */ + if (has_mbyte) + { + int len = 0; + + // Count the number of characters to be escaped. + for (p = eap->arg; *p != NUL; ++p) + { + #ifdef FEAT_GUI + if (*p == CSI) // leadbyte CSI + len += 2; + #endif + for (l = (*mb_ptr2len)(p) - 1; l > 0; --l) + if (*++p == K_SPECIAL // trailbyte K_SPECIAL or CSI + #ifdef FEAT_GUI + || *p == CSI + #endif + ) + len += 2; + } + if (len > 0) + { + arg = alloc(STRLEN(eap->arg) + len + 1); + if (arg != NULL) + { + len = 0; + for (p = eap->arg; *p != NUL; ++p) + { + arg[len++] = *p; + #ifdef FEAT_GUI + if (*p == CSI) + { + arg[len++] = KS_EXTRA; + arg[len++] = (int)KE_CSI; + } + #endif + for (l = (*mb_ptr2len)(p) - 1; l > 0; --l) + { + arg[len++] = *++p; + if (*p == K_SPECIAL) + { + arg[len++] = KS_SPECIAL; + arg[len++] = KE_FILLER; + } + #ifdef FEAT_GUI + else if (*p == CSI) + { + arg[len++] = KS_EXTRA; + arg[len++] = (int)KE_CSI; + } + #endif + } + arg[len] = NUL; + } + } + } + } + + ++ex_normal_busy; + if (save_current_state(&save_state)) + { + /* + * Repeat the :normal command for each line in the range. When no + * range given, execute it just once, without positioning the cursor + * first. + */ + do + { + if (eap->addr_count != 0) + { + curwin->w_cursor.lnum = eap->line1++; + curwin->w_cursor.col = 0; + check_cursor_moved(curwin); + } + + exec_normal_cmd(arg != NULL + ? arg + : eap->arg, eap->forceit ? REMAP_NONE : REMAP_YES, FALSE); + } + while (eap->addr_count > 0 && eap->line1 <= eap->line2 && !got_int); + } + + // Might not return to the main loop when in an event handler. + update_topline_cursor(); + + restore_current_state(&save_state); + --ex_normal_busy; + setmouse(); + #ifdef CURSOR_SHAPE + ui_cursor_shape(); // may show different cursor shape + #endif + + vim_free(arg); + } + + /* + * ":startinsert", ":startreplace" and ":startgreplace" + */ + static void + ex_startinsert(exarg_T *eap) + { + if (eap->forceit) + { + // cursor line can be zero on startup + if (!curwin->w_cursor.lnum) + curwin->w_cursor.lnum = 1; + set_cursor_for_append_to_line(); + } + #ifdef FEAT_TERMINAL + // Ignore this when running in an active terminal. + if (term_job_running(curbuf->b_term)) + return; + #endif + + // Ignore the command when already in Insert mode. Inserting an + // expression register that invokes a function can do this. + if (State & MODE_INSERT) + return; + + if (eap->cmdidx == CMD_startinsert) + restart_edit = 'a'; + else if (eap->cmdidx == CMD_startreplace) + restart_edit = 'R'; + else + restart_edit = 'V'; + + if (!eap->forceit) + { + if (eap->cmdidx == CMD_startinsert) + restart_edit = 'i'; + curwin->w_curswant = 0; // avoid MAXCOL + } + + if (VIsual_active) + showmode(); + } + + /* + * ":stopinsert" + */ + static void + ex_stopinsert(exarg_T *eap UNUSED) + { + restart_edit = 0; + stop_insert_mode = TRUE; + clearmode(); + } + + /* + * Execute normal mode command "cmd". + * "remap" can be REMAP_NONE or REMAP_YES. + */ + void + exec_normal_cmd(char_u *cmd, int remap, int silent) + { + // Stuff the argument into the typeahead buffer. + ins_typebuf(cmd, remap, 0, TRUE, silent); + exec_normal(FALSE, FALSE, FALSE); + } + + /* + * Execute normal_cmd() until there is no typeahead left. + * When "use_vpeekc" is TRUE use vpeekc() to check for available chars. + */ + void + exec_normal(int was_typed, int use_vpeekc, int may_use_terminal_loop UNUSED) + { + oparg_T oa; + int c; + + // When calling vpeekc() from feedkeys() it will return Ctrl_C when there + // is nothing to get, so also check for Ctrl_C. + clear_oparg(&oa); + finish_op = FALSE; + while ((!stuff_empty() + || ((was_typed || !typebuf_typed()) && typebuf.tb_len > 0) + || (use_vpeekc && (c = vpeekc()) != NUL && c != Ctrl_C)) + && !got_int) + { + update_topline_cursor(); + #ifdef FEAT_TERMINAL + if (may_use_terminal_loop && term_use_loop() + && oa.op_type == OP_NOP && oa.regname == NUL + && !VIsual_active) + { + // If terminal_loop() returns OK we got a key that is handled + // in Normal model. With FAIL we first need to position the + // cursor and the screen needs to be redrawn. + if (terminal_loop(TRUE) == OK) + normal_cmd(&oa, TRUE); + } + else + #endif + // execute a Normal mode cmd + normal_cmd(&oa, TRUE); + } + } + + #ifdef FEAT_FIND_ID + static void + ex_checkpath(exarg_T *eap) + { + find_pattern_in_path(NULL, 0, 0, FALSE, FALSE, CHECK_PATH, 1L, + eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW, + (linenr_T)1, (linenr_T)MAXLNUM); + } + + #if defined(FEAT_QUICKFIX) + /* + * ":psearch" + */ + static void + ex_psearch(exarg_T *eap) + { + g_do_tagpreview = p_pvh; + ex_findpat(eap); + g_do_tagpreview = 0; + } + #endif + + static void + ex_findpat(exarg_T *eap) + { + int whole = TRUE; + long n; + char_u *p; + int action; + + switch (cmdnames[eap->cmdidx].cmd_name[2]) + { + case 'e': // ":psearch", ":isearch" and ":dsearch" + if (cmdnames[eap->cmdidx].cmd_name[0] == 'p') + action = ACTION_GOTO; + else + action = ACTION_SHOW; + break; + case 'i': // ":ilist" and ":dlist" + action = ACTION_SHOW_ALL; + break; + case 'u': // ":ijump" and ":djump" + action = ACTION_GOTO; + break; + default: // ":isplit" and ":dsplit" + action = ACTION_SPLIT; + break; + } + + n = 1; + if (vim_isdigit(*eap->arg)) // get count + { + n = getdigits(&eap->arg); + eap->arg = skipwhite(eap->arg); + } + if (*eap->arg == '/') // Match regexp, not just whole words + { + whole = FALSE; + ++eap->arg; + p = skip_regexp(eap->arg, '/', magic_isset()); + if (*p) + { + *p++ = NUL; + p = skipwhite(p); + + // Check for trailing illegal characters + if (!ends_excmd2(eap->arg, p)) + eap->errmsg = ex_errmsg(e_trailing_characters_str, p); + else + set_nextcmd(eap, p); + } + } + if (!eap->skip) + find_pattern_in_path(eap->arg, 0, (int)STRLEN(eap->arg), + whole, !eap->forceit, + *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY, + n, action, eap->line1, eap->line2); + } + #endif + + + #ifdef FEAT_QUICKFIX + /* + * ":ptag", ":ptselect", ":ptjump", ":ptnext", etc. + */ + static void + ex_ptag(exarg_T *eap) + { + g_do_tagpreview = p_pvh; // will be reset to 0 in ex_tag_cmd() + ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1); + } + + /* + * ":pedit" + */ + static void + ex_pedit(exarg_T *eap) + { + win_T *curwin_save = curwin; + + if (ERROR_IF_ANY_POPUP_WINDOW) + return; + + // Open the preview window or popup and make it the current window. + g_do_tagpreview = p_pvh; + prepare_tagpreview(TRUE, TRUE, FALSE); + + // Edit the file. + do_exedit(eap, NULL); + + if (curwin != curwin_save && win_valid(curwin_save)) + { + // Return cursor to where we were + validate_cursor(); + redraw_later(UPD_VALID); + win_enter(curwin_save, TRUE); + } + # ifdef FEAT_PROP_POPUP + else if (WIN_IS_POPUP(curwin)) + { + // can't keep focus in popup window + win_enter(firstwin, TRUE); + } + # endif + g_do_tagpreview = 0; + } + #endif + + /* + * ":stag", ":stselect" and ":stjump". + */ + static void + ex_stag(exarg_T *eap) + { + postponed_split = -1; + postponed_split_flags = cmdmod.cmod_split; + postponed_split_tab = cmdmod.cmod_tab; + ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1); + postponed_split_flags = 0; + postponed_split_tab = 0; + } + + /* + * ":tag", ":tselect", ":tjump", ":tnext", etc. + */ + static void + ex_tag(exarg_T *eap) + { + ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name); + } + + static void + ex_tag_cmd(exarg_T *eap, char_u *name) + { + int cmd; + + switch (name[1]) + { + case 'j': cmd = DT_JUMP; // ":tjump" + break; + case 's': cmd = DT_SELECT; // ":tselect" + break; + case 'p': cmd = DT_PREV; // ":tprevious" + break; + case 'N': cmd = DT_PREV; // ":tNext" + break; + case 'n': cmd = DT_NEXT; // ":tnext" + break; + case 'o': cmd = DT_POP; // ":pop" + break; + case 'f': // ":tfirst" + case 'r': cmd = DT_FIRST; // ":trewind" + break; + case 'l': cmd = DT_LAST; // ":tlast" + break; + default: // ":tag" + #ifdef FEAT_CSCOPE + if (p_cst && *eap->arg != NUL) + { + ex_cstag(eap); + return; + } + #endif + cmd = DT_TAG; + break; + } + + if (name[0] == 'l') + { + #ifndef FEAT_QUICKFIX + ex_ni(eap); + return; + #else + cmd = DT_LTAG; + #endif + } + + do_tag(eap->arg, cmd, eap->addr_count > 0 ? (int)eap->line2 : 1, + eap->forceit, TRUE); + } + + /* + * Check "str" for starting with a special cmdline variable. + * If found return one of the SPEC_ values and set "*usedlen" to the length of + * the variable. Otherwise return -1 and "*usedlen" is unchanged. + */ + int + find_cmdline_var(char_u *src, int *usedlen) + { + int len; + int i; + static char *(spec_str[]) = { + "%", + #define SPEC_PERC 0 + "#", + #define SPEC_HASH (SPEC_PERC + 1) + "", // cursor word + #define SPEC_CWORD (SPEC_HASH + 1) + "", // cursor WORD + #define SPEC_CCWORD (SPEC_CWORD + 1) + "", // expr under cursor + #define SPEC_CEXPR (SPEC_CCWORD + 1) + "", // cursor path name + #define SPEC_CFILE (SPEC_CEXPR + 1) + "", // ":so" file name + #define SPEC_SFILE (SPEC_CFILE + 1) + "", // ":so" file line number + #define SPEC_SLNUM (SPEC_SFILE + 1) + "", // call stack + #define SPEC_STACK (SPEC_SLNUM + 1) + "